查询多列必须匹配 EF Core 中同时设置的值

Query where multiple columns have to match a value set simultaneously in EF Core

问题

我有一个加载本地数据文件的客户端应用程序。此数据文件为每个项目指定 TypeVersion

从这个文件中,我编译了一个 TypeVersion 对的列表。

var typeVersionSets = datafile.Select(item => new { Type = item.TypeId, Version = item.VersionId }).Distinct();

:不止这两个字段,为了简单起见,我只表示这两个。

我还有一个 SQL 服务器,它 运行 在云端。我需要从满足值对的 table 中获取所有记录(因此列值必须同时匹配 )。

我写了这个不能被 EF Core 运行 的简单查询:

List<MyTableRow> MyResult = await dbContext.MyTable
    .Where(dbItem => typeVersionSets.Contains(new { Type = dbItem.TypeId, Version = dbItem.VersionId }))
    .ToListAsync();

我得到以下运行时间错误:

One or more errors occurred. (The LINQ expression 'DbSet().Where(p => __MyTableRowTypeVersions_2.Contains(new { Type = p.TypeId, Version = p.VersionId }))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.)

TLDR 一些细节

MyTable太大了,每次都下载下来,在客户端计算LINQ表达式。

typeVersionSets的数量相当少(比如说10套)。

当然,我可以循环遍历 typeVersionSets,例如:

List<MyTableRow> MyResult = new List<MyTableRow>();

foreach (var set in typeVersionSets)
{
    MyResult.AddRange(
            await dbContext.MyTable
                .Where(pp => pp.TypeId == set.Type && pp.VersionId == set.Version)
                .ToListAsync()
            );
}
    

但是,这将需要 10 次数据库调用。

此代码将被每个用户和许多用户执行多次。

是否有更有效的解决方案,可以导致每个事件调用 1 个数据库,而不会将大量不必要的数据传输到客户端(或服务器)。

一些补充说明

我使用:

万一重要,我无法迁移到 EF Core 6,因为这需要迁移到 .NET(核心)6.0,这会引发很多超出我范围的问题。

我倾向于构建一个动态 Expression<Func<MyTableRow, bool>> 来表示过滤器。

var p = Expression.Parameter(typeof(MyTableRow), "dbItem");

var parts = new List<Expression>();
foreach (var set in typeVersionSets)
{
    var typeIdValue = Expression.Property(p, nameof(MyTableRow.TypeId));
    var typeIdTarget = Expression.Constant(set.Type);
    var typeIdTest = Expression.Equal(typeIdValue, typeIdTarget);
    
    var versionIdValue = Expression.Property(p, nameof(MyTableRow.VersionId));
    var versionIdTarget = Expression.Constant(set.Version);
    var versionIdTest = Expression.Equal(versionIdValue, versionIdTarget);
    
    var part = Expression.AndAlso(typeIdTest, versionIdTest);
    parts.Add(part);
}

var body = parts.Aggregate(Expression.OrElse);
var filter = Expression.Lambda<Func<MyTableRow, bool>>(body, p);

List<MyTableRow> MyResult = await dbContext.MyTable
    .Where(filter)
    .ToListAsync()

Expression Trees (C#) | Microsoft Docs

你可以使用这个:

dbContext.MyTable
    .FilterByItems(typeVersionSets, (pp, set) => pp.TypeId == set.Type && pp.VersionId == set.Version, true)
    .ToListAsync();