2009-05-20 2 views
83

Ich versuche zu ermitteln, wie zählen die übereinstimmenden Zeilen in einer Tabelle mit dem EntityFramework.Wie COUNT Zeilen innerhalb EntityFramework, ohne Inhalte zu laden?

Das Problem ist, dass jede Zeile viele Megabyte Daten (in einem binären Feld) haben kann. Natürlich wäre die SQL etwas wie folgt aus:

SELECT COUNT(*) FROM [MyTable] WHERE [fkID] = '1'; 

ich alle Zeilen laden konnte und dann mit dem Grafen zu finden:

var owner = context.MyContainer.Where(t => t.ID == '1'); 
owner.MyTable.Load(); 
var count = owner.MyTable.Count(); 

Aber das ist höchst ineffizient. Gibt es einen einfacheren Weg?


EDIT: Danke, alles. Ich habe die DB von einem privaten Anhang verschoben, damit ich Profiling ausführen kann; Das hilft aber führt zu Verwirrungen, die ich nicht erwartet habe. an und ich will nicht, dass die Truck zu verlassen, es sei denn es gibt -

Und meine realen Daten ist ein bisschen tiefer, werde ich Paletten von Hüllen von Artikel verwenden Trucks Trage mindestens eine Artikel drin.

Meine Versuche sind unten gezeigt. Der Teil, den ich nicht bekomme, ist, dass CASE_2 niemals auf den DB-Server (MSSQL) zugreift.

var truck = context.Truck.FirstOrDefault(t => (t.ID == truckID)); 
if (truck == null) 
    return "Invalid Truck ID: " + truckID; 
var dlist = from t in ve.Truck 
    where t.ID == truckID 
    select t.Driver; 
if (dlist.Count() == 0) 
    return "No Driver for this Truck"; 

var plist = from t in ve.Truck where t.ID == truckID 
    from r in t.Pallet select r; 
if (plist.Count() == 0) 
    return "No Pallets are in this Truck"; 
#if CASE_1 
/// This works fine (using 'plist'): 
var list1 = from r in plist 
    from c in r.Case 
    from i in c.Item 
    select i; 
if (list1.Count() == 0) 
    return "No Items are in the Truck"; 
#endif 

#if CASE_2 
/// This never executes any SQL on the server. 
var list2 = from r in truck.Pallet 
     from c in r.Case 
     from i in c.Item 
     select i; 
bool ok = (list.Count() > 0); 
if (!ok) 
    return "No Items are in the Truck"; 
#endif 

#if CASE_3 
/// Forced loading also works, as stated in the OP... 
bool ok = false; 
foreach (var pallet in truck.Pallet) { 
    pallet.Case.Load(); 
    foreach (var kase in pallet.Case) { 
     kase.Item.Load(); 
     var item = kase.Item.FirstOrDefault(); 
     if (item != null) { 
      ok = true; 
      break; 
     } 
    } 
    if (ok) break; 
} 
if (!ok) 
    return "No Items are in the Truck"; 
#endif 

Und die SQL aus CASE_1 resultiert, wird geleitet durch Sp_executesql, aber:

SELECT [Project1].[C1] AS [C1] 
FROM (SELECT cast(1 as bit) AS X) AS [SingleRowTable1] 
LEFT OUTER JOIN (SELECT 
    [GroupBy1].[A1] AS [C1] 
    FROM (SELECT 
     COUNT(cast(1 as bit)) AS [A1] 
     FROM [dbo].[PalletTruckMap] AS [Extent1] 
     INNER JOIN [dbo].[PalletCaseMap] AS [Extent2] ON [Extent1].[PalletID] = [Extent2].[PalletID] 
     INNER JOIN [dbo].[Item] AS [Extent3] ON [Extent2].[CaseID] = [Extent3].[CaseID] 
     WHERE [Extent1].[TruckID] = '....' 
    ) AS [GroupBy1]) AS [Project1] ON 1 = 1 

