【问题标题】:Join large list of Integers into LINQ Query将大量整数加入 LINQ 查询
【发布时间】:2013-01-18 09:32:28
【问题描述】:

我有 LINQ 查询返回以下错误: “传入的表格数据流 (TDS) 远程过程调用 (RPC) 协议流不正确。此 RPC 请求中提供的参数过多。最大值为 2100”。

我所需要的只是计算所有具有 BirthDate 且我的 ID 包含在列表中的客户。 我的客户 ID 列表可能很大(数百万条记录)。

这里是查询:

List<int> allClients = GetClientIDs();

int total = context.Clients.Where(x => allClients.Contains(x.ClientID) && x.BirthDate != null).Count();

当查询以这种方式重写时,

int total = context
    .Clients
    .Count(x => allClients.Contains(x.ClientID) && x.BirthDate != null);

它会导致同样的错误。

还尝试以不同的方式制作它,它会吃掉所有的记忆:

List<int> allClients = GetClientIDs();

total = (from x in allClients.AsQueryable()
         join y in context.Clients
         on x equals y.ClientID
         where y.BirthDate != null
         select x).Count();

【问题讨论】:

  • GetClientIDs 从哪里获取数据?如果它从数据库中提取它,您可能希望将其组合到查询中,而不是提取所有 id 以在单独的查询中使用。
  • 我认为这种形式是不可能做到的。 allClients.Contains(x.ClientID) 强制查询在本地工作。
  • @HamletHakobyan 不,allClients 可以是包含 linq-to-sql 查询的 IQueryable。如果是这样,它将作为 SQL 合并到 total 查询中。
  • @GertArnold 第一次转换allClients.AsQueryable() 不能保证allClientsIQueryable。无论如何,即使 allClientsIQueryable 也不能保证将整个 LINQ 查询转换为 SQL 查询。
  • @HamletHakobyan 也许吧。但juharr的建议值得一试。康斯坦丁,如果那是不可能的,我认为你唯一的选择就是分块进行查询。

标签: c# asp.net sql linq linq-to-sql


【解决方案1】:

我们在工作中遇到了同样的问题。问题是list.Contains() 创建了一个WHERE column IN (val1, val2, ... valN) 语句,因此您只能在其中放入多少值。我们最终做的实际上是像你一样分批做。

但是,我认为我可以为您提供更简洁、更优雅的代码来执行此操作。这是一个扩展方法,将添加到您通常使用的其他 Linq 方法中:

public static IEnumerable<IEnumerable<T>> BulkForEach<T>(this IEnumerable<T> list, int size = 1000)
{
    for (int index = 0; index < list.Count() / size + 1; index++)
    {
        IEnumerable<T> returnVal = list.Skip(index * size).Take(size).ToList();
        yield return returnVal;
    }
}

然后你这样使用它:

foreach (var item in list.BulkForEach())
{
    // Do logic here. item is an IEnumerable<T> (in your case, int)
}  

编辑
或者,如果你愿意,你可以让它像普通的 List.ForEach() 一样:

public static void BulkForEach<T>(this IEnumerable<T> list, Action<IEnumerable<T>> action, int size = 1000)
{
    for (int index = 0; index < list.Count() / size + 1; index++)
    {
        IEnumerable<T> returnVal = list.Skip(index * size).Take(size).ToList();
        action.Invoke(returnVal);
    }
}

这样使用:

list.BulkForEach(p => { /* Do logic */ });

【讨论】:

    【解决方案2】:

    正如 Gert Arnold 之前提到的,分块查询可以解决问题,但看起来很糟糕:

    List<int> allClients = GetClientIDs();
    
    int total = 0;
    
    const int sqlLimit = 2000;
    
    int iterations = allClients.Count() / sqlLimit;
    
    for (int i = 0; i <= iterations; i++)
    {
        List<int> tempList = allClients.Skip(i * sqlLimit).Take(sqlLimit).ToList();
    
        int thisTotal = context.Clients.Count(x => tempList.Contains(x.ClientID) && x.BirthDate != null);
    
        total = total + thisTotal;
    }
    

    【讨论】:

    • GetClientIDs 是做什么的?
    • 从 CSV 文件中获取客户端 ID
    • 我想这意味着您的选择非常有限,将您的请求分块是您的最佳选择。
    【解决方案3】:

    如上所述,您的查询可能被翻译为:

    select count(1)
    from Clients
    where ClientID = @id1 or ClientID = @id2 -- and so on up to the number of ids returned by GetClientIDs.
    

    您需要更改您的查询,以免向它传递太多参数。

    要查看生成的 SQL,您可以设置 Clients.Log = Console.Out,这将导致它在执行时被写入调试窗口。

    编辑:

    分块的一种可能替代方法是将 ID 作为分隔字符串发送到服务器,并在数据库中创建一个 UDF,该 UDF 可以将该字符串转换回列表。

    var clientIds = string.Jon(",", allClients);
    
    var total = (from client in context.Clients
                join clientIds in context.udf_SplitString(clientIds)
                    on client.ClientId equals clientIds.Id
                select client).Count();
    

    Google 上有很多拆分字符串的 UDF 示例。

    【讨论】:

      【解决方案4】:

      另一种选择,并且可能是查询时最快的方法,是将 CSV 文件中的数字添加到数据库中的临时表中,然后执行连接查询。

      分块进行查询意味着在客户端和数据库之间进行大量往返。如果您感兴趣的 ID 列表是静态的或很少更改,我建议使用临时表的方法。

      【讨论】:

      • 实际上,我对此有疑问。 EF 不是每个 Insert 语句都进行一次往返吗?那么通过 EF 向临时添加 100,000 条记录,然后查询该数据不会比执行 10,000 次选择慢吗?
      • 我认为 EntityFramework 不支持临时表的概念,所以我正在考虑通过执行原始 SQL 语句将数据放到服务器上,但要回答你的问题,你是对的,最后我检查了每个插入语句是否会在 EF 中往返,尽管有一些关于 EF6 的建议可以支持批处理。
      【解决方案5】:

      如果您不介意将工作从数据库移动到应用程序服务器并拥有内存,请尝试此操作。

      int total = context.Clients.AsEnumerable().Where(x => allClients.Contains(x.ClientID) && x.BirthDate != null).Count();

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多