2009-07-18 4 views
1

Ich stieß auf eine interessante Bug mit LINQ zu SQL. Sehen Sie sich den folgenden Code an, der aus einer LINQtoSQL-Abfrage einer Suchmaschine, die ich gerade schreibe, übersetzt wurde.LINQ to SQL und Objektlebenszeiten, Referenzen vs. Werte

Das Ziel der Abfrage ist, alle Gruppen zu finden, die die IDs "Joe", "Jeff", "Jim" in fortlaufender Reihenfolge haben.

Achten Sie besonders auf die Variablen localKeyword und localInt. Wenn Sie die Deklarationen dieser scheinbar nutzlosen lokalen Variablen löschen und sie durch die Proxys ersetzen würden, würden Sie feststellen, dass die Abfrage nicht mehr funktioniert.

Ich bin immer noch ein Anfänger mit LINQ zu SQL, aber es sieht aus wie es alle Einheimischen als Referenzen weitergegeben wird. Dies führt dazu, dass die Abfrage nur den Wert von lokalen Variablen hat, wenn die Abfrage ausgewertet wird. In LINQ to SQL endete meine Abfrage wie

Die Abfrage ist natürlich umschrieben. Was genau passiert, ist das ein Fehler? Ich möchte vielleicht besser verstehen, warum dies geschieht. Sie sollten den Code kompiliert in Visual Studio 2008.

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Linq; 

namespace BreakLINQ 
{ 
    class Program 
    { 
     public struct DataForTest 
     { 
      private int _sequence; 
      private string _ID; 
      private string _group; 

      public int Sequence 
      { 
       get 
       { 
        return _sequence; 
       } 
       set 
       { 
        _sequence = value; 
       } 
      } 
      public string ID 
      { 
       get 
       { 
        return _ID; 
       } 
       set 
       { 
        _ID = value; 
       } 
      } 
      public string Group 
      { 
       get 
       { 
        return _group; 
       } 
       set 
       { 
        _group = value; 
       } 
      } 
     } 
     static void Main(string[] args) 
     { 
      List<DataForTest> elements = new List<DataForTest> 
      { 
       new DataForTest() { Sequence = 0, ID = "John", Group="Bored" }, 
       new DataForTest() { Sequence = 1, ID = "Joe", Group="Bored" }, 
       new DataForTest() { Sequence = 2, ID = "Jeff", Group="Bored" }, 
       new DataForTest() { Sequence = 3, ID = "Jim", Group="Bored" }, 
       new DataForTest() { Sequence = 1, ID = "Jim", Group="Happy" }, 
       new DataForTest() { Sequence = 2, ID = "Jack", Group="Happy" }, 
       new DataForTest() { Sequence = 3, ID = "Joe", Group="Happy" }, 
       new DataForTest() { Sequence = 1, ID = "John", Group="Sad" }, 
       new DataForTest() { Sequence = 2, ID = "Jeff", Group="Sad" }, 
       new DataForTest() { Sequence = 3, ID = "Jack", Group="Sad" } 
      }; 

      string[] order = new string[] { "Joe", "Jeff", "Jim" }; 
      int sequenceID = 0; 
      var query = from item in elements 
         select item; 
      foreach (string keyword in order) 
      { 
       if (sequenceID == 0) 
       { 
        string localKeyword = keyword; 
        query = from item in query 
          where item.ID == localKeyword 
          select item; 
       } 
       else 
       { 
        string localKeyword = keyword; 
        int localSequence = sequenceID; 
        query = from item in query 
          where (from secondItem in elements 
            where secondItem.Sequence == item.Sequence + localSequence && 
             secondItem.ID == localKeyword 
            select secondItem.Group).Contains(item.Group) 
          select item; 
       } 
       sequenceID++; 
      } 
     } 
    } 
} 

Der Wert der Abfrage finden, nachdem der Code abgeschlossen ist sollte den Wert { "Joe", "Bored", 1}.

+0

Als nitpick, Sie haben System.Linq verwendet; zweimal. –

+0

Ich kann in Ihrem Code keine LINQ to SQL-Dateien sehen. Das ist nur LINQ zu Objekten. btw siehe: http://StackOverflow.com/Questions/1095707/what-is-the-Exact-Definition-of-A-Closure/1095770#1095770 –

Antwort

3

Der Grund dieser Variablen ohne die ‚Proxying‘ versagt ist, dass die Variablen erfasst durch die Ausdrücke in der LINQ-Abfrage sind. Ohne die Proxies verweist jede Iteration der Schleife auf die gleichen zwei Variablen (Schlüsselwort sequenceID), und wenn die Abfrage schließlich ausgewertet und ausgeführt wird, ist der für jede dieser Referenzen ersetzte Wert identisch; nämlich, welcher Wert in diesen Variablen vorhanden ist, wenn die Schleife beendet wird (in diesem Fall wollen Sie, dass wir 'Query' auswerten).

Die Abfrage verhält sich wie erwartet mit die Proxies, weil die erfassten Variablen pro Iteration der Schleife eindeutig deklariert werden; Nachfolgende Iterationen ändern die erfassten Variablen nicht, da sie nicht mehr im Gültigkeitsbereich sind. Die Proxy-Variablen sind überhaupt nicht nutzlos. Darüber hinaus ist dieses Verhalten von Entwurf; lass mich sehen, ob ich eine gute Referenz finden kann ...

2
var correctQuery = 
    from o in elements 
    join tw in elements on o.Sequence equals tw.Sequence - 1 
    join th in elements on tw.Sequence equals th.Sequence - 1 
    where 
     o.ID == "Joe" && tw.ID == "Jeff" && th.ID == "Jim" && o.Group == tw.Group && 
     th.Group == tw.Group 
    select new {o.ID, o.Sequence, o.Group}; 
+0

Für eine Suchmaschine benötigen Sie eine Möglichkeit, dies mit einem beliebigen Array zu tun von Elementen, sagen wir neue Zeichenfolge [] {"John", "Joe", "Jeff", "Jim"}; – hannasm

+0

Allerdings war ich überrascht, wie viel sauberer dies wird :) – hannasm

2

Dies ist kein Fehler, es ist "von Design."

Was hier unter der Haube passiert ist, dass Sie die Iterationsvariable einer for-Schleife in einem Lambda-Ausdruck erfassen. Es wird tatsächlich in einer Abfrage verwendet, aber unter der Haube wird dies in einen Lambda-Ausdruck übersetzt.

In einer for-Schleife gibt es nur eine Iterationsvariable für alle Schleifen. Nicht eine für jede Iteration der Schleife. Jede Abfrage erfasst also dieselbe Variable. Wenn sie ausgeführt wird, wird die Abfrage gegen den aktuellen oder in diesem Fall letzten Wert ausgeführt, der in der Iterationsvariablen gespeichert ist.

Der Grund dafür, dass Ihr temporärer Variablentrick funktioniert, ist, dass es für jede Iteration der Schleife im Wesentlichen eine Instanz der temporären Variablen geben wird. Jede Abfrage erfasst also einen anderen, unabhängigen Wert.

Ein prägnantes Beispiel demo'ing dieses Problem ist wie folgt

var list = new List<Func<int>>(); 
foreach (var cur in Enumerable.Range(1,3)) { 
    list.Add(() => cur); 
} 
foreach (var lambda in list) { 
    Console.WriteLine(lambda()); // always prints 3 
}