我可以用什么构造代替Contains?

Kli*_*Max 16 c# linq sql-server-2008-r2

我有一个包含id的列表:

var myList = new List<int>();
Run Code Online (Sandbox Code Playgroud)

我想从db中选择来自myList的id的所有对象:

var objList= myContext.MyObjects.Where(t => myList.Contains(t.Id)).ToList();
Run Code Online (Sandbox Code Playgroud)

但是当myList.Count > 8000我收到错误时:

查询处理器耗尽了内部资源,无法生成查询计划.这是一种罕见的事件,仅适用于引用大量表或分区的极其复杂的查询或查询.请简化查询.如果您认为错误地收到了此消息,请与客户支持服务联系以获取更多信息.

我认为这是因为我使用过Contains().我可以使用什么而不是包含?

Mar*_*age 14

您可以通过添加AsEnumerable()"隐藏" Where实体框架中的子句来在客户端执行查询:

var objList = myContext
  .MyObjects
  .AsEnumerable()
  .Where(t => myList.Contains(t.Id))
  .ToList();
Run Code Online (Sandbox Code Playgroud)

要提高性能,您可以使用以下命令替换列表HashSet:

var myHashSet = new HashSet<int>(myList);
Run Code Online (Sandbox Code Playgroud)

然后相应地修改谓词Where:

  .Where(t => myHashSet.Contains(t.Id))
Run Code Online (Sandbox Code Playgroud)

就实施时间而言,这是"简单"的解决方案.但是,由于查询正在运行客户端,因此可能会导致性能不佳,因为MyObjects在筛选之前会将所有行都提取到客户端.

您收到错误的原因是因为Entity Framework将您的查询转换为以下内容:

SELECT ...
FROM ...
WHERE column IN (ID1, ID2, ... , ID8000)
Run Code Online (Sandbox Code Playgroud)

因此,基本上,列表中的所有8000 ID都包含在生成的SQL中,超出了SQL Server可以处理的限制.

生成此SQL的实体框架"查找" ICollection<T>是由两者实现的List<T>,HashSet<T>因此如果您尝试在服务器端保留查询,则无法通过使用来提高性能HashSet<T>.然而,在客户端的故事,不同的是这里ContainsO(1)HashSet<T>O(N)List<T>.

  • 可以说该表包含800万行,并非不合理.假设所需的"id"之一是数据库返回的最后一个.返回的99.99%的数据将被丢弃.我还断言,通过网络传输选定的,然后丢弃的数据所花费的时间将大大超过任何其他性能节省.即使您只选择ID,也就是31MB的浪费转移以获得32Kb的数据. (8认同)

Jod*_*ell 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
Run Code Online (Sandbox Code Playgroud)

然后,在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]"
        }));
Run Code Online (Sandbox Code Playgroud)


DrK*_*och 5

您可以创建一个临时数据库表,该表使用该临时List 表示myList和重构您的查询JOIN.

出错的原因是生成的实际查询包含的所有元素myList.

基本上,DB(查询处理器)需要查看两个列表才能进行过滤.如果第二个列表太大而不适合查询,则必须另外提供(例如作为临时表)

  • @TravisJ向表中添加8000个小行通常需要多长时间?不是特别的. (3认同)
  • 是的,重点是:OP必须为**每个**查询创建此临时表 (2认同)

Pao*_*sco 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);
Run Code Online (Sandbox Code Playgroud)

当然,你需要以适合自己的某种方式找出好的"块大小".根据表和列表的大小,在代码中加载整个表和过滤器可能更方便,如其他答案中所建议的那样.