linq - 构建动态表达式来比较两个表

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

我有两个表,表 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 部分的困境,因为不同的表将有不同的主键名称。

非常感谢任何帮助。

最佳答案

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

它又可以被编译为委托(delegate)并在 LINQ to Objects 中使用(因为您使用的是 IEnumerable )查询,例如

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

关于linq - 构建动态表达式来比较两个表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68676228/

相关文章:

c# - 如何使用 Fluent API 正确控制 EF Core 中的级联删除?

c# - 如何在 EF Core C# 中使用 Bogus 为一对一和多对多关系生成测试数据?

c# - Entity Framework LINQ to Entities 加入查询超时

linq - 表达式类型.引用

rest - HTTP 动词 PUT 和 DELETE : 405 Method not allowed - how to allow?

c# - 如何在asp.net core中修改HttpContext.Request.Form

VB.NET 在使用 Group By LINQ 语句时返回 IEnumerable(Of IEnumerable(Of T))

c# - 如何放置两个 DataValueField?

c# - .NET Core 中的 HttpContext 和缓存 >= 1.0

c# - 实体类型 'Microsoft.AspNetCore.Identity.IdentityRole' 处于影子状态