2009-06-17 3 views
3

Ich habe dies ein wenig vereinfacht, weil ich eine allgemeine Antwort suche. Lassen Sie uns sagen, dass ich eine Tabelle Setup wie diese haben:Linq. Enthält mit großem Satz verursacht TDS-Fehler

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

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

Und in meinem C# Programm habe ich durch eine Menge Ärger gegangen, um die übergeordneten Datensätze zu finden, die mich interessieren und stopfen sie in eine Liste . Parent ist eine wirklich große Tabelle, und ich würde sie lieber nicht mehr als nötig abfragen. So Eichhörnchen ich die Schlüssel weg:

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

Später in Linq kann ich sagen, alle untergeordneten Datensätze für die zugehörigen Eltern finden:

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

Das ist alles groß, bis recs mehr als 2100 enthält Einträge. Dann bekomme ich einen TDS RPC Fehler (zu viele Parameter).

So wie ich es sehe ich kann:

  • Sie das Ganze in gerade nach oben SQL (wollte nicht wirklich mit einem Datareader den Streit gehen durch, etc ...). Es gab ein externes System bei der Qualifizierung der Aufzeichnungen, also weiß ich nicht, ob das auch möglich ist. Außerdem würde ich diese Liste zweimal erstellen - einmal, wenn ich sie in .Contains() verwenden muss, und wieder für andere Zwecke.

  • Brechen Sie die Liste (recs) auf, und lesen Sie Child in Chunks.

Wenn ich es brechen in Stücke, dann meine ziemlich Linq ein wenig weiter nach unten wird nicht funktionieren:

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

Da die Liste recs Dinge enthalten, die zusammen werden mussten gruppiert , aber notwendigerweise getrennt für die Linq-Abfrage.

Antwort

2

Wir verwenden eine SQL-Funktion nimmt eine varchar (max) getrennte Liste von Werten als Argument und Rückgabe einer Tabellenvariablen. Die Linq sieht wie folgt aus:

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

wo nodeIdList ist eine String-Variable, die Liste der IDs aus einer früheren Abfrage enthält. Hässlich, aber es geht um die 2100 Parameter Grenze.

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

sieht das wie die einzige Lösung aus, die tatsächlich funktioniert –

+0

John, können Sie bitte einige Beispiele Ihrer Lösung bereitstellen. –

0

Anstatt SQL und einen DataReader zu verwenden, können Sie auch zwei gespeicherte Prozeduren schreiben und diese über LINQ verwenden. Oder lesen Sie die Liste der IDs über LINQ und füttern Sie diese als Eingabe für Ihre gespeicherten Prozeduren.

Sie werden wahrscheinlich nicht in der Lage sein, das Problem zu vieler Parameter zu lösen (haben Sie Zugriff auf die Datenbank) und die Chunk-Lösung ist nicht sehr nett und löst das ganze Problem wegen der zweiten Abfrage nicht.

EDIT: Da die recs Sammlung nicht vollständig Datenbank-generiert wird, müssen Sie eine Möglichkeit, Ihrer Datenbank den Inhalt dieser Sammlung mitzuteilen. Ich denke, Ihre beste Option wäre die Verwendung einer gespeicherten Prozedur (oder zwei), die die Auflistung als große durch Kommas getrennte Zeichenfolge akzeptiert. In der gespeicherten Prozedur teilen Sie die Zeichenfolge erneut in IDs.

Somelinks erläutert, wie Sie eine Split-String-Funktion schreiben und verwenden.

Übrigens, wenn Sie SQL Server 2008 verwenden, gibt es einen weitaus besseren Ansatz als String-Analyse: table-valued parameters. Sie können eine Tabelle als Parameter an Ihre gespeicherte Prozedur übergeben.

+0

Mein großes Problem ist, dass die Datensätze in recs Qualifying nicht alle in SQL getan war - es gibt ein anderes System beteiligt. Die Schlüssel in einen temporären Tisch schaufeln zu können, würde auch funktionieren, außer dass ich nicht glaube, dass ich auch von Linq zu diesem Temp-Tisch komme. –

+0

Ich habe meine Antwort mit einigen zusätzlichen Informationen zur Lösung des Problems aktualisiert. –

0

Das sieht wie ein Job für Linq .Join() aus. Ich habe unten Objekte anstelle einer Datenbank als Datenquelle verwendet, aber das Konzept ist das gleiche, wenn Ihr LinqToSql-Provider es unterstützt.

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

Ausgang:

2000 
2500 

können Sie das gleiche in Ihrem Wörterbuch Erstellungscode Join wie folgt verwenden:

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

Schöne Lösung. Meine einzige Sorge ist, wenn die Anzahl der möglichen Datensätze in der Datenbank sehr sehr groß ist. Werden die Daten abgerufen und dann verknüpft, um die unerwünschten Datensätze nach dem Abruf zu entfernen, oder werden sie auf der SQL Server-Seite eliminiert und dann das Ergebnis übertragen? –

+0

Dies funktioniert nicht. Sie versuchen, eine clientseitige IEnumerable (recs) in einer SQL-Abfrage zu verwenden (dies wird nur für Enthält unterstützt, die in einen IN-Ausdruck übersetzt wird). Fragen Sie sich, wie LINQtoSQL die Datenbank über den Inhalt von recs informieren soll. Dies funktioniert nur, wenn Sie zuerst den Inhalt der gesamten Childs-Tabelle an den Client senden und dann mit recs verbinden. –

+1

Das Abrufen nur der ChildId und ParentRecordId, um den Join auf der Clientseite durchzuführen, ist möglicherweise weniger teuer. Befolgen Sie dies mit Abfragen für die Child-Details der übereinstimmenden Datensätze und/oder brechen Sie sie in Chunks von weniger als 2100 auf, vielleicht mithilfe einer InSetsOf-Implementierung http://stackoverflow.com/questions/1034429/how-to-prevent-memory- overflow-wenn-mit-einem-ierumerablet-und-linq-zu-sql/1035039 # 1035039. Schließlich scheint SQL Server 2008, wenn das eine Option Clint ist, tabellenname Parameter http://msdn.microsoft.com/en-us/library/bb675163.aspx als eine Art und Weise um das 2100 Parameterlimit hinzugefügt. – Handcraftsman

0

Ich denke, das ist das, was Sie suchen:

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

Also ... ich weiß es nicht in welchem ​​Zustand Sie verwenden die Eltern für immer, aber hoffentlich kann es mit einem Lambda-Ausdruck, wie geschehen:

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