C# EntityFramework-包含复合键的查询
给定ID列表,我可以通过以下方式查询所有相关行: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)); 或者,您可以使用另一个技巧,通过添加密
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();