Come COUNT righe all'interno di EntityFramework senza caricare i contenuti?


109

Sto cercando di determinare come contare le righe corrispondenti su una tabella utilizzando EntityFramework.

Il problema è che ogni riga potrebbe contenere molti megabyte di dati (in un campo binario). Ovviamente l'SQL sarebbe qualcosa del genere:

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

Potrei caricare tutte le righe e poi trovare il conteggio con:

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

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


EDIT: Grazie a tutti. Ho spostato il DB da un allegato privato in modo da poter eseguire la profilazione; questo aiuta ma provoca confusioni che non mi aspettavo.

E i miei dati reali sono un po 'più approfonditi, userò camion che trasportano pallet di casse di articoli e non voglio che il camion se ne vada a meno che non ci sia almeno un oggetto .

I miei tentativi sono mostrati di seguito. La parte che non capisco è 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 l'SQL risultante da CASE_1 viene reindirizzato tramite 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

[ Non ho realmente camion, autisti, pallet, valigie o articoli; come puoi vedere dall'SQL, le relazioni tra camion e pallet e pallet-cassa sono molti a molti, anche se non credo che importi. I miei oggetti reali sono intangibili e più difficili da descrivere, quindi ho cambiato i nomi. ]


1
come hai risolto il problema del caricamento dei pallet?
Sherlock

Risposte:


123

Sintassi della 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.


Perché il SelectMany()? È necessario? Non funzionerebbe correttamente senza di essa?
Jo Smo

@JoSmo, no, questa è una domanda completamente diversa.
Craig Stuntz

Grazie per avermelo chiarito. Volevo solo essere sicuro. :)
Jo Smo

1
Puoi dirmi perché è diverso con SelectMany? Non capisco. Lo faccio senza SelectMany ma diventa molto lento perché ho oltre 20 milioni di record. Ho provato la risposta di Yang Zhang e funziona alla grande, volevo solo sapere cosa fa SelectMany.
mikesoft

1
@AustinFelipe Senza la chiamata a SelectMany, la query restituirebbe il numero di righe in MyContainer con ID uguale a "1". La chiamata SelectMany restituisce tutte le righe in MyTable che appartengono al risultato precedente della query (ovvero il risultato di MyContainer.Where(o => o.ID == '1'))
sbecker

48

Penso che tu voglia qualcosa di simile

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

(modificato per riflettere i commenti)


1
No, ha bisogno del conteggio delle entità in MyTable referenziate dall'unica entità con ID = 1 in MyContainer
Craig Stuntz

3
Per inciso, se t.ID è un PK, il conteggio nel codice sopra sarà sempre 1. :)
Craig Stuntz

2
@ Craig, hai ragione, avrei dovuto usare t.ForeignTable.ID. Aggiornato.
Kevin,

1
Bene, questo è breve e semplice. La mia scelta è: var count = context.MyTable.Count(t => t.MyContainer.ID == '1'); non lunga e brutta: var count = (from o in context.MyContainer where o.ID == '1' from t in o.MyTable select t).Count(); ma dipende dallo stile di codifica ...
CL

assicurati di includere "using System.Linq", altrimenti non funzionerà
CountMurphy

16

A quanto ho capito, la risposta selezionata carica ancora tutti i test correlati. Secondo questo blog 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

In 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 fare Find(1)richieste aggiuntive . Basta creare l'entità e allegare al contesto:var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
tenbits

13

Questo è il mio codice:

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

Assicurati che la variabile sia definita come IQueryable, quindi quando usi il metodo Count (), EF eseguirà qualcosa di simile

select count(*) from ...

In caso contrario, se i record sono definiti come IEnumerable, l'SQL generato interrogherà l'intera tabella e conterà le righe restituite.


10

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

A volte è sufficiente conoscere un numero approssimativo di righe dal database e, in tal caso, un'istruzione 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

Questo ispezionerà la vista di gestione dinamica ed estrarrà 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 o aggiornato al 100%. Ma in molti casi, questo è "abbastanza buono" (e mette molto meno onere sul server).

Forse funzionerebbe anche per te? Ovviamente, per usarlo in EF, dovresti racchiuderlo in una procedura memorizzata o utilizzare una chiamata diretta "Esegui query SQL".

Marc


1
Non sarà una scansione completa della tabella a causa del riferimento FK in WHERE. Verranno scansionati solo i dettagli del master. Il problema di prestazioni che stava riscontrando era dovuto al caricamento dei dati del BLOB, non al conteggio dei record. Presumendo che in genere non ci siano decine di migliaia + di record di dettaglio per record principale, non "ottimizzerei" qualcosa che non sia effettivamente lento.
Craig Stuntz

OK, sì, in tal caso, selezionerai solo un sottoinsieme - dovrebbe andare bene. Per quanto riguarda i dati del blob, avevo l'impressione che potessi impostare un "caricamento differito" su qualsiasi colonna in una qualsiasi delle tue tabelle EF per evitare di caricarlo, quindi potrebbe aiutare.
marc_s

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

4

Utilizza il metodo ExecuteStoreQuery del contesto dell'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')l'SQL generato sarà esattamente quello che stai facendo, ma il codice è molto più carino. Nessuna entità viene caricata in memoria in quanto tale. Provalo in LINQPad, se lo desideri: ti mostrerà l'SQL usato sotto le coperte.
Drew Noakes il

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

3

Penso che 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();

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

Se MyContainer è il genitore e MyTable sono i figli nella relazione, allora dovevi stabilire quella relazione con una chiave esterna, non sono sicuro in quale altro modo potresti sapere quali entità MyTable sono associate a un'entità MyContainer ... Ma forse io fatto
un'ipotesi
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.