[ich wirklich nicht haben Trucks, Treiber, Paletten, Kisten oder Gegenstände; Wie Sie aus der SQL sehen können, sind die Truck-Pallet- und Pallet-Case-Beziehungen viele-zu-viele - obwohl ich das nicht für wichtig halte. Meine realen Objekte sind immaterielle Werte und schwerer zu beschreiben, daher habe ich die Namen geändert.]

+1

Wie haben Sie das Palettenladeproblem gelöst? – Sherlock

Antwort

98

Abfragesyntax:

var count = (from o in context.MyContainer 
      where o.ID == '1' 
      from t in o.MyTable 
      select t).Count(); 

Methodensyntax:

var count = context.MyContainer 
      .Where(o => o.ID == '1') 
      .SelectMany(o => o.MyTable) 
      .Count() 

Beide generieren dieselbe SQL-Abfrage.

+0

Warum die 'SelectMany()'? Wird es benötigt? Würde es ohne es nicht richtig funktionieren? –

+0

@JoSmo, nein, das ist eine völlig andere Abfrage. –

+0

Danke, dass du das für mich geklärt hast. Ich wollte nur sicher sein. :) –

37

Ich glaube, Sie wie

etwas wollen
var count = context.MyTable.Count(t => t.MyContainer.ID == '1'); 

(bearbeitet Kommentare zu reflektieren)

+1

Ich glaube, er braucht die Anzahl von MyTable und nicht von MyContainer ... – bytebender

+0

Nein, er benötigt die Anzahl der Entitäten in MyTable referenziert von der einen Einheit mit ID = 1 in MyContainer –

+3

Übrigens, wenn t.ID eine PK ist, dann zähle im obigen code wird * immer * 1 sein. :) –

2

ich denke, das sollte funktionieren ...

var query = from m in context.MyTable 
      where m.MyContainerId == '1' // or what ever the foreign key name is... 
      select m; 

var count = query.Count(); 
+0

Dies ist die Richtung, die ich zuerst ging, aber es ist mein Verständnis, dass, wenn Sie es manuell hinzugefügt haben, m eine MyContainer-Eigenschaft, aber keine MyContainerId haben wird. Was Sie also untersuchen möchten, ist m.MyContainer.ID. – Kevin

+0

Wenn MyContainer das übergeordnete Element ist und MyTable die untergeordneten Elemente in der Beziehung sind, mussten Sie diese Beziehung mit einem Fremdschlüssel herstellen. Ich bin mir nicht sicher, wie Sie sonst wissen würden, welche MyTable-Entitäten mit einer MyContainer-Entität verknüpft sind ... Aber vielleicht Ich habe eine Vermutung über die Struktur gemacht ... – bytebender

8

Nun, selbst die SELECT COUNT(*) FROM Table wird ziemlich ineffizient sein, besonders bei großen Tabellen, da SQL Server wirklich nichts anderes tun kann, als einen vollständigen Tabellenscan durchzuführen (Clustered Index Scan).

Manchmal ist es gut genug, um eine ungefähre Anzahl der Zeilen aus der Datenbank zu kennen, und in einem solchen Fall eine Aussage wie diese könnte genügen:

SELECT 
    SUM(used_page_count) * 8 AS SizeKB, 
    SUM(row_count) AS [RowCount], 
    OBJECT_NAME(OBJECT_ID) AS TableName 
FROM 
    sys.dm_db_partition_stats 
WHERE 
    OBJECT_ID = OBJECT_ID('YourTableNameHere') 
    AND (index_id = 0 OR index_id = 1) 
GROUP BY 
    OBJECT_ID 

Dies wird die dynamische Verwaltungssicht prüfen und extrahieren die Anzahl der Zeilen und die Tabellengröße von ihm, eine bestimmte Tabelle gegeben. Dazu addieren Sie die Einträge für den Heap (index_id = 0) oder den Clustered-Index (index_id = 1).

