ste*_*rnr 26 .net c# entity-framework
给定一个id列表,我可以通过以下方式查询所有相关行:
context.Table.Where(q => listOfIds.Contains(q.Id));
Run Code Online (Sandbox Code Playgroud)
但是当Table具有复合键时,如何实现相同的功能?
Ger*_*old 39
这是一个令人讨厌的问题,我不知道任何优雅的解决方案.
假设您有这些组合键,并且您只想选择标记的组合(*).
Id1 Id2
--- ---
1 2 *
1 3
1 6
2 2 *
2 3 *
... (many more)
Run Code Online (Sandbox Code Playgroud)
如何做到这一点是实体框架快乐的一种方式?让我们看看一些可能的解决方案,看看它们是否有用.
Join
或Contains
)配对最好的解决方案是创建所需对的列表,例如Tuples,(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
Run Code Online (Sandbox Code Playgroud)
在LINQ to objects中,这将是完美的,但是,太糟糕了,EF会抛出一个例外
无法创建类型'System.Tuple`2(...)的常量值在此上下文中仅支持基元类型或枚举类型.
这是一种相当笨拙的方式告诉您它无法将此语句转换为SQL,因为Tuples
它不是原始值列表(如int
或string
).1.出于同样的原因,使用Contains
(或任何其他LINQ语句)的类似语句将失败.
当然,我们可以将问题转化为简单的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
Run Code Online (Sandbox Code Playgroud)
不用说,这不是一个好的解决方案.db.Table
可能包含数百万条记录.
Contains
陈述因此,让我们提供EF两个原始值列表,[1,2]
for Id1
和[2,3]
for Id2
.我们不想使用join(参见旁注),所以让我们使用Contains
:
from entity in db.Table
where ids1.Contains(entity.Id1) && ids2.Contains(entity.Id2)
select entity
Run Code Online (Sandbox Code Playgroud)
但现在结果还包含实体{1,3}
!当然,这个实体完全匹配两个谓词.但是请记住,我们越来越近了.我们现在只获得其中的四个实体,而不是将数百万个实体吸引到内存中.
Contains
具有计算值解决方案3失败,因为两个单独的Contains
语句不仅过滤其值的组合.如果我们首先创建组合列表并尝试匹配这些组合,该怎么办?我们从解决方案1中知道该列表应包含原始值.例如:
var computed = ids1.Zip(ids2, (i1,i2) => i1 * i2); // [2,6]
Run Code Online (Sandbox Code Playgroud)
和LINQ声明:
from entity in db.Table
where computed.Contains(entity.Id1 * entity.Id2)
select entity
Run Code Online (Sandbox Code Playgroud)
这种方法存在一些问题.首先,你会看到这也会返回实体{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
Run Code Online (Sandbox Code Playgroud)
(这适用于EF6,而不是早期版本).
这变得相当混乱.但是一个更重要的问题是这个解决方案不是可以解决的,这意味着:它绕过任何数据库索引Id1
并且Id2
可能已经使用过.这将表现得非常糟糕.
因此,我能想到的唯一可行的解决方案是内存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;
Run Code Online (Sandbox Code Playgroud)
这可能不是优雅,凌乱,但到目前为止,它是我发现的这个问题的唯一可扩展的2解决方案,并应用于我自己的代码中.
使用类似Linqkit的Predicate构建器或替代方法,您可以构建一个查询,该查询包含组合列表中每个元素的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
Run Code Online (Sandbox Code Playgroud)
但是生成的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]
Run Code Online (Sandbox Code Playgroud)
有n-1 UNION
s.当然,根本不可扩展.
后来补充:
在EF版本6.1.3的某个地方,这已经有了很大的改进.该UNION
■找变得简单,他们不再嵌套.以前,查询将放弃本地序列中少于50个元素(SQL异常:SQL语句的某些部分嵌套得太深.)非嵌套UNION
允许本地序列最多为数千(!)个元素.虽然有很多元素,但它仍然很慢.
2只要Contains
语句是可伸缩的:Scalable包含针对SQL后端的LINQ方法
yv9*_*89c 10
以下解决方案利用QueryableValues。这是我编写的一个库,主要是为了解决 SQL Server 中由于使用 LINQ 方法组成本地值的查询而导致的查询计划缓存污染问题Contains
。它还允许您以高性能的方式在查询中组合复杂类型的值,这将实现这个问题中所要求的。
首先,您需要安装并设置库,之后您可以使用以下任何模式,允许您使用组合键查询实体:
// 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();
Run Code Online (Sandbox Code Playgroud)
QueryableValues在 MIT 许可证下分发。
您可以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();
Run Code Online (Sandbox Code Playgroud)
小智 -1
如果是复合键,您可以使用另一个 idlist 并在代码中添加一个条件
context.Table.Where(q => listOfIds.Contains(q.Id) && listOfIds2.Contains(q.Id2));
Run Code Online (Sandbox Code Playgroud)
或者您可以使用另一种技巧,通过添加密钥来创建密钥列表
listofid.add(id+id1+......)
context.Table.Where(q => listOfIds.Contains(q.Id+q.id1+.......));
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
14389 次 |
最近记录: |