EntityFramework - 包含复合键的查询

Posted

技术标签:

【中文标题】EntityFramework - 包含复合键的查询【英文标题】:EntityFramework - contains query of composite key 【发布时间】:2014-11-29 16:29:54 【问题描述】:

给定一个 id 列表,我可以通过以下方式查询所有相关行:

context.Table.Where(q => listOfIds.Contains(q.Id));

但是当 Table 有一个复合键时,你如何实现相同的功能呢?

【问题讨论】:

我喜欢EntityFramework,我也喜欢复合键,但我不喜欢它们在一起。 EntityFramework绝对在构建时考虑了简单的代理键。 我认为这不是正确的问题,因为这对 SQL 来说是不自然的。以后的语言你会怎么做? 任何登陆这里的人:我推荐this answer。 【参考方案1】:

这是一个令人讨厌的问题,我不知道任何优雅的解决方案。

假设您有这些组合键,并且您只想选择标记的组合键 (*)。

Id1  Id2
---  ---
1    2 *
1    3
1    6
2    2 *
2    3 *
... (many more)

如何做到这一点是Entity Framework高兴的一种方式?让我们看看一些可能的解决方案,看看它们是否有用。

解决方案 1:Join(或 Contains)成对

最好的解决方案是创建一个您想要的对的列表,例如元组 (List<Tuple<int,int>>) 并将数据库数据与此列表连接:

from entity in db.Table // db is a DbContext
join pair in Tuples on new  entity.Id1, entity.Id2 
                equals new  Id1 = pair.Item1, Id2 = pair.Item2 
select entity

在 LINQ to objects 中这将是完美的,但是,太糟糕了,EF 会抛出类似的异常

无法创建类型为 'System.Tuple`2 (...) 的常量值在此上下文中仅支持原始类型或枚举类型。

这是一种相当笨拙的方式来告诉您它无法将此语句转换为 SQL,因为Tuples 不是原始值列表(如intstring)。1。出于同样的原因,使用 Contains(或任何其他 LINQ 语句)的类似语句也会失败。

解决方案 2:内存中

当然,我们可以将问题转化为简单的 LINQ 对象,如下所示:

from entity in db.Table.AsEnumerable() // fetch db.Table into memory first
join pair Tuples on new  entity.Id1, entity.Id2 
             equals new  Id1 = pair.Item1, Id2 = pair.Item2 
select entity

不用说,这不是一个好的解决方案。 db.Table 可能包含数百万条记录。

解决方案 3:两个 Contains 语句

所以让我们为 EF 提供两个原始值列表,[1,2] 对应 Id1[2,3] 对应 Id2。我们不想使用join(见附注),所以让我们使用Contains

from entity in db.Table
where ids1.Contains(entity.Id1) && ids2.Contains(entity.Id2)
select entity

但现在结果还包含实体1,3!好吧,当然,这个实体完全匹配这两个谓词。但请记住,我们越来越近了。我们现在只获得了其中的四个,而不是将数百万个实体放入内存中。

解决方案 4:一个带有计算值的 Contains

解决方案 3 失败,因为两个单独的 Contains 语句不仅过滤了它们的值的组合。如果我们首先创建一个组合列表并尝试匹配这些组合呢?我们从解决方案 1 中知道该列表应该包含原始值。例如:

var computed = ids1.Zip(ids2, (i1,i2) => i1 * i2); // [2,6]

和 LINQ 语句:

from entity in db.Table
where computed.Contains(entity.Id1 * entity.Id2)
select entity

