2009-05-20 16 views
83

Sto provando a determinare come contare le righe corrispondenti su una tabella utilizzando EntityFramework.Come COUNT righe all'interno di EntityFramework senza caricare il contenuto?

Il problema è che ogni riga può avere molti megabyte di dati (in un campo binario). Naturalmente la SQL sarebbe qualcosa di simile:

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

potrei caricare tutte le righe e quindi trovare il conte con:

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

Ma questo è grossolanamente inefficiente. c'è un modo più facile?


EDIT: Grazie, tutti. Ho spostato il DB da un privato collegato in modo da poter eseguire il profiling; questo aiuta ma causa confusioni che non mi aspettavo.

E la mia vera dei dati è un po 'più in profondità, userò Trucks portando Pallets di casi di Voci - e io non voglio che il Truck di lasciare a meno che non ci sia Almeno uno Articolo in esso.

I miei tentativi sono mostrati di seguito. La parte che non ottengo è che CASE_2 non accede mai al server DB (MSSQL).

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 

E la SQL risultante da CASE_1 viene convogliata attraverso sp_executesql, ma:

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 

[io in realtà non hanno Camion, Driver, bancali, casse o Articoli; come potete vedere dall'SQL, le relazioni Truck-Pallet e Pallet-Case sono molte-a-molti - anche se non penso che importi. I miei oggetti reali sono immateriali e più difficili da descrivere, quindi ho cambiato i nomi.]

+1

come è stato risolto il problema di caricamento del pallet? – Sherlock

risposta

98

sintassi di query:

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

Sintassi del metodo:

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

Entrambi generano la stessa query SQL.

+0

Perché il 'SelectMany()'? È necessario? Non funzionerebbe correttamente senza di esso? –

+0

@JoSmo, no, è una query completamente diversa. –

+0

Grazie per averlo chiarito. Volevo solo essere sicuro. :) –

37

Penso che si desidera qualcosa di simile

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

(modificati in modo da riflettere i commenti)

+1

Credo che abbia bisogno del conteggio di MyTable e non di MyContainer ... – bytebender

+0

No, ha bisogno del conteggio delle entità in MyTable cui fa riferimento l'entità con ID = 1 in MyContainer –

+3

Per inciso, se t.ID è un PK, poi conteggio nel codice sopra sarà * sempre * essere 1. :) –

2

Penso che questo dovrebbe funzionare ...

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

Questa è la direzione in cui sono andato all'inizio, ma sono a mia conoscenza che, a meno che tu non l'abbia aggiunto manualmente, m avrà una proprietà MyContainer ma nessun MyContainerId. Quindi, quello che vuoi esaminare è m.MyContainer.ID. – Kevin

+0

Se MyContainer è il genitore e MyTable sono i bambini nella relazione, è necessario stabilire tale relazione con una chiave esterna, non sono sicuro in quale altro modo si saprebbe quali entità MyTable sono associate a un'entità MyContainer ... Ma forse Ho fatto un'ipotesi sulla struttura ... – bytebender

8

Bene, anche lo SELECT COUNT(*) FROM Table sarà abbastanza inefficiente, specialmente su tabelle di grandi dimensioni, poiché SQL Server non può davvero fare altro che eseguire una scansione completa della tabella (scansione dell'indice in cluster).

A volte, è abbastanza buono per conoscere un numero approssimativo di righe dal database, e in tal caso, una dichiarazione come questa potrebbe essere sufficiente:

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 

Ciò ispezionare la visualizzazione di gestione dinamica ed estrarre il numero di righe e la dimensione della tabella da essa, data una tabella specifica. Lo fa sommando le voci per l'heap (index_id = 0) o l'indice cluster (index_id = 1).

È veloce, è facile da usare, ma non è garantito che sia accurato al 100% o aggiornato. Ma in molti casi, questo è "abbastanza buono" (e mette molto meno peso sul server).

Forse potrebbe funzionare anche per te? Ovviamente, per utilizzarlo in EF, è necessario ricomporlo in un proc memorizzato o utilizzare una chiamata "Esegui query SQL" diretta.

Marc

+1

Non sarà una tabella completa scansione a causa del riferimento FK nel WHERE. Verranno scansionati solo i dettagli del master. Il problema di prestazioni che stava riscontrando era dal caricamento dei dati di blob, non dal conteggio dei record. Presumendo che non ci siano in genere decine di migliaia + di record di dettaglio per master record, non "ottimizzare" qualcosa che non sia effettivamente lento. –

+0

OK, sì, in tal caso, selezionerai solo un sottoinsieme, che dovrebbe andare bene. Per quanto riguarda i dati blob, ho avuto l'impressione che fosse possibile impostare un "caricamento differito" su qualsiasi colonna in una qualsiasi delle tabelle EF per evitare di caricarlo, in modo che potesse essere di aiuto. –

+0

Esiste un modo per utilizzare questo SQL con EntityFramework? Ad ogni modo, in questo caso, avevo solo bisogno di sapere che c'erano delle righe corrispondenti, ma ho intenzionalmente posto la domanda più in generale. – NVRAM

3

Utilizzare il ExecuteStoreQuery metodo del contesto entità. Ciò evita il download dell'intero set di risultati e la deserializzazione in oggetti per eseguire un semplice conteggio delle righe.

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

Se scrivi 'int count = context.MyTable.Count (m => m.MyContainerID == '1')' allora l'SQL generato sarà esattamente quello che stai facendo, ma il codice è molto più bello. Nessuna entità viene caricata in memoria come tale. Provalo in LINQPad se vuoi - ti mostrerà l'SQL usato sotto le copertine. –

+0

SQL in linea. . non è la mia cosa preferita – Duanne

14

A quanto ho capito, la risposta selezionata carica ancora tutti i test correlati. Secondo questo blog di msdn, c'è un modo migliore.

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

particolare

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

Non è necessario effettuare la richiesta extra 'Trova (1)'. Basta creare l'entità e collegarla al contesto: 'var princess = new PrincessEntity {Id = 1}; context.Princesses.Attach (principessa); ' – tenbits

8

Questo è il mio codice:

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

Assicurarsi che la variabile è definita come IQueryable poi, quando si utilizza il metodo Count(), EF eseguirà qualcosa like

select count(*) from ... 

Altrimenti, se i record sono definiti come IEnumerable, sql generati interrogherà l'intera tabella e conteggi le righe restituite.

Problemi correlati