2009-06-17 73 views
3

我已經簡化了這一點,因爲我在尋找一個通用的答案。比方說,我有一個表的設置是這樣的:Linq .Contains with large set cause TDS error

Parent 
recno int (unique, pk) 
date  datetime 
stuff varchar(50) 

Child 
parentrecno (int, fk) --- PK 
sequence (int)  --- PK 
data  varchar(50) 

在我的C#程序,我經歷了很多麻煩的是找父記錄,我很感興趣,並把它們塞進一個列表。父母是一張非常大的桌子,我寧願不要查詢它。所以我喜歡儲存的鑰匙:

List<int> recs = (from d in Parent where [.....] select d.recno).ToList(); 
Linq中

後來我可以說,找到所有的子記錄相關聯的父母:

var kids = from k in database.Childs 
     where recs.Contains(k.parentrecno) 
     select new { k }; 

這是所有偉大的,直到經濟共同體包含超過2100條目。然後我得到一個TDS RPC錯誤(太多參數)。

我看到它,我能方式:

  • 不要在直線上升SQL整個事情(其實不想做經歷的麻煩與一個DataReader,等...)。有一個涉及記錄資格的外部系統,所以我不知道這是否完全有可能。另外,我會生成這個列表兩次 - 一次當我需要在.Contains()中使用它,並再次用於其他目的。

  • 打破列表(錄製),然後閱讀塊中的孩子。

如果我把它分解成塊,然後我漂亮的LINQ遠一點下來不會在所有的工作:

var kids2 = (from kid in paydb.Childs 
     where 
      recs.Contains(kid.parentrecno) 
     group pay by kid.parentrecno into kgroup 
     select new { ParentRecNo = kgroup.Key, KidRecords = kgroup }) 
       .ToDictionary(kx => kx.ParentRecNo); 

因爲列表區域經濟共同體將包含需要組合在一起的東西,但必須爲Linq查詢拆分。

回答

2

我們使用SQL函數取值的一個varchar(最大值)分隔的列表作爲參數並返回一個表變量。 linq看起來像這樣:

from a in Context.SomeTable  
join nl in Context.ParseDelimited(nodeIdList) on a.NodeId.ToString() equals nl.FieldValue 

其中nodeIdList是一個字符串變量,包含來自先前查詢的id列表。醜陋的,但它確實得到了2100參數限制。

create function dbo.ParseDelimited(@delimitedList NVARCHAR(MAX)) returns @tblSample table(counter int, fieldValue NVARCHAR(100)) 
WITH SCHEMABINDING 
as 
begin 
    declare @counter NVARCHAR( 4) 
    declare @fieldValue NVARCHAR(100) 

    declare @tmpTable table(counter int primary key, fieldValue NVARCHAR(100)) 

    set @counter = 1 

    while charindex(',', @delimitedList) > 0 
    begin 
    set @fieldValue = ltrim(rtrim(substring(@delimitedList, 1, charIndex(',', @delimitedList)-1))) 

    insert into @tmpTable select @counter, @fieldValue 

    set @delimitedList = ltrim(rtrim(substring(@delimitedList, (charindex(',', @delimitedList) + 1), len(@delimitedList)))) 

    set @counter = @counter + 1 
    end 

    if ltrim(rtrim(@delimitedList)) != '' 
    begin 
    insert into @tmpTable select @counter, @delimitedList 
    end 

    insert into @tblSample select counter, fieldValue from @tmpTable 

    return 
end 
0

除了使用SQL和DataReader,您還可以編寫兩個存儲過程並通過LINQ使用這些存儲過程。或者甚至通過LINQ讀取id列表並將其作爲輸入存儲到您的存儲過程中。

您可能無法解決太多的參數問題(您是否有權訪問數據庫),並且塊解決方案不是很好,並且由於第二個查詢而不能解決整個問題。

編輯:由於recs集合不是完全由數據庫生成的,因此需要某種方式來告訴數據庫該集合的內容。我認爲你最好的選擇是使用一個存儲過程(或兩個),接受集合作爲一個大的逗號分隔的字符串。在存儲過程中,將字符串再次分割爲ID。

Somelinks解釋如何編寫和使用拆分字符串函數。順便說一下,如果您使用的是SQL Server 2008,那麼比字符串解析有更好的方法:table-valued parameters。您可以將一個表作爲參數傳遞給您的存儲過程。

+0

我的一個大問題是,在REC中限定記錄並非全部都是在SQL中完成的 - 還有另一個系統涉及到。能夠將這些密鑰鏟入臨時表也是可以的,除非我不認爲我可以從Linq獲得臨時表。 – 2009-06-17 16:56:53

+0

我更新了我的答案,並提供了有關如何解決問題的其他信息。 – 2009-06-23 07:05:53

0

這看起來像Linq .Join()的工作。我已經使用下面的對象而不是數據庫作爲數據源,但是如果你的LinqToSql提供者支持它,這個概念是相同的。

List<int> recs = new List<int>(Enumerable.Range(1, 2500)); 
List<Child> children = new List<Child>(Enumerable.Range(2000, 1000) 
    .Select(x => new Child 
    { 
     ParentRecNo = x 
    })); 

var kids = children.Join(recs, x => x.ParentRecNo, y => y, (x, y) => x); 

Console.WriteLine(kids.First().ParentRecNo); 
Console.WriteLine(kids.Last().ParentRecNo); 

輸出:

2000 
2500 

你可以用在你的字典建立代碼相同的加入如下:

var kids2 = children 
    .Join(recs, x => x.ParentRecNo, y => y, (x, y) => x) 
    .GroupBy(x => x.ParentRecNo) 
    .Select(kgroup => new 
     { 
      ParentRecNo = kgroup.Key, 
      KidRecords = kgroup 
     }) 
    .ToDictionary(kx => kx.ParentRecNo); 
+0

不錯的解決方案。 我唯一擔心的是數據庫中可能記錄的數量非常大。數據是否被檢索然後加入 - 在檢索後消除不需要的記錄,還是在SQL Server端消除了這些數據,然後將結果拉下來? – 2009-06-23 16:01:26

0

我認爲這是你在找什麼:

List<Child> children = 
database.Parents.Where('your favourite lambda expression').Select(x=>x.Childs).ToList(); 

所以...我不知道您正在使用的條件得到父母,但希望它可以用lambda表達式來完成,如:

List<Child> children = database.Parents 
    .Where(p=>p.recno > 10 && p.recno < 40) 
    .Select(x=>x.Childs).ToList();