2013-02-03 42 views
4

我有LINQ查詢返回以下錯誤: 「傳入的表格數據流(TDS)遠程過程調用(RPC)協議流不正確。在此提供的參數太多RPC請求,最大值爲2100「。將大整數列入LINQ查詢

我所需要的是計算所有有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(); 
+1

GetClientIDs從哪裏獲取數據?如果它從數據庫中拉出來,你可能想把它合併到你的查詢中,而不是把所有的id都用在單獨的查詢中。 – juharr

+0

我認爲這是不可能的。 'allClients.Contains(x.ClientID)'強制查詢在本地工作。 –

+1

@HamletHakobyan否,'allClients'可以是包含linq-to-sql查詢的'IQueryable'。如果是這樣,它將在'total'查詢中作爲SQL合併。 –

回答

0

那麼作爲格特·阿諾德前面所提到的,使得塊查詢解決了這個問題,但它看起來討厭:

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; 
} 
+0

GetClientIDs是做什麼的? –

+0

它從CSV文件獲取客戶端ID –

+0

我想這意味着你的選項非常有限,並且分塊請求是最佳選擇。 –

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

在谷歌UDF上有很多分割字符串的例子。

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) 
} 

編輯
或者,如果你更喜歡,你可以使它像普通列表一樣行事。的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 */ }); 
0

另一種選擇和可能是在查詢最快的時間是從CSV添加您的數字文件到臨時表中您的數據庫和然後執行連接查詢。

以塊的形式進行查詢意味着您的客戶端和數據庫之間需要進行大量往返。如果您感興趣的ID列表是靜態的或很少發生更改,我建議使用臨時表的方法。

+0

其實我有一個問題。每個插入語句不是EF執行一次往返?因此,不會通過EF向臨時數據添加100,000條記錄,然後查詢該數據比執行10,000條選擇要慢? – Artless

+0

我不認爲EntityFramework支持臨時表的概念,所以我想通過執行原始SQL語句將數據放到服務器上,但要回答你的問題,你是對的,最後我檢查了每個插入語句儘管對EF6有一些建議可以支持批量,但在EF中是往返。 – Ameen

0

如果您不介意將工作從數據庫移到應用程序服務器並獲得內存,請嘗試此操作。 。

INT總= context.Clients.AsEnumerable()式(!X => allClients.Contains(x.ClientID)& & x.BirthDate = NULL).Count之間();