2013-08-08 15 views
5

Wenn ich die GetFoo().Count() von einer Methode außerhalb meiner DB-Klasse aufrufen, führt Entity Framework 5 eher eine ineffiziente SELECT-Abfrage als eine COUNT. Wenn ich ein paar andere Fragen wie this lese, sehe ich, dass dies erwartetes Verhalten ist.Entitätsframework - COUNT statt SELECT

public IEnumerable<DbItems> GetFoo() 
{ 
    return context.Items.Where(d => d.Foo.equals("bar")); 
} 

Ich habe daher ein Zählverfahren meiner DB-Klasse hinzugefügt, die korrekt eine COUNT Abfrage führt:

public int GetFooCount() 
{ 
    return context.Items.Where(d => d.Foo.equals("bar")).Count(); 
} 

mich mehrere Male von Definieren von Anfragen zu speichern, würde Ich mag, dies ändern Zu dem Folgendem. Dies führt jedoch erneut eine SELECT, obwohl es innerhalb der DB-Klasse ist. Warum ist das - und wie kann ich es vermeiden?

public int GetFooCount() 
{ 
    return this.GetFoo().Count(); 
} 
+1

Context.Items.Count (d => d.Foo == "bar"); du brauchst nicht den Where call :) –

Antwort

14

da GetFoo() Gibt ein IEnumerable<DbItems>, die Abfrage als SELECT ausgeführt wird, dann wird Count auf die Sammlung von Objekten angewendet und ist nicht auf die SQL projiziert.

Eine Option kehrt ein IQueryable<DbItems> statt:

public IQueryable<DbItems> GetFoo() 
{ 
    return context.Items.Where(d => d.Foo.equals("bar")); 
} 

Aber das kann das Verhalten anderer Anrufer ändern, die die colection erwarten geladen werden (mit IQueryable wird es faul belastet sein). Insbesondere Methoden, die .Where Aufrufe hinzufügen, die nicht in SQL übersetzt werden können.Leider werden Sie diese zur Kompilierzeit nicht kennen, daher ist ein gründliches Testen notwendig.

Ich würde stattdessen erstellen neue Methode, die eine IQueryable zurückgibt:

public IQueryable<DbItems> GetFooQuery() 
{ 
    return context.Items.Where(d => d.Foo.equals("bar")); 
} 

so Ihre vorhandenen Nutzungen sind nicht betroffen. Wenn Sie diesen Code wiederverwenden wollte konnte man GetFoo ändern:

public IEnumerable<DbItems> GetFoo() 
{ 
    return GetFooQuery().AsEnumerable(); 
} 
13

Um dieses Verhalten, das Sie Unterschied zwischen IEnumerable<T> und IQueryable<T> Erweiterungen verstehen müssen, zu verstehen. Zuerst arbeitet man mit Linq zu Objects, das sind In-Memory-Abfragen. Diese Abfragen werden nicht in SQL übersetzt, da dies einfacher .NET-Code ist. Also, wenn Sie etwas IEnumerable<T> Wert, und Sie Count() dies ruft Enumerable.Count Extension-Methode ausgeführt werden, die so etwas wie ist:

public static int Count<TSource>(this IEnumerable<TSource> source) 
{ 
    int num = 0; 
    foreach(var item in source) 
     num++; 

    return num; 
} 

Aber es gibt ganz andere Geschichte mit IQueryable<T> Erweiterungen. Diese Methoden werden vom zugrunde liegenden LINQ-Provider (EF in Ihrem Fall) in etwas anderes als .NET-Code übersetzt. Z.B. zu SQL. Und diese Übersetzung tritt auf, wenn Sie eine Abfrage ausführen. Alle Abfragen werden analysiert, und es wird ein nettes (naja, nicht immer nettes) SQL generiert. Diese SQL wird in der Datenbank ausgeführt, und das Ergebnis wird Ihnen als Ergebnis der Abfrageausführung zurückgegeben.

So gibt Ihre Methode IEnumerable<T> zurück - das bedeutet, dass Sie die Methode Enumerable.Count() verwenden, die im Speicher ausgeführt werden soll. Somit wird die folgende Abfrage durch EF in SQL übersetzt und dann die Anzahl der Elemente berechnet, die mit der obigen Methode in-memory berechnet wurden. Aber wenn Sie Rückkehr Typ IQueryable<T> ändern, dann ändert sich alles

public IQueryable<DbItems> GetFoo() 
{ 
    return context.Items.Where(d => d.Foo.equals("bar")); 
} 

Jetzt Queryable<T>.Count() ausgeführt wird. Das bedeutet, dass die Abfrage weiterhin aufgebaut wird (nun, Count() ist der Operator, der die Abfrageausführung erzwingt, aber Count() wird Teil dieser Abfrage). Und EF übersetzt

context.Items.Where(d => d.Foo.equals("bar")).Count() 

in SQL-Abfrage, die auf Serverseite ausgeführt wird.

+0

Hi! Ich habe versucht, mit .AsQueryable(). Count() und .AsEnumerable(). Count() und die Performance-Statistiken, die ich bekomme, sind die gleichen. Eg. - In MySql Workbench "Wählen Sie Anzahl (*) von Äpfel" ~ 1ms - In EF LINQ "dbContext.apples.Count()" ~ 800ms – Ross

+1

@Ross Ich denke, Sie sollten neue Frage mit allen erforderlichen Informationen erstellen. Z.B. Wenn es Ihre erste Abfrage ist, benötigt EF etwas Zeit, um edm zu erstellen –