Najpierw przetestowałbym to, aby mieć pewność. Wydajność nie musi być taka zła.
Jeśli chcesz wprowadzić wszystkie wiersze w jednej transakcji, wywołaj ją po całej klasie AddToClassName. Jeśli wiersze można wprowadzać niezależnie, zapisz zmiany po każdym wierszu. Spójność bazy danych jest ważna.
Druga opcja mi się nie podoba. Byłoby dla mnie mylące (z punktu widzenia użytkownika końcowego), gdybym dokonał importu do systemu i zmniejszyłby się o 10 wierszy na 1000, tylko dlatego, że 1 jest zły. Możesz spróbować zaimportować 10, a jeśli się to nie powiedzie, spróbuj pojedynczo, a następnie zaloguj się.
Sprawdź, czy zajmuje to dużo czasu. Nie pisz „prawdopodobnie”. Jeszcze tego nie wiesz. Dopiero gdy jest to rzeczywiście problem, pomyśl o innym rozwiązaniu (marc_s).
EDYTOWAĆ
Zrobiłem kilka testów (czas w milisekundach):
10000 rzędów:
SaveChanges () po 1 wierszu: 18510,534
SaveChanges () after 100 wierszy: 4350,3075
SaveChanges () after 10000 wierszy: 5233,0635
50000 rzędów:
SaveChanges () after 1 row: 78496,929
SaveChanges () after 500 wierszy: 22302,2835
SaveChanges () after 50000 wierszy: 24022,8765
Tak więc zatwierdzenie po n wierszach jest w rzeczywistości szybsze niż w końcu.
Polecam:
- SaveChanges () po n wierszach.
- Jeśli jedno zatwierdzenie nie powiedzie się, wypróbuj je jeden po drugim, aby znaleźć wadliwy wiersz.
Klasy testowe:
STÓŁ:
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]
Klasa:
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);
}
}
Właśnie zoptymalizowałem bardzo podobny problem we własnym kodzie i chciałbym wskazać optymalizację, która zadziałała dla mnie.
Zauważyłem, że większość czasu podczas przetwarzania SaveChanges, niezależnie od tego, czy przetwarzamy 100 czy 1000 rekordów jednocześnie, jest związana z procesorem. Tak więc, przetwarzając konteksty za pomocą wzorca producenta / konsumenta (zaimplementowanego za pomocą BlockingCollection), byłem w stanie znacznie lepiej wykorzystać rdzenie procesora i uzyskałem z łącznie 4000 zmian na sekundę (zgodnie z wartością zwracaną SaveChanges) do ponad 14 000 zmian na sekundę. Wykorzystanie procesora zmieniło się z około 13% (mam 8 rdzeni) do około 60%. Nawet używając wielu wątków konsumenckich, ledwo opodatkowałem (bardzo szybki) system IO dysku, a wykorzystanie procesora SQL Server nie było wyższe niż 15%.
Odciążając zapisywanie do wielu wątków, masz możliwość dostrojenia zarówno liczby rekordów przed zatwierdzeniem, jak i liczby wątków wykonujących operacje zatwierdzania.
Okazało się, że utworzenie 1 wątku producenta i (liczby rdzeni procesora) -1 wątków konsumenckich pozwoliło mi dostroić liczbę rekordów zatwierdzonych na partię tak, że liczba elementów w BlockingCollection wahała się między 0 a 1 (po tym, jak wątek konsumencki wziął jeden pozycja). W ten sposób było wystarczająco dużo pracy, aby zużywające wątki działały optymalnie.
Ten scenariusz wymaga oczywiście stworzenia nowego kontekstu dla każdej partii, co jest szybsze nawet w przypadku scenariusza jednowątkowego dla mojego przypadku użycia.
źródło
Jeśli chcesz zaimportować tysiące rekordów, użyłbym czegoś takiego jak SqlBulkCopy, a nie Entity Framework.
źródło
Użyj procedury składowanej.
Uważam, że byłby to najłatwiejszy i najszybszy sposób na zrobienie tego.
źródło
Przepraszam, wiem, że ten wątek jest stary, ale myślę, że może to pomóc innym osobom z tym problemem.
Miałem ten sam problem, ale istnieje możliwość sprawdzenia poprawności zmian przed ich zatwierdzeniem. Mój kod wygląda tak i działa dobrze. Przy pomocy
chUser.LastUpdated
sprawdzam czy to nowy wpis czy tylko zmiana. Ponieważ nie jest możliwe ponowne załadowanie wpisu, którego nie ma jeszcze w bazie danych.// 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();
źródło
saveChanges()
możesz usunąć te, które spowodowałyby błąd.SaveChanges
wywołaniu. Nie rozwiązujesz tego problemu. Zauważ, że istnieje więcej potencjalnych przyczyn niepowodzenia SaveChanges niż błędów walidacji. Nawiasem mówiąc, możesz po prostu oznaczyć elementy jakoUnchanged
zamiast ich ponownego wczytywania / usuwania.SaveChanges
niepowodzenia. I to rozwiązuje problem. Jeśli ten post naprawdę Ci przeszkadza w tym wątku mogę to usunąć, mój problem został rozwiązany, po prostu próbuję pomóc innym.GetValidationErrors()
czy to „fałszywe” wywołanie bazy danych i wyszukuje błędy, czy co? Dzięki za odpowiedź :)