Пользовательский EF Core AddOrUpdate с составными ключами - PullRequest
2 голосов
/ 17 марта 2019

Я построил расширение для Microsoft.EntityFrameworkCore, которое реализует AddOrUpdateMethod.Он работает нормально, но с сущностями с составным первичным ключом метод AnyAsync всегда возвращает false, даже если есть объекты с одинаковым ключом.

Это метод:

public static async Task AddOrUpdateAsync<TEntity>(this DbSet<TEntity> table, Expression<Func<TEntity, object>> key, Expression<Func<TEntity, bool>> deleteExpression, params TEntity[] entities) where TEntity : class
{
    var getKeyFunction = key.Compile();
    var getShouldDeleteFunction = deleteExpression.Compile();
    var context = GetDbContext(table);
    foreach (var entity in entities)
    {
        var primaryKey = getKeyFunction(entity);
        var body = Expression.Equal(Expression.Convert(key.Body, primaryKey.GetType()), Expression.Constant(primaryKey));
        Expression<Func<TEntity, bool>> query = Expression.Lambda<Func<TEntity, bool>>(body, key.Parameters);
        var exist = await table.AnyAsync(query);
        context.Entry(entity).State = exist
            ? getShouldDeleteFunction(entity) ? EntityState.Deleted : EntityState.Modified
            : getShouldDeleteFunction(entity) ? EntityState.Detached : EntityState.Added;
    }
}

private static DbContext GetDbContext<T>(this DbSet<T> table) where T : class
{
    var infrastructure = table as IInfrastructure<IServiceProvider>;
    var serviceProvider = infrastructure.Instance;
    var currentDbContext = serviceProvider.GetService(typeof(ICurrentDbContext)) as ICurrentDbContext;
    return currentDbContext.Context;
}

и я использую это так:

await db.Reports.AddOrUpdateAsync(r => new { r.Number, r.Year }, r => r.Active == false, response.Reports.ToArray());

Я думаю, что это происходит, потому что я использую анонимный тип в качестве ключа, но я не знаю, как это исправить.

1 Ответ

3 голосов
/ 18 марта 2019

Проблема, похоже, заключается в использовании константного выражения анонимного типа, которое в настоящее время вызывает оценку клиента , а оператор C # == сравнивает анонимные типы по ссылке, поэтому всегда возвращает false.

Хитрость для получения желаемого перевода сервера состоит в том, чтобы "вызвать" выражение key с entity, заменив параметр на Expression.Constant(entity) (Expression.Invoke в этом случае не работает)

Удалите строку var getKeyFunction = key.Compile();, если она больше не нужна, и используйте следующее:

foreach (var entity in entities)
{
    var parameter = key.Parameters[0];
    var body = Expression.Equal(
        key.Body,
        key.Body.ReplaceParameter(parameter, Expression.Constant(entity))
    );
    var query = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
    var exist = await table.AnyAsync(query);
    // ...
}

, где ReplaceParameter - это обычный вспомогательный метод выражения:

public static partial class ExpressionUtils
{
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
        => new ParameterReplacer { Source = source, Target = target }.Visit(expression);

    class ParameterReplacer : ExpressionVisitor
    {
        public ParameterExpression Source;
        public Expression Target;
        protected override Expression VisitParameter(ParameterExpression node)
            => node == Source ? Target : node;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...