Entity Framework C #: problema di memoria di input delle estensioni di massa

Aug 17 2020

Attualmente sto utilizzando EF Extensions. Una cosa che non capisco, "dovrebbe aiutare con le prestazioni"

tuttavia inserire un milione di record nella variabile List è di per sé un problema di memoria. Quindi, se si desidera aggiornare milioni di record, senza tenere tutto in memoria, come è possibile farlo in modo efficiente?

Dovremmo usare a for loope aggiornare in batch diciamo 10.000? EFExtensions BulkUpdate dispone di funzionalità native per supportarlo?

Esempio:

var productUpdate = _dbContext.Set<Product>()
    .Where(x => x.ProductType == 'Electronics');  // this creates IQueryable

await productUpdate.ForEachAsync(c => c.ProductBrand = 'ABC Company');

_dbContext.BulkUpdateAsync(productUpdate.ToList());

Risorsa:

https://entityframework-extensions.net/bulk-update

Risposte

Pac0 Aug 18 2020 at 00:27

Ho trovato il modo "corretto" per le estensioni EF di eseguire un aggiornamento collettivo con una condizione simile a una query:

var productUpdate = _dbContext.Set<Product>()
    .Where(x => x.ProductType == 'Electronics')
    .UpdateFromQuery( x => new Product { ProductBrand = "ABC Company" });

Ciò dovrebbe risultare in un SQL corretto UPDATE ... SET ... WHERE, senza la necessità di caricare prima le entità, come da documentazione :

Perché UpdateFromQueryè più veloce di SaveChanges, BulkSaveChangese BulkUpdate?

UpdateFromQueryesegue un'istruzione direttamente in SQL come UPDATE [TableName] SET [SetColumnsAndValues] WHERE [Key].

Altre operazioni normalmente richiedono uno o più round trip del database, il che rallenta le prestazioni.

Puoi controllare la sintassi di lavoro su questo esempio di violino dotnet , adattato dal loro esempio di BulkUpdate.

Altre considerazioni

  • Nessuna menzione di operazioni batch per questo, purtroppo.

  • Prima di fare un grande aggiornamento come questo, potrebbe valere la pena considerare di disattivare gli indici che potresti avere su questa colonna e ricostruirli in seguito. Ciò è particolarmente utile se ne hai molti.

  • Attento alla condizione in Where, se non può essere tradotto come SQL da EF, allora sarà fatto lato client, ovvero il "solito" terribile roundtrip "Carica - modifica in memoria - aggiorna"

2 Flater Aug 17 2020 at 23:43

Questo è in realtà qualcosa per cui EF non è fatto. Le interazioni del database di EF iniziano dall'oggetto record e fluiscono da lì. EF non può generare un AGGIORNAMENTO parziale (cioè non sovrascrivendo tutto) se l'entità non è stata modificata (e quindi caricata), e allo stesso modo non può CANCELLARE i record in base a una condizione invece che a una chiave.

Non esiste un equivalente EF (senza caricare tutti quei record) per la logica di aggiornamento / eliminazione condizionale come

UPDATE People
SET FirstName = 'Bob'
WHERE FirstName = 'Robert'

o

DELETE FROM People
WHERE FirstName = 'Robert'

Fare ciò utilizzando l'approccio EF ti richiederà di caricare tutte queste entità solo per rispedirle (con un aggiornamento o una cancellazione) al database, e questo è uno spreco di larghezza di banda e prestazioni come hai già scoperto.

La soluzione migliore che ho trovato qui è bypassare i metodi compatibili con LINQ di EF e invece eseguire tu stesso l'SQL grezzo. Questa operazione può ancora essere eseguita utilizzando un contesto EF.

using (var ctx = new MyContext())
{
    string updateCommand = "UPDATE People SET FirstName = 'Bob' WHERE FirstName = 'Robert'";
    int noOfRowsUpdated = ctx.Database.ExecuteSqlCommand(updateCommand);

    string deleteCommand = "DELETE FROM People WHERE FirstName = 'Robert'";
    int noOfRowsDeleted = ctx.Database.ExecuteSqlCommand(deleteCommand);
}

Maggiori informazioni qui . Ovviamente non dimenticare di proteggerti dall'iniezione SQL, se pertinente.

La sintassi specifica per eseguire SQL raw può variare in base alla versione di EF / EF Core ma, per quanto ne so, tutte le versioni consentono di eseguire SQL raw.


Non posso commentare le prestazioni di EF Extensions o BulkUpdate in modo specifico e non ho intenzione di acquistarlo da loro.

Sulla base della loro documentazione, non sembrano avere i metodi con le firme giuste per consentire la logica di aggiornamento / eliminazione condizionale.

  • BulkUpdate non sembra permetterti di inserire la condizione logica (il WHERE nel tuo comando UPDATE) che ti permetterebbe di ottimizzarlo.
  • BulkDeleteha ancora BatchSizeun'impostazione, che suggerisce che stanno ancora gestendo i record uno alla volta (beh, per batch immagino) e non utilizzando una singola query DELETE con una condizione (clausola WHERE).

In base al codice previsto nella domanda, EF Extensions non ti dà davvero ciò di cui hai bisogno. È più performante ed economico eseguire semplicemente SQL non elaborato sul database, poiché questo ignora la necessità di EF di caricare le sue entità.

Aggiornamento
Potrei correggo, c'è un po 'di supporto per la logica di aggiornamento condizionale, come si vede qui . Tuttavia, non mi è chiaro mentre l'esempio carica ancora tutto in memoria e qual è lo scopo di quella logica WHERE condizionale se hai già caricato tutto in memoria (perché non usare LINQ in memoria quindi?)

Tuttavia, anche se funziona senza caricare le entità, è comunque:

  • più limitato (sono consentiti solo i controlli di uguaglianza, rispetto a SQL che consente qualsiasi condizione booleana che sia SQL valida),
  • relativamente complesso (non mi piace la loro sintassi, forse è soggettiva)
  • e più costoso (ancora una libreria a pagamento)

rispetto al rotolare la tua query SQL non elaborata. Vorrei comunque suggerire di rotolare il tuo SQL grezzo qui, ma questa è solo la mia opinione.