将大型Integers列表加入LINQ Query

时间:2013-02-03 15:17:45

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

我有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();

5 个答案:

答案 0 :(得分: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 */ });

答案 1 :(得分:0)

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

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,它可以将该字符串转换回列表。

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的例子。

答案 2 :(得分:0)

正如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;
}

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

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

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