Questo è un seguito da una domanda precedente , in cui stavo cercando di capire la causa principale del mio codice in esecuzione lentamente. Penso di averlo ristretto ad un esempio minimale di seguito. Ho una struttura di base del database come segue:
public class Foo
{
public int Id { get; set; }
public string Bar { get; set; }
}
public class FooContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
}
Ora, se avessi una lista di oggetti Foo
, e volessi aggiungerli al database, il modo suggerito sarebbe usare AddRange()
. Ma ho notato che ci è voluto molto tempo, ed è influenzato negativamente dal numero di elementi nella collezione, anche con una piccola quantità come 200. Così l'ho scritto manualmente, e viola, corre più veloce!
class Program
{
static void Main(string[] args)
{
var foos = Enumerable.Range(0, 200).Select(index => new Foo { Bar = index.ToString() });
// Make sure the timing doesn't include the first connection
using (var context = new FooContext())
{
context.Database.Connection.Open();
}
var s1 = Stopwatch.StartNew();
using (var context = new FooContext())
{
context.Foos.AddRange(foos);
context.SaveChanges();
}
s1.Stop();
var s2 = Stopwatch.StartNew();
using (var context = new FooContext())
{
// Ignore the lack of sanitization, this is for demonstration purposes
var query = string.Join(";\n", foos.Select(f => "INSERT INTO Foos ([Bar]) VALUES (" + f.Bar + ")"));
context.Database.ExecuteSqlCommand(query);
}
s2.Stop();
Console.WriteLine("Normal way: {0}", s1.Elapsed);
Console.WriteLine("Hard way : {0}", s2.Elapsed);
Console.ReadKey();
}
}
Il mio pensiero iniziale era che Entity Framework potesse utilizzare una transazione separata per ogni voce, ma la registrazione di SQL mostra che non è il caso. Quindi, perché c'è una tale differenza nei tempi di esecuzione?
Mentre faccio qualche ricerca sulla tua domanda mi sono imbattuto in questo articolo illuminante: http://www.codinghelmet.com/?path=howto/bulk-insert
Ecco una citazione:
Ogni oggetto che è stato inserito ha richiesto due istruzioni SQL: una per inserire un record e un'altra per ottenere l'identità del nuovo record
Questo diventa un problema quando si inseriscono più record. Un problema che viene intensificato dal fatto che ogni record viene inserito uno alla volta (ma questo è fuori dal contesto della tua domanda dato che stai già testando l'inserto uno per uno). Quindi se stai inserendo 200 record, si tratta di 400 istruzioni sql che vengono eseguite una alla volta.
Quindi dalla mia comprensione EF non è semplicemente costruito per l'inserimento di massa. Anche se è semplice come inserire 200 record. Quale a me sembra una grande delusione.
Ho iniziato a pensare "Allora qual è l'EF buono in ogni caso, non può nemmeno inserire un paio di record". Bene darò oggetti di scena EF in due aree:
Quindi, in poche parole, sembra che se si ha un'operazione che richiede l'inserimento di un gruppo di record, potrebbe essere meglio usare SqlBulkCopy . Quale può inserire migliaia di record in pochi secondi.
So che questa potrebbe non essere la risposta che vuoi sentire, perché credimi mi sconvolge anche perché uso EF molto, ma non vedo alcun modo per aggirarlo
Dal momento che non puoi conviverci e non puoi vivere senza di esso, hai pensato di chiamare SaveChangesAsync ()?
Ho cercato in lungo e in largo di trovare un modo per disattivare la sincronizzazione della chiave primaria, ma non ho trovato nessuno per EF 6 e meno.
Il nucleo EF passa un vero da DBContext.SaveChanges () a ciò che credo possa scatenare questa sincronizzazione. L'altro sovraccarico consente ai chiamanti di passare false come parametro di controllo.