这种方法存在一些问题。首先,您会看到这也返回实体1,6。组合函数 (a*b) 不会生成唯一标识数据库中的一对的值。现在我们可以创建一个字符串列表,如["Id1=1,Id2=2","Id1=2,Id2=3]" 并执行

from entity in db.Table
where computed.Contains("Id1=" + entity.Id1 + "," + "Id2=" + entity.Id2)
select entity

(这适用于 EF6,而不是早期版本)。

这变得非常混乱。但更重要的问题是这个解决方案不是sargable,这意味着:它绕过了Id1Id2 上本来可以使用的任何数据库索引。这将表现得非常非常糟糕。

解决方案 5:2 和 3 的最佳解决方案

所以我能想到的唯一可行的解​​决方案是Containsjoin 在内存中的组合:首先像解决方案3 一样执行 contains 语句。记住,它让我们非常接近我们想要的。然后通过将结果连接为内存列表来优化查询结果:

var rawSelection = from entity in db.Table
                   where ids1.Contains(entity.Id1) && ids2.Contains(entity.Id2)
                   select entity;

var refined = from entity in rawSelection.AsEnumerable()
              join pair in Tuples on new  entity.Id1, entity.Id2 
                              equals new  Id1 = pair.Item1, Id2 = pair.Item2 
              select entity;

它可能并不优雅,也很混乱,但到目前为止,它是我发现并应用于我自己的代码中的唯一可扩展的2解决方案。

解决方案 6:使用 OR 子句构建查询

使用像 Linqkit 或替代品这样的谓词构建器,您可以构建一个查询,其中包含组合列表中每个元素的 OR 子句。对于非常短的列表来说,这可能是一个可行的选择。如果有几百个元素,查询将开始表现得很糟糕。所以我不认为这是一个好的解决方案,除非你可以 100% 确定总会有少量元素。可以在here 找到有关此选项的详细说明。


1有趣的是,当您加入原始列表时,EF 确实会创建一条 SQL 语句,就像这样

from entity in db.Table // db is a DbContext
join i in MyIntegers on entity.Id1 equals i
select entity

但是生成的 SQL 是很荒谬的。 MyIntegers 仅包含 5(!) 个整数的真实示例如下所示:

SELECT 
    [Extent1].[CmpId] AS [CmpId], 
    [Extent1].[Name] AS [Name], 
    FROM  [dbo].[Company] AS [Extent1]
    INNER JOIN  (SELECT 
        [UnionAll3].[C1] AS [C1]
        FROM  (SELECT 
            [UnionAll2].[C1] AS [C1]
            FROM  (SELECT 
                [UnionAll1].[C1] AS [C1]
                FROM  (SELECT 
                    1 AS [C1]
                    FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
                UNION ALL
                    SELECT 
                    2 AS [C1]
                    FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]) AS [UnionAll1]
            UNION ALL
                SELECT 
                3 AS [C1]
                FROM  ( SELECT 1 AS X ) AS [SingleRowTable3]) AS [UnionAll2]
        UNION ALL
            SELECT 
            4 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable4]) AS [UnionAll3]
    UNION ALL
        SELECT 
        5 AS [C1]
        FROM  ( SELECT 1 AS X ) AS [SingleRowTable5]) AS [UnionAll4] ON [Extent1].[CmpId] = [UnionAll4].[C1]

有 n-1 个UNIONs。当然,这根本不是可扩展的。

稍后添加: 在通往 EF 版本 6.1.3 的道路上的某个地方,这已经有了很大的改进。 UNIONs 变得更简单,不再嵌套。以前,查询将放弃本地序列中少于 50 个元素(SQL 异常:您的 SQL 语句的某些部分嵌套太深。)非嵌套 UNION 允许本地序列最多几千(!)个元素。尽管有“许多”元素,但它仍然很慢。

2Contains 语句的可扩展性而言:Scalable Contains method for LINQ against a SQL backend

【讨论】:

这(解决方案 5)是我最终所做的,但这感觉是一种糟糕的方法...... 是的。问题的根源是我们必须处理一个极度过时的语言规范(SQL),它从未提供在一个语句中加入临时多维列表的方法(就像我们可以通过IN 语句处理一个简单的列表一样) .有特定于 RDBMS 的解决方法或修复(Oracle 有一个非常好的解决方法),但 EF 可能不会投资于实施这些。 明确一点,如果您在其上运行的集合超过 2100 个元素 (here),.Contains 将引发异常。 非常感谢这个非常有见地的回答。 Solution 7: Write a stored procedure which does a join on multiple columns.【参考方案2】:

您可以为每个复合主键使用Union

var compositeKeys = new List<CK> 

    new CK  id1 = 1, id2 = 2 ,
    new CK  id1 = 1, id2 = 3 ,
    new CK  id1 = 2, id2 = 4 
;

IQuerable<CK> query = null;
foreach(var ck in compositeKeys)

    var temp = context.Table.Where(x => x.id1 == ck.id1 && x.id2 == ck.id2);
    query = query == null ? temp : query.Union(temp);

var result = query.ToList();

【讨论】:

请问,这个方法的弱点是什么?在我看来,这是一个很好的解决方案,应该是公认的答案。谁能看到这个答案的缺点? @Sam 它将为每个复合键构建单独的 sql 查询,然后合并结果 - 这是该方法的弱点 - 性能 @H.Wojtowicz,每个compositeKeys不会是单独的sql查询,它将是一个查询,由几个子查询组成。 @SlavaUtesinov 我不准确。我的意思是联合发生在 sql server 端。我的意思是查询是不必要的,只要所有必需的条件都可以放在一个 sql 中 - 请参阅this【参考方案3】:

