我有一个包含ID的列表:
var myList = new List<int>();
我想从db中选择来自myList的所有对象:
var objList= myContext.MyObjects.Where(t => myList.Contains(t.Id)).ToList();
但是当myList.Count > 8000
我收到错误时:
查询处理器耗尽了内部资源而无法使用 制定查询计划。这是一个罕见的事件,只有预期 非常复杂的查询或引用非常大的查询 表或分区的数量。请简化查询。如果你 相信您错误地收到了此消息,请联系客户 支持服务以获取更多信息。
我认为这是因为我使用了Contains()
。我可以使用什么而不是包含?
答案 0 :(得分:14)
您可以在客户端执行查询,方法是添加AsEnumerable()
以隐藏实体框架中的Where
子句:
var objList = myContext
.MyObjects
.AsEnumerable()
.Where(t => myList.Contains(t.Id))
.ToList();
要提高效果,您可以使用HashSet
:
var myHashSet = new HashSet<int>(myList);
然后相应地修改Where
中的谓词:
.Where(t => myHashSet.Contains(t.Id))
这是实施时间方面的“简单”解决方案。但是,由于查询正在运行客户端,因此可能会导致性能不佳,因为所有MyObjects
行都会在过滤之前被提取到客户端。
您收到错误的原因是因为实体框架会将您的查询转换为以下内容:
SELECT ...
FROM ...
WHERE column IN (ID1, ID2, ... , ID8000)
所以基本上,列表中的所有8000 ID都包含在生成的SQL中,超出了SQL Server可以处理的限制。
生成此SQL的实体框架“查找”是ICollection<T>
由List<T>
和HashSet<T>
实现的,因此如果您尝试在服务器端保留查询,则会得到使用HashSet<T>
没有改善性能。但是,在客户端,Contains
O(1)
HashSet<T>
O(N)
和List<T>
{{1}}的情况不同。
答案 1 :(得分:8)
如果您想要表现良好,我建议您使用表值参数和存储过程。
在数据库中,使用TSQL,
CREATE TYPE [dbo].[IdSet] AS TABLE
(
[Id] INT
);
GO
CREATE PROCEDURE [dbo].[Get<table>]
@ids [dbo].[IdSet] READONLY
AS
SET NOCOUNT ON;
SELECT
<Column List>
FROM
[dbo].[<table>] [T]
WHERE
[T].[Id] IN (SELECT [Id] FROM @ids);
RETURN 0;
GO
然后,在C#中
var ids = new DataTable()
ids.Columns.Add("Id", typeof(int));
foreach (var id in myList)
{
ids.Rows.Add(id);
}
var objList = myContext.SqlQuery<<entity>>(
"[dbo].[Get<table>] @ids",
new SqlParameter("@ids", SqDbType.Structured)
{
Value = ids,
TypeName = "[dbo].[IdSet]"
}));
答案 2 :(得分:5)
您可以创建一个代表myList
的临时数据库表,并使用该临时列表将您的查询重构为JOIN
。
错误的原因是生成的实际查询包含myList
的所有元素。
基本上,DB(查询处理器)需要查看两个列表才能进行过滤。如果第二个列表太大而不适合查询,则必须另外提供(例如作为临时表)
答案 3 :(得分:5)
您可以将列表拆分为多个子列表,然后运行单独的查询:
int start = 0;
int count = 0;
const int chunk_size = 1000;
do {
count = Math.Min(chunk_size, myList.Count - start);
var tmpList = myList.GetRange(start, count);
// run query with tmpList
var objList= myContext.MyObjects.Where(t => tmpList.Contains(t.Id)).ToList();
// do something with results...
start += count;
} while (start < myList.Count);
当然,你需要以适合自己的某种方式找出好的“块大小”。根据表和列表的大小,在代码中加载整个表和过滤器可能更方便,如其他答案所示。
答案 4 :(得分:0)
为什么不试试
var objList= from obj in myContext.MyObjects
join myId in myList on obj.Id equals myId
select obj;
答案 5 :(得分:0)
如果您的ID列表来自db,则不要使用List
IQueryable< int > myList = myContext.Obj1.Where(...).Select(x => x.Id);
var objList = myContext.MyObjects.Where(t => myList.Contains(t.Id)).ToList();
如果不是这样,那么我建议创建一个扩展方法来分块处理。您可以定义自己的块大小。
类似这样的东西:
public static class QueryableExtensions
{
public static List<T1> WhereContains<T1, T2>(this IQueryable<T1> set, List<T2> values, string property)
{
int chunkSize = 5000;
int currentChunk = 1;
List<T1> results = new List<T1>();
int valuesLeft = values.Count;
while (valuesLeft > 0)
{
List<T2> currentValues = values.Skip((currentChunk - 1) * chunkSize).Take(chunkSize).ToList();
results.AddRange(set.Where($"@0.Contains(outerIt.{property})", new object[] { currentValues }).ToList());
valuesLeft -= chunkSize;
currentChunk++;
}
return results;
}
}
希望有帮助!