I'm creating a generic async update method for Entity Framework 6.x on .NET Framework 4.8. Here's the class:
public class GenericUpdate<TEntity, TId, TDto>
where TEntity : class
where TId : IConvertible
where TDto : class
{
public async Task UpdateSingleAsync(string searchPropertyName, TId searchPropertyValue, TDto dto)
{
try
{
var tableType = typeof(TEntity);
// https://stackoverflow.com/questions/30029230/dynamic-lambda-expression-for-singleordefault
var param = Expression.Parameter(tableType, "m");
var searchProperty = Expression.PropertyOrField(param, searchPropertyName);
var constSearchValue = Expression.Constant(searchPropertyValue);
var body = Expression.Equal(searchProperty, constSearchValue);
var lambda = Expression.Lambda(body, param);
using (var context = new MyContext())
{
var dbTable = context.Set(tableType);
var genericSingleOrDefaultAsyncMethod =
typeof(QueryableExtensions).GetMethods().First(m => m.Name == "SingleOrDefaultAsync" && m.GetParameters().Length == 2);
var specificSingleOrDefaultAsync = genericSingleOrDefaultAsyncMethod.MakeGenericMethod(tableType);
// https://stackoverflow.com/a/16153317/177416
var result = (Task<TEntity>) specificSingleOrDefault.Invoke(null, new object[] { dbTable, lambda });
await result;
context.Entry(result).CurrentValues.SetValues(dto);
await context.SaveChangesAsync();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
throw;
}
}
}
On the result
, it blows up with:
System.ArgumentException: Object of type 'System.Linq.Expressions.Expression [System.Func[MyEntities.Models.SomeEntity,System.Boolean]]' cannot be converted to type 'System.Threading.CancellationToken'.
How do you call Invoke
on an async method? What am I doing wrong?
Update 1: I used this answer to add the (Task) to my code but obviously I'm doing something wrong.
Update 2: Following @StephenCleary, made this change:
dynamic dbTable = context.Set(tableType);
var result = await QueryableExtensions.SingleOrDefaultAsync(dbTable, lambda);
The first suggestion didn't work as dbTable
didn't have a SingleOrDefaultAsync
.
Now getting this error:
The best overloaded method match for 'System.Data.Entity.QueryableExtensions.SingleOrDefaultAsync(System.Linq.IQueryable, System.Threading.CancellationToken)' has some invalid arguments
Update 3 & solution: Thanks to @StephenCleary, this solution works like a charm:
dynamic lambda = Expression.Lambda(body, param);
using (var context = new MyContext())
{
dynamic dbTable = context.Set(tableType);
var result = await QueryableExtensions.SingleOrDefaultAsync(dbTable, lambda);
if(result != null)
{
context.Entry(result).CurrentValues.SetValues(dto);
await context.SaveChangesAsync();
}
}