Ich würde es zuerst testen, um sicher zu sein. Die Leistung muss nicht so schlecht sein.
Wenn Sie alle Zeilen in einer Transaktion eingeben müssen, rufen Sie sie nach der gesamten AddToClassName-Klasse auf. Wenn Zeilen unabhängig eingegeben werden können, speichern Sie die Änderungen nach jeder Zeile. Die Datenbankkonsistenz ist wichtig.
Zweite Option mag ich nicht. Es wäre für mich (aus Sicht des Endbenutzers) verwirrend, wenn ich in das System importieren würde, und es würde 10 von 1000 Zeilen ablehnen, nur weil 1 schlecht ist. Sie können versuchen, 10 zu importieren. Wenn dies fehlschlägt, versuchen Sie es nacheinander und melden Sie sich dann an.
Testen Sie, ob es lange dauert. Schreiben Sie nicht "wahrscheinlich". Du weißt es noch nicht. Denken Sie nur dann an eine andere Lösung (marc_s), wenn es sich tatsächlich um ein Problem handelt.
BEARBEITEN
Ich habe einige Tests durchgeführt (Zeit in Millisekunden):
10000 Zeilen:
SaveChanges () nach 1 Zeile: 18510.534
SaveChanges () nach 100 Zeilen: 4350,3075
SaveChanges () nach 10000 Zeilen: 5233,0635
50000 Zeilen:
SaveChanges () nach 1 Zeile: 78496.929
SaveChanges () nach 500 Zeilen: 22302,2835
SaveChanges () nach 50000 Zeilen: 24022,8765
Es ist also tatsächlich schneller, nach n Zeilen festzuschreiben als nach allem.
Meine Empfehlung lautet:
- SaveChanges () nach n Zeilen.
- Wenn ein Commit fehlschlägt, versuchen Sie es nacheinander, um eine fehlerhafte Zeile zu finden.
Testklassen:
TABELLE:
CREATE TABLE [dbo].[TestTable](
[ID] [int] IDENTITY(1,1) NOT NULL,
[SomeInt] [int] NOT NULL,
[SomeVarchar] [varchar](100) NOT NULL,
[SomeOtherVarchar] [varchar](50) NOT NULL,
[SomeOtherInt] [int] NULL,
CONSTRAINT [PkTestTable] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Klasse:
public class TestController : Controller
{
private readonly Random _rng = new Random();
private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private string RandomString(int size)
{
var randomSize = _rng.Next(size);
char[] buffer = new char[randomSize];
for (int i = 0; i < randomSize; i++)
{
buffer[i] = _chars[_rng.Next(_chars.Length)];
}
return new string(buffer);
}
public ActionResult EFPerformance()
{
string result = "";
TruncateTable();
result = result + "SaveChanges() after 1 row:" + EFPerformanceTest(10000, 1).TotalMilliseconds + "<br/>";
TruncateTable();
result = result + "SaveChanges() after 100 rows:" + EFPerformanceTest(10000, 100).TotalMilliseconds + "<br/>";
TruncateTable();
result = result + "SaveChanges() after 10000 rows:" + EFPerformanceTest(10000, 10000).TotalMilliseconds + "<br/>";
TruncateTable();
result = result + "SaveChanges() after 1 row:" + EFPerformanceTest(50000, 1).TotalMilliseconds + "<br/>";
TruncateTable();
result = result + "SaveChanges() after 500 rows:" + EFPerformanceTest(50000, 500).TotalMilliseconds + "<br/>";
TruncateTable();
result = result + "SaveChanges() after 50000 rows:" + EFPerformanceTest(50000, 50000).TotalMilliseconds + "<br/>";
TruncateTable();
return Content(result);
}
private void TruncateTable()
{
using (var context = new CamelTrapEntities())
{
var connection = ((EntityConnection)context.Connection).StoreConnection;
connection.Open();
var command = connection.CreateCommand();
command.CommandText = @"TRUNCATE TABLE TestTable";
command.ExecuteNonQuery();
}
}
private TimeSpan EFPerformanceTest(int noOfRows, int commitAfterRows)
{
var startDate = DateTime.Now;
using (var context = new CamelTrapEntities())
{
for (int i = 1; i <= noOfRows; ++i)
{
var testItem = new TestTable();
testItem.SomeVarchar = RandomString(100);
testItem.SomeOtherVarchar = RandomString(50);
testItem.SomeInt = _rng.Next(10000);
testItem.SomeOtherInt = _rng.Next(200000);
context.AddToTestTable(testItem);
if (i % commitAfterRows == 0) context.SaveChanges();
}
}
var endDate = DateTime.Now;
return endDate.Subtract(startDate);
}
}
Ich habe gerade ein sehr ähnliches Problem in meinem eigenen Code optimiert und möchte auf eine Optimierung hinweisen, die für mich funktioniert hat.
Ich habe festgestellt, dass die meiste Zeit bei der Verarbeitung von SaveChanges, unabhängig davon, ob 100 oder 1000 Datensätze gleichzeitig verarbeitet werden, an die CPU gebunden ist. Durch die Verarbeitung der Kontexte mit einem Producer / Consumer-Muster (implementiert mit BlockingCollection) konnte ich CPU-Kerne viel besser nutzen und kam von insgesamt 4000 Änderungen / Sekunde (wie durch den Rückgabewert von SaveChanges angegeben) auf über 14.000 Änderungen / Sekunde. Die CPU-Auslastung stieg von ca. 13% (ich habe 8 Kerne) auf ca. 60%. Selbst bei Verwendung mehrerer Consumer-Threads habe ich das (sehr schnelle) Festplatten-E / A-System kaum besteuert, und die CPU-Auslastung von SQL Server betrug nicht mehr als 15%.
Durch das Auslagern der Speicherung auf mehrere Threads können Sie sowohl die Anzahl der Datensätze vor dem Festschreiben als auch die Anzahl der Threads, die die Festschreibungsvorgänge ausführen, optimieren.
Ich habe festgestellt, dass ich durch das Erstellen von 1 Producer-Thread und (Anzahl der CPU-Kerne) -1 Consumer-Threads die Anzahl der pro Stapel festgeschriebenen Datensätze so einstellen konnte, dass die Anzahl der Elemente in der BlockingCollection zwischen 0 und 1 schwankte (nachdem ein Consumer-Thread einen genommen hatte Artikel). Auf diese Weise gab es gerade genug Arbeit, damit die konsumierenden Threads optimal funktionieren.
In diesem Szenario muss natürlich für jeden Stapel ein neuer Kontext erstellt werden, der selbst in einem Single-Thread-Szenario für meinen Anwendungsfall schneller ist.
quelle
Wenn Sie Tausende von Datensätzen importieren müssen, würde ich so etwas wie SqlBulkCopy verwenden und nicht das Entity Framework dafür.
quelle
Verwenden Sie eine gespeicherte Prozedur.
Ich glaube, dies wäre der einfachste und schnellste Weg, dies zu tun.
quelle
Entschuldigung, ich weiß, dass dieser Thread alt ist, aber ich denke, dies könnte anderen Menschen bei diesem Problem helfen.
Ich hatte das gleiche Problem, aber es besteht die Möglichkeit, die Änderungen zu überprüfen, bevor Sie sie festschreiben. Mein Code sieht so aus und funktioniert einwandfrei. Mit dem
chUser.LastUpdated
überprüfe ich, ob es sich um einen neuen Eintrag oder nur um eine Änderung handelt. Weil es nicht möglich ist, einen Eintrag neu zu laden, der sich noch nicht in der Datenbank befindet.// Validate Changes var invalidChanges = _userDatabase.GetValidationErrors(); foreach (var ch in invalidChanges) { // Delete invalid User or Change var chUser = (db_User) ch.Entry.Entity; if (chUser.LastUpdated == null) { // Invalid, new User _userDatabase.db_User.Remove(chUser); Console.WriteLine("!Failed to create User: " + chUser.ContactUniqKey); } else { // Invalid Change of an Entry _userDatabase.Entry(chUser).Reload(); Console.WriteLine("!Failed to update User: " + chUser.ContactUniqKey); } } _userDatabase.SaveChanges();
quelle
saveChanges()
diejenigen löschen, die einen Fehler verursachen würden.SaveChanges
Aufruf effizient festgeschrieben werden sollen. Sie sprechen dieses Problem nicht an. Beachten Sie, dass es mehr mögliche Gründe für das Fehlschlagen von SaveChanges gibt als Validierungsfehler. Übrigens können Sie Entitäten auch einfach als neu markierenUnchanged
/ löschen.SaveChanges
fehlschlagen. Und das löst das Problem. Wenn dieser Beitrag Sie in diesem Thread wirklich stört, kann ich dies löschen, mein Problem ist gelöst, ich versuche nur, anderen zu helfen.GetValidationErrors()
, "fälscht" es einen Aufruf der Datenbank und ruft Fehler ab oder was? Vielen Dank für