您可以像这样使用两个键创建字符串集合(我假设您的键是 int 类型):

var id1id2Strings = listOfIds.Select(p => p.Id1+ "-" + p.Id2);

然后你可以在你的数据库上使用“包含”:

using (dbEntities context = new dbEntities())
            
                var rec = await context.Table1.Where(entity => id1id2Strings .Contains(entity.Id1+ "-" + entity.Id2));
                return rec.ToList();
            

【讨论】:

正如 Gert Arnord 在他的评论中提到的那样“这个解决方案是不可预测的,这意味着:它绕过了 Id1 和 Id2 上本来可以使用的任何数据库索引。”【参考方案4】:

使用 SQL Server 的 Entity Framework Core 解决方案

以下解决方案使用QueryableValues。这是我编写的一个库,主要解决由使用Contains LINQ 方法组成本地值的查询引起的query plan cache pollution in SQL Server 问题。它还允许您以高效的方式在查询中组合 complex types 的值,这将实现此问题中所要求的内容。

首先您需要install and set up the library,之后您可以使用以下任何一种模式来使用复合键查询您的实体:

// Required to make the AsQueryableValues method available on the DbContext.
using BlazarTech.QueryableValues;

// Local data that will be used to query by the composite key
// of the fictitious OrderProduct table.
var values = new[]

    new  OrderId = 1, ProductId = 10 ,
    new  OrderId = 2, ProductId = 20 ,
    new  OrderId = 3, ProductId = 30 
;

// Optional helper variable (needed by the second example due to CS0854)
var queryableValues = dbContext.AsQueryableValues(values);

// Example 1 - Using a Join (preferred).
var example1Results = dbContext
    .OrderProduct
    .Join(
        queryableValues,
        e => new  e.OrderId, e.ProductId ,
        v => new  v.OrderId, v.ProductId ,
        (e, v) => e
    )
    .ToList();

// Example 2 - Using Any (similar behavior as Contains).
var example2Results = dbContext
    .OrderProduct
    .Where(e => queryableValues
        .Where(v =>
            v.OrderId == e.OrderId &&
            v.ProductId == e.ProductId
        )
        .Any()
    )
    .ToList();

有用的链接

Nuget Package GitHub Repository Benchmarks

QueryableValues 在 MIT 许可下分发。

【讨论】:

我认为这是解决这个问题的最佳方案。 @sternr,如果你还在,请将其标记为已接受,以便更容易找到。【参考方案5】:

也遇到了这个问题,需要一个既不执行表扫描又提供完全匹配的解决方案。

这可以通过结合Gert Arnold's Answer中的解决方案3和解决方案4来实现

var firstIds = results.Select(r => r.FirstId);
var secondIds = results.Select(r => r.SecondId);
var compositeIds = results.Select(r => $"r.FirstId:r.SecondId");
var query = from e in dbContext.Table
 
            //first check the indexes to avoid a table scan
            where firstIds.Contains(e.FirstId) && secondIds.Contains(e.SecondId))
 
            //then compare the compositeId for an exact match
            //ToString() must be called unless using EF Core 5+
            where compositeIds.Contains(e.FirstId.ToString() + ":" + e.SecondId.ToString()))
            select e;
var entities = await query.ToListAsync();

【讨论】:

【参考方案6】:

对于 EF Core,我使用 bucketized IN method by EricEJ 的略微修改版本将复合键映射为元组。对于小数据集,它的表现相当不错。

使用示例

List<(int Id, int Id2)> listOfIds = ...
context.Table.In(listOfIds, q => q.Id, q => q.Id2);

实施

