【问题标题】:Building dynamic expression to compare between two table构建动态表达式以在两个表之间进行比较
【发布时间】:2021-08-06 04:42:10
【问题描述】:

我有两个表,表 A 和表 B,它们具有相同的方案。

我想比较来自不同上下文的两个表。如果表 B 中的记录在表 A 中不存在,则将其插入到 A 上下文中。

我必须弄清楚单一类型的表看起来很像。

var a = context.Set<T>().AsEnumerable();
var b = context2.Set<T>().AsEnumerable();

var NotExistsOnA = b.Where(bRecord => 
                          !a.Any(aRecord =>
                                 bRecord.xxxId.Equals(aRecord.xxxId))).ToList();

如何创建动态表达式以使代码可重用,因为将使用几个表进行比较。我被困在如何处理 xxxId 部分,因为不同的表会有不同的主键名称。

非常感谢任何帮助。

【问题讨论】:

    标签: linq asp.net-core entity-framework-core expression-trees dynamic-expression


    【解决方案1】:

    EF Core 元数据服务可用于获取有关实体主键的信息,例如

    var entityType = context.Model.FindEntityType(typeof(T));
    var primaryKey = entityType.FindPrimaryKey();
    

    那么这个信息可以用来动态构建相等比较器:

    public static Expression<Func<T, T, bool>> GetPrimaryKeyCompareExpression<T>(this DbContext context)
    {
        var entityType = context.Model.FindEntityType(typeof(T));
        var primaryKey = entityType.FindPrimaryKey();
        var first = Expression.Parameter(typeof(T), "first");
        var second = Expression.Parameter(typeof(T), "second");
        var body = primaryKey.Properties
            .Select(p => Expression.Equal(
                Expression.Property(first, p.PropertyInfo),
                Expression.Property(second, p.PropertyInfo)))
            .Aggregate(Expression.AndAlso); // handles composite PKs
        return Expression.Lambda<Func<T, T, bool>>(body, first, second);
    }
    
    

    这反过来可以被编译为委托并在 LINQ to Objects 中使用(因为您使用的是IEnumerables)查询,例如

    var setA = context.Set<T>().AsEnumerable();
    var setB = context2.Set<T>().AsEnumerable();
    
    var comparePKs = context.GetPrimaryKeyCompareExpression<T>().Compile();
    var notExistsOnA = setB
        .Where(b => !setA.Any(a => comparePKs(a, b)))
        .ToList();
    

    但请注意,在 LINQ to Objects 中,!Any(...) 内部查询条件效率低下,因为它是线性搜索操作,因此生成的时间复杂度是二次的 (O(Na * Nb),因此您会遇到更大数据的性能问题套。

    所以一般来说使用连接运算符会更好。但是它不需要比较,它需要键选择器,它也可以类似于上面的构建,但这次发出Tuple 实例(为了处理复合 PK),例如

    public static Expression<Func<T, object>> GetPrimaryKeySelector<T>(this DbContext context)
    {
        var entityType = context.Model.FindEntityType(typeof(T));
        var primaryKey = entityType.FindPrimaryKey();
        var item = Expression.Parameter(typeof(T), "item");
        var body = Expression.Call(
            typeof(Tuple), nameof(Tuple.Create),
            primaryKey.Properties.Select(p => p.ClrType).ToArray(),
            primaryKey.Properties.Select(p => Expression.Property(item, p.PropertyInfo)).ToArray()
        );
        return Expression.Lambda<Func<T, object>>(body, item);
    }
    
    

    可以如下使用

    var selectPK = context.GetPrimaryKeySelector<T>().Compile();
    var notExistsOnA = setB
        .GroupJoin(setA, selectPK, selectPK, (b, As) => (b, As))
        .Where(r => !r.As.Any())
        .Select(r => r.b)
        .ToList();
    

    【讨论】:

    • 非常感谢您的回答。我按照你最后的建议得到了我想要的结果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-20
    • 1970-01-01
    • 1970-01-01
    • 2012-11-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多