在Invoke上调用async会产生CancellationToken错误。

问题描述 投票:1回答:1

我正在.NET Framework 4.8上为Entity Framework 6.x创建一个通用的异步更新方法。下面是这个类。

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

在... result,它就会爆炸。

System.ArgumentException: 类型为'System.Linq.Expressions.Expression [System.Func[MyEntities.Models.SomeEntity,System.Boolean]]的对象不能转换为'System.Threading.CancellationToken'类型。

如何调用 "System.Threading.CancellationToken "类型?Invoke 在异步方法上?我到底做错了什么?

更新1: 我用了这个 回答 来添加(Task)到我的代码中,但显然我做错了什么。

更新2.在@StephenCleary的基础上,做了这样的修改:在我的代码中添加(Task),但显然我做错了。 按照@StephenCleary的建议,做了这个修改。

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

第一个建议没有用,因为 dbTable 没有 SingleOrDefaultAsync.

现在得到这个错误。

'System.Data.Entity.QueryableExtensions.SingleOrDefaultAsync(System.Linq.IQueryable, System.Threading.CancellationToken)'的最佳重载方法匹配有一些无效的参数。

更新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();
    }
}
c# async-await cancellation-token
1个回答
2
投票

通用异步更新方法

我不得不怀疑,一个复杂得可怕的方法是否真的比六七个干净明显的方法更容易维护。

话说回来,你得到的错误表明你检索的是错误的 SingleOrDefaultAsync. 事实上,该代码 typeof(QueryableExtensions).GetMethods().First(m => m.Name == "SingleOrDefaultAsync" && m.GetParameters().Length == 2) 只是返回该名称的第一个有两个参数的方法。这对于确保你得到正确的方法来说,信息量是不够的。

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

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);
© www.soinside.com 2019 - 2024. All rights reserved.