public static IQueryable<TQuery> In<TKey1, TKey2, TQuery>(
            this IQueryable<TQuery> queryable,
            IEnumerable<(TKey1, TKey2)> values,
            Expression<Func<TQuery, TKey1>> key1Selector,
            Expression<Func<TQuery, TKey2>> key2Selector)
        
            if (values is null)
            
                throw new ArgumentNullException(nameof(values));
            

            if (key1Selector is null)
            
                throw new ArgumentNullException(nameof(key1Selector));
            

            if (key2Selector is null)
            
                throw new ArgumentNullException(nameof(key2Selector));
            

            if (!values.Any())
            
                return queryable.Take(0);
            

            var distinctValues = Bucketize(values);

            if (distinctValues.Length > 1024)
            
                throw new ArgumentException("Too many parameters for SQL Server, reduce the number of parameters", nameof(values));
            

            var predicates = distinctValues
                .Select(v =>
                
                    // Create an expression that captures the variable so EF can turn this into a parameterized SQL query
                    Expression<Func<TKey1>> value1AsExpression = () => v.Item1;
                    Expression<Func<TKey2>> value2AsExpression = () => v.Item2;
                    var firstEqual = Expression.Equal(key1Selector.Body, value1AsExpression.Body);

                    var visitor = new ReplaceParameterVisitor(key2Selector.Parameters[0], key1Selector.Parameters[0]);

                    var secondEqual = Expression.Equal(visitor.Visit(key2Selector.Body), value2AsExpression.Body);

                    return Expression.AndAlso(firstEqual, secondEqual);
                )
                .ToList();

            while (predicates.Count > 1)
            
                predicates = PairWise(predicates).Select(p => Expression.OrElse(p.Item1, p.Item2)).ToList();
            

            var body = predicates.Single();

            var clause = Expression.Lambda<Func<TQuery, bool>>(body, key1Selector.Parameters[0]);

            return queryable.Where(clause);
        

        class ReplaceParameterVisitor : ExpressionVisitor
        
            private ParameterExpression _oldParameter;
            private ParameterExpression _newParameter;

            public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
            
                _oldParameter = oldParameter;
                _newParameter = newParameter;
            

            protected override Expression VisitParameter(ParameterExpression node)
            
                if (ReferenceEquals(node, _oldParameter))
                    return _newParameter;

                return base.VisitParameter(node);
            
        

        /// <summary>
        /// Break a list of items tuples of pairs.
        /// </summary>
        private static IEnumerable<(T, T)> PairWise<T>(this IEnumerable<T> source)
        
            var sourceEnumerator = source.GetEnumerator();
            while (sourceEnumerator.MoveNext())
            
                var a = sourceEnumerator.Current;
                sourceEnumerator.MoveNext();
                var b = sourceEnumerator.Current;

                yield return (a, b);
            
        

        private static TKey[] Bucketize<TKey>(IEnumerable<TKey> values)
        
            var distinctValueList = values.Distinct().ToList();

            // Calculate bucket size as 1,2,4,8,16,32,64,...
            var bucket = 1;
            while (distinctValueList.Count > bucket)
            
                bucket *= 2;
            

            // Fill all slots.
            var lastValue = distinctValueList.Last();
            for (var index = distinctValueList.Count; index < bucket; index++)
            
                distinctValueList.Add(lastValue);
            

            var distinctValues = distinctValueList.ToArray();
            return distinctValues;
        

【讨论】:

【参考方案7】:

您需要一组代表您要查询的键的对象。

class Key

    int Id1 get;set;
    int Id2 get;set;

如果您有两个列表,并且您只需检查每个值是否出现在它们各自的列表中,那么您将获得列表的笛卡尔积 - 这可能不是您想要的。相反,您需要查询所需的特定组合

List<Key> keys = // get keys;

context.Table.Where(q => keys.Any(k => k.Id1 == q.Id1 && k.Id2 == q.Id2)); 

我不完全确定这是对实体框架的有效使用;您可能在将Key 类型发送到数据库时遇到问题。如果发生这种情况,那么您可以发挥创造力:

var composites = keys.Select(k => p1 * k.Id1 + p2 * k.Id2).ToList();
context.Table.Where(q => composites.Contains(p1 * q.Id1 + p2 * q.Id2)); 

您可以创建一个同构函数(素数对此很有用),类似于哈希码,您可以使用它来比较这对值。只要乘法因子是互质的,这个模式就会是同构的(一对一)——即p1*Id1 + p2*Id2 的结果将唯一标识Id1Id2 的值,只要质数是正确选择。

但是,您最终会遇到一种情况,即您正在实施复杂的概念,并且必须有人支持这一点。可能最好编写一个采用有效键对象的存储过程。

【讨论】:

我必须检查您的第一个解决方案,但至于第二个 - 虽然它可以工作,但它会导致全表扫描,而不是对键使用直接查询 @sternr 完全正确。这是非常不愉快的。请注意我的最后一句话,您最好编写一个存储过程。【参考方案8】:

