Вызов asyn c в Invoke вызывает ошибку CancellationToken - PullRequest
1 голос
/ 24 апреля 2020

Я создаю обобщенный c asyn c метод обновления для Entity Framework 6.x on. NET Framework 4.8. Вот класс:

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 '.

Как вы вызываете Invoke в асинхронном режиме c метод? Что я делаю неправильно?

Обновление 1: Я использовал этот ответ , чтобы добавить (Задачу) в мой код, но, очевидно, я делаю что-то не так.

Обновление 2: После @StephenCleary внесены следующие изменения:

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

Первое предложение не сработало, поскольку dbTable не имело SingleOrDefaultAsync.

Теперь получаем эту ошибку:

Наилучший перегруженный метод соответствует «System.Data.Entity.QueryableExtensions.SingleOrDefaultAsyn c (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();
    }
}

1 Ответ

2 голосов
/ 24 апреля 2020

generi c asyn c метод обновления

У меня есть вопрос, действительно ли один ужасно сложный метод более удобен в обслуживании, чем полдюжины или около того чистых и очевидных методов .

Тем не менее, ошибка, которую вы получаете, указывает на то, что вы получаете неправильную перегрузку 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);
...