在 Invoke 上调用异步会产生 CancellationToken 错误

Calling async on Invoke produces CancellationToken error

我正在为 .NET Framework 4.8 上的 Entity Framework 6.x 创建通用异步更新方法。这是 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);

            // 
            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);

                // 
                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;
        }
    }
}

result 上,它爆炸了:

System.ArgumentException: Object of type 'System.Linq.Expressions.Expression [System.Func[MyEntities.Models.SomeEntity,System.Boolean]]' cannot be converted to type 'System.Threading.CancellationToken'.

如何在异步方法上调用 Invoke?我究竟做错了什么?

更新 1: 我用这个 answer 将(任务)添加到我的代码中,但显然我做错了什么。

更新 2: 在 @StephenCleary 之后,进行了此更改:

dynamic dbTable = context.Set(tableType);
var result = await QueryableExtensions.SingleOrDefaultAsync(dbTable, lambda);

第一个建议无效,因为 dbTable 没有 SingleOrDefaultAsync

现在出现此错误:

The best overloaded method match for 'System.Data.Entity.QueryableExtensions.SingleOrDefaultAsync(System.Linq.IQueryable, System.Threading.CancellationToken)' has some invalid arguments

更新 3 和解决方案:感谢@StephenCleary,这个解决方案非常有效:

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();
    }
}

generic async update method

我不得不质疑一个极其复杂的方法是否真的比六个左右简洁明了的方法更易于维护。

就是说,您收到的错误表明您正在检索 SingleOrDefaultAsync 的错误重载。实际上,代码 typeof(QueryableExtensions).GetMethods().First(m => m.Name == "SingleOrDefaultAsync" && m.GetParameters().Length == 2) 只是 returns 该名称的第一个具有两个参数的方法。信息量不足,无法确保您获得正确的方法。

您可以添加更多检查以确保参数是您期望的类型,或者您可以完全放弃反射,因为它似乎没有必要:

var dbTable = context.Set(tableType);
var result = await dbTable.SingleOrDefaultAsync(lambda);

这是假设 dbTable 是合适的类型。如果它是某种未指定的类型(例如 object),您仍然可以通过将其设置为 dynamic:

来使用编译器的重载解析
dynamic dbTable = context.Set(tableType);
var result = await QueryableExtensions.SingleOrDefaultAsync(dbTable, lambda);