在没有通用解决方案的情况下,我认为有两点需要考虑:

    避免使用多列主键(也会使单元测试更容易)。 但如果必须这样做,其中一个可能会降低 查询结果大小为 O(n),其中 n 是理想查询的大小 结果。从这里开始,上面 Gerd Arnold 的解决方案 5。

例如,导致我这个问题的问题是查询订单行,其中关键是订单 ID + 订单行号 + 订单类型,并且来源具有隐含的订单类型。即订单类型为常数,订单ID将查询集减少为相关订单的订单行,每个订单通常有5个或更少。

换种说法:如果您有一个复合键,则变化是其中一个键几乎没有重复项。应用上面的解决方案 5。

【讨论】:

【参考方案9】:

我尝试了这个解决方案,它对我很有效,输出查询很完美,没有任何参数

using LinqKit; // nuget     
   var customField_Ids = customFields?.Select(t => new CustomFieldKey  Id = t.Id, TicketId = t.TicketId ).ToList();

    var uniqueIds1 = customField_Ids.Select(cf => cf.Id).Distinct().ToList();
    var uniqueIds2 = customField_Ids.Select(cf => cf.TicketId).Distinct().ToList();
    var predicate = PredicateBuilder.New<CustomFieldKey>(false); //LinqKit
    var lambdas = new List<Expression<Func<CustomFieldKey, bool>>>();
    foreach (var cfKey in customField_Ids)
    
        var id = uniqueIds1.Where(uid => uid == cfKey.Id).Take(1).ToList();
        var ticketId = uniqueIds2.Where(uid => uid == cfKey.TicketId).Take(1).ToList();
        lambdas.Add(t => id.Contains(t.Id) && ticketId.Contains(t.TicketId));
    

    predicate = AggregateExtensions.AggregateBalanced(lambdas.ToArray(), (expr1, expr2) =>
     
         var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
         return Expression.Lambda<Func<CustomFieldKey, bool>>
               (Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
     );


    var modifiedCustomField_Ids = repository.GetTable<CustomFieldLocal>()
         .Select(cf => new CustomFieldKey()  Id = cf.Id, TicketId = cf.TicketId ).Where(predicate).ToArray();

【讨论】:

【参考方案10】:

我最终为这个问题编写了一个依赖于System.Linq.Dynamic.Core 的助手;

它有很多代码,目前没有时间重构,但感谢您的输入/建议。

        public static IQueryable<TEntity> WhereIsOneOf<TEntity, TSource>(this IQueryable<TEntity> dbSet, 
            IEnumerable<TSource> source, 
            Expression<Func<TEntity, TSource,bool>> predicate) where TEntity : class
        
            var (where, pDict) = GetEntityPredicate(predicate, source);
            return dbSet.Where(where, pDict);

            (string WhereStr, IDictionary<string, object> paramDict) GetEntityPredicate(Expression<Func<TEntity, TSource, bool>> func, IEnumerable<TSource> source)
            
                var firstP = func.Parameters[0];
                var binaryExpressions = RecurseBinaryExpressions((BinaryExpression)func.Body);

                var i = 0;
                var paramDict = new Dictionary<string, object>();
                var res = new List<string>();
                foreach (var sourceItem in source)
                
                    var innerRes = new List<string>();
                    foreach (var bExp in binaryExpressions)
                    
                        var emp = ToEMemberPredicate(firstP, bExp);
                        var val = emp.GetKeyValue(sourceItem);
                        var pName = $"@i++";
                        paramDict.Add(pName, val);
                        var str = $"emp.EntityMemberName emp.SQLOperator pName";
                        innerRes.Add(str);
                    

                    res.Add( "(" + string.Join(" and ", innerRes) + ")");
                

                var sRes = string.Join(" || ", res);

                return (sRes, paramDict);
            
            
            EMemberPredicate ToEMemberPredicate(ParameterExpression firstP, BinaryExpression bExp)
            
                var lMember = (MemberExpression)bExp.Left;
                var rMember = (MemberExpression)bExp.Right;

                var entityMember = lMember.Expression == firstP ? lMember : rMember;
                var keyMember = entityMember == lMember ? rMember : lMember;

                return new EMemberPredicate(entityMember, keyMember, bExp.NodeType);
            
            List<BinaryExpression> RecurseBinaryExpressions(BinaryExpression e, List<BinaryExpression> runningList = null)
            
                if (runningList == null) runningList = new List<BinaryExpression>();

                if (e.Left is BinaryExpression lbe)
                
                    var additions = RecurseBinaryExpressions(lbe);
                    runningList.AddRange(additions);
                
                
                if (e.Right is BinaryExpression rbe)
                
                    var additions = RecurseBinaryExpressions(rbe);
                    runningList.AddRange(additions);
                

                if (e.Left is MemberExpression && e.Right is MemberExpression)
                
                    runningList.Add(e);
                

                return runningList;
            
        

助手类:

    public class EMemberPredicate
    
        public readonly MemberExpression EntityMember;
        public readonly MemberExpression KeyMember;
        public readonly PropertyInfo KeyMemberPropInfo;
        public readonly string EntityMemberName;
        public readonly string SQLOperator;

        public EMemberPredicate(MemberExpression entityMember, MemberExpression keyMember, ExpressionType eType)
        
            EntityMember = entityMember;
            KeyMember = keyMember;
            KeyMemberPropInfo = (PropertyInfo)keyMember.Member;
            EntityMemberName = entityMember.Member.Name;
            SQLOperator = BinaryExpressionToMSSQLOperator(eType);
        

        public object GetKeyValue(object o)
        
            return KeyMemberPropInfo.GetValue(o, null);
        

        private string BinaryExpressionToMSSQLOperator(ExpressionType eType)
        
            switch (eType)
            
                case ExpressionType.Equal:
                    return "==";
                case ExpressionType.GreaterThan:
                    return ">";
                case ExpressionType.GreaterThanOrEqual:
                    return ">=";
                case ExpressionType.LessThan:
                    return "<";
                case ExpressionType.LessThanOrEqual:
                    return "<=";
                case ExpressionType.NotEqual:
                    return "<>";
                default:
                    throw new ArgumentException($"eType is not a handled Expression Type.");
            
        
    

这样使用:

// This can be a Tuple or whatever..  If Tuple, then y below would be .Item1, etc.
// This data structure is up to you but is what I use.
[FromBody] List<CustomerAddressPk> cKeys
            var res = await dbCtx.CustomerAddress
                .WhereIsOneOf(cKeys, (x, y) => y.CustomerId == x.CustomerId 
                   && x.AddressId == y.AddressId)
                .ToListAsync();

希望这对其他人有所帮助。

【讨论】:

【参考方案11】:

如果是复合键,您可以使用另一个 idlist 并在代码中为其添加条件

context.Table.Where(q => listOfIds.Contains(q.Id) && listOfIds2.Contains(q.Id2));

或者您可以使用另一种技巧通过添加它们来创建密钥列表

listofid.add(id+id1+......)
context.Table.Where(q => listOfIds.Contains(q.Id+q.id1+.......));

【讨论】:

第一个查询错误(因为它假定两个值都是唯一的)第二个查询错误,但会导致全表扫描 是的,我知道第一个是错误的,但第二个我会工作,给我你想要的数据和结果示例...... 对不起,我的意思是第二个没有错,但它会导致无法接受的全表扫描...【参考方案12】:

我在 EF Core 5.0.3 上使用 Postgres 提供程序进行了尝试。

context.Table
    .Select(entity => new
    
        Entity = entity,
        CompositeKey = entity.Id1 + entity.Id2,
    )
    .Where(x => compositeKeys.Contains(x.CompositeKey))
    .Select(x => x.Entity);

这产生了类似的 SQL:

SELECT *
FROM table AS t
WHERE t.Id1 + t.Id2 IN (@__compositeKeys_0)), 

注意事项

仅应在 Id1Id2 的组合始终产生唯一结果的情况下使用(例如,它们都是 UUID) 这不能使用索引,尽管您可以使用索引将复合键保存到数据库中

【讨论】:

这是个坏主意。对于数字和文本列,它都会产生模棱两可的结果(ab+ca+bc 产生相同的值)并且扫描整个表而不使用任何索引 好点@PanagiotisKanavos。我在其中一个 ID 是 UUID 的情况下使用它,因此几乎可以保证结果是唯一的。我已经更新了我的答案以包括这个和索引问题警告。

以上是关于EntityFramework - 包含复合键的查询的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework 4.1 Code First,一对一,一个表连接到复合键的单个键字段

实体框架:如何从具有复合键的表中返回一行?

使用实体框架将行插入到具有复合键的表中

通过 Razor 页面传递多个参数值

使用复合/复合主键的缺点是啥?

Spring OneToMany 与复合键的关系与另一个具有复合键的关系