Es ist schnell, es ist einfach zu bedienen, aber es ist nicht garantiert, dass es 100% ig genau oder aktuell ist. In vielen Fällen ist dies jedoch "gut genug" (und belastet den Server viel weniger).

Vielleicht würde das auch für Sie funktionieren? Um es in EF zu verwenden, müssten Sie es natürlich in einem gespeicherten Proc einpacken oder einen direkten Aufruf von "SQL-Abfrage ausführen" verwenden.

Marc

+1

Es wird keine vollständige Tabelle sein Scan aufgrund der FK-Referenz in der WHERE. Nur Details des Masters werden gescannt. Das Leistungsproblem, das er hatte, war das Laden von Blob-Daten, nicht die Anzahl der Aufzeichnungen. Unter der Annahme, dass es typischerweise nicht zehntausende Datensätze pro Stammdatensatz gibt, würde ich nicht "optimieren", was nicht wirklich langsam ist. –

+0

OK, ja, in diesem Fall werden Sie nur eine Teilmenge auswählen - das sollte in Ordnung sein. Was die Blob-Daten anbelangt - ich hatte den Eindruck, dass Sie ein "deferred loading" für jede Spalte in einer Ihrer EF-Tabellen festlegen könnten, um das Laden zu vermeiden. –

+0

Gibt es eine Möglichkeit, dieses SQL mit dem EntityFramework zu verwenden? Wie auch immer, in diesem Fall musste ich nur wissen, dass es passende Zeilen gab, aber ich stellte die Frage absichtlich allgemeiner. – NVRAM

3

Verwenden der ExecuteStoreQuery Methode des Entity-Kontext. Dies vermeidet das Herunterladen der gesamten Ergebnismenge und das Deserialisieren in Objekte, um eine einfache Zeilenzählung durchzuführen.

int count; 

    using (var db = new MyDatabase()){ 
     string sql = "SELECT COUNT(*) FROM MyTable where FkId = {0}"; 

     object[] myParams = {1}; 
     var cntQuery = db.ExecuteStoreQuery<int>(sql, myParams); 

     count = cntQuery.First<int>(); 
    } 
+6

Wenn Sie 'int count = context.MyTable.Count (m => m.MyContainerID == '1')' schreiben, ähnelt das generierte SQL genau dem, was Sie tun, aber der Code ist viel schöner. Es werden keine Entitäten als solche in den Speicher geladen. Probieren Sie es in LINQPad aus, wenn Sie möchten - es zeigt Ihnen die SQL, die unter den Covern verwendet wird. –

+0

Inline-SQL. . nicht mein Lieblingsding. – Duanne

14

Wie ich es verstehe, lädt die ausgewählte Antwort immer noch alle zugehörigen Tests. Laut diesem msdn Blog gibt es einen besseren Weg.

http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx

Speziell

using (var context = new UnicornsContext()) 

    var princess = context.Princesses.Find(1); 

    // Count how many unicorns the princess owns 
    var unicornHaul = context.Entry(princess) 
         .Collection(p => p.Unicorns) 
         .Query() 
         .Count(); 
} 
+3

Es ist nicht notwendig, zusätzliche 'Find (1)' Anfrage zu machen. Erstellen Sie einfach die Entität und hängen Sie sie an den Kontext an: 'var princess = new PrincessEntity {Id = 1}; context.Princesses.Attach (Prinzessin); ' – tenbits

8

Dies ist mein Code:

IQueryable<AuctionRecord> records = db.AuctionRecord; 
var count = records.Count(); 

Sicherstellen, dass die Variable als IQueryable definiert wird dann, wenn Sie Count() Methode verwenden, wird EF etwas ausführen wie

select count(*) from ... 

Wenn die Datensätze andernfalls als IEnumerable definiert sind, fragt das generierte sql die gesamte Tabelle ab und zählt die Anzahl der zurückgegebenen Zeilen.

Verwandte Themen