C# EntityFramework-包含复合键的查询

C# EntityFramework-包含复合键的查询,c#,.net,entity-framework,C#,.net,Entity Framework,给定ID列表,我可以通过以下方式查询所有相关行: context.Table.Where(q => listOfIds.Contains(q.Id)); 但是,当表具有复合键时,如何实现相同的功能?对于复合键,您可以使用另一个idlist并在代码中为此添加一个条件 context.Table.Where(q => listOfIds.Contains(q.Id) && listOfIds2.Contains(q.Id2)); 或者,您可以使用另一个技巧,通过添加密

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

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

但是,当表具有复合键时,如何实现相同的功能?

对于复合键,您可以使用另一个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+.......));

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

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)); 
您可以创建一个同构函数(素数很好),类似于哈希代码,您可以使用它来比较这对值。只要乘法因子是共素数,该模式将是同构的(一对一)-即只要正确选择素数,
p1*Id1+p2*Id2
的结果将唯一标识
Id1
Id2
的值


但是你最终会遇到这样一种情况:你正在实现复杂的概念,而必须有人来支持这一点。可能最好编写一个存储过程,它接受有效的键对象。

这是一个棘手的问题,我不知道有什么好的解决方案

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

如何做到这一点才是实体框架满意的方式?让我们看看一些可能的解决方案,看看它们是否有用

解决方案1:
Join
(或
包含
)成对 最好的解决方案是创建所需对的列表,例如元组(
list
),并将数据库数据与此列表连接:

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
在linqto对象中,这将是完美的,但是,太糟糕了,EF将抛出一个异常,如

无法创建'System'类型的常量值。此上下文中仅支持元组'2(…)的基元类型或枚举类型

这是一种相当笨拙的方式,告诉您它无法将此语句转换为SQL,因为
元组
不是一个基本值列表(如
int
string
)。出于同样的原因,使用
包含
的类似语句(或任何其他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
不用说,这不是一个好的解决方案<代码>数据库。表
可能包含数百万条记录

解决方案3:两个
包含
语句
因此,让我们为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:一个
包含有计算值的

解决方案3失败,因为两个独立的
包含的
语句不仅过滤它们的值的组合。如果我们先创建一个组合列表并尝试匹配这些组合会怎么样?我们从解决方案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中工作,而不是在早期版本中)

这越来越乱了。但更重要的问题是,此解决方案并非如此,这意味着:它绕过了
Id1
Id2
上本来可以使用的任何数据库索引。这将表现得非常糟糕

解决方案5:2和3中的最佳方案 因此,我能想到的唯一可行的解决方案是在内存中结合使用
Contains
join
:首先执行解决方案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或alternatives之类的谓词生成器,可以为组合列表中的每个元素构建一个包含or子句的查询。对于真正的短名单,这可能是一个可行的选择。如果有几百个元素,查询将开始执行得非常糟糕。所以我不认为这是一个好的解决方案,除非你能确信100%的元素总是有少量的。这一选择的一个细节可以找到


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个(!)整数,如下所示:

选择
[Extent1].[CmpId]作为[CmpId],
[Extent1].[Name]作为[Name],
来自[dbo].[Company]作为[Extent1]
内部联接(选择
[UnionAll3][C1]作为[C1]
从(选择
[UnionAll2][C1]作为[C1]
从(选择
[UnionAll1][C1]AS[C1]
从(选择
1 AS[C1]
FROM(选择1作为X)作为[SingleRowTable1]
联合所有
挑选
2 AS[C1]
从(选择1
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;
from entity in db.Table // db is a DbContext
join i in MyIntegers on entity.Id1 equals i
select entity
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();
            }
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();
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();