2009-03-18 16 views
3

Ich habe den folgenden Code:LinqToSql seltsames Verhalten


var tagToPosts = (from t2p in dataContext.TagToPosts 
            join t in dataContext.Tags on t2p.TagId equals t.Id 
            select new { t2p.Id, t.Name }); 
//IQueryable tag2postsToDelete; 
foreach (Tag tag in tags) 
{ 
    Debug.WriteLine(tag); 
    tagToPosts = tagToPosts.Where(t => t.Name != tag.Name); 
} 
IQueryable tagToPostsToDelete = (from t2p in dataContext.TagToPosts 
              join del in tagToPosts on t2p.Id equals del.Id 
              select t2p); 
dataContext.TagToPosts.DeleteAllOnSubmit(tagToPostsToDelete); 

wo tags eine List<Tag> - Stichwortliste Konstruktor erstellt, so haben sie keine ID. Ich führe den Code aus, setze einen Brems-Punkt mit Debug in Verbindung und warte auf ein paar Zyklen. Dann setze ich tagToPosts.ToList() in das Watch-Fenster, damit die Abfrage ausgeführt wird. In SQL Profiler kann ich die folgende Abfrage sehen:

exec sp_executesql N'SELECT [t0].[Id], [t1].[Name] 
FROM [dbo].[tblTagToPost] AS [t0] 
INNER JOIN [dbo].[tblTags] AS [t1] ON [t0].[TagId] = [t1].[Id] 
WHERE ([t1].[Name] @p0) AND ([t1].[Name] @p1) AND ([t1].[Name] @p2)',N'@p0 nvarchar(4),@p1 nvarchar(4),@p2 nvarchar(4)',@p0=N'tag3',@p1=N'tag3',@p2=N'tag3' 

Wir alle Parameter Parameter sehen den Wert des letzten tag.Name in einem Zyklus haben. Haben Sie eine Idee, wie Sie dies erreichen können und bekommen Zyklus Where mit neuen Bedingungen jeden Tymus hinzufügen? Ich kann sehen, dass IQueryable vor der Ausführung nur Zeiger auf die Variable speichert.

Antwort

10

Ihre foreach ändern:

foreach (Tag tag in tags){ 
    var x = tag.Name; 
    tagToPosts = tagToPosts.Where(t => t.Name != x); 
} 

Der Grund für diese lazy evaluation und variable Erfassung ist. Grundsätzlich, was Sie in der foreach Anweisung tun, ist nicht Ergebnisse filtern, wie es aussehen könnte. Sie erstellen einen Ausdrucksbaum, der von einigen auszuführenden Variablen abhängt. Es ist wichtig zu beachten, dass die tatsächlichen Variablen in diesen Ausdrucksbäumen erfasst werden und nicht ihre Werte zum Zeitpunkt der Erfassung. Da die Variable tag jedes Mal verwendet wird (und ihr Gültigkeitsbereich das gesamte foreach ist, so dass sie am Ende jeder Iteration nicht aus dem Gültigkeitsbereich herausfällt), wird sie für jeden Teil des Ausdrucks erfasst, und der letzte Wert wird für verwendet alle ihrer Vorkommen. Die Lösung besteht darin, eine temporäre Variable zu verwenden, die im Bereich im Bereich liegt, so dass sie bei jeder Iteration den Gültigkeitsbereich verlässt und bei der nächsten Iteration als neue Variable betrachtet wird.

1

Ja, Mehrdads Antwort ist richtig. Wenn ein Closure eine Variable in C# erfasst (was passiert, wenn sich Ihr "Where" -Lambda auf die Variable "tag" bezieht), wendet der Compiler eine ziemlich genaue Regel an, wie das Capture durchgeführt wird. Wenn die erfasste Variable innerhalb des gleichen Bereichs liegt, wie durch die umgebenden {und} Klammern festgelegt, wird der Wert dieser Variablen so wie er ist erfasst. Wenn es außerhalb des Gültigkeitsbereichs liegt, wird nur ein Verweis auf diese Variable erfasst. In Ihrem ursprünglichen Beitrag hatte die Variablen "tag" bereits die gesamte Schleife durchlaufen und den letzten Wert erreicht. Wenn Sie jedoch die von Mehrdad vorgeschlagene Änderung vornehmen, erfassen Sie eine Variable in demselben Bereich, sodass der individuelle Wert der Variablen in Ihren Abschluss eingebettet wird, sodass Sie die gewünschten Ergebnisse erhalten.

Übrigens, Sie können sagen, "Ja, aber die Variable" Tag "ist im gleichen Bereich." Aber es ist wirklich nicht, denn unter der Haube, der Compiler dreht ein for-each in etwa so aus (dies ist sehr rau, nur um zu zeigen, was mit den Klammern geschieht):

{ 
    var iterator = GetTheIterator(); 
    { 
     while(iterator.MoveNext()) 
     { 
      // Your loop code here 
     } 
    } 
} 

Punkt ist, Ihr Iterator für Ein for-each wird immer in einem Bereich außerhalb des Bereichs liegen, in dem sich der Loop-Code befindet.