Jak wyczyścić śledzone jednostki w ramach encji

84

Uruchamiam kod korekcyjny, który działa na dużym stosie jednostek, ponieważ jego prędkość maleje, to znaczy, ponieważ liczba śledzonych obiektów w kontekście rośnie z każdą iteracją, może to zająć dużo czasu, więc zapisuję zmiany na końcu każdej iteracji. Każda iteracja jest niezależna i nie zmienia wcześniej załadowanych jednostek.

Wiem, że mogę wyłączyć śledzenie zmian, ale nie chcę, ponieważ nie jest to kod wstawiania zbiorczego, ale ładowanie encji i obliczanie kilku rzeczy i jeśli liczby są nieprawidłowe ustaw nowe liczby i zaktualizuj / usuń / utwórz kilka dodatkowych podmiotów. Wiem, że mogę utworzyć nowy DbContext dla każdej iteracji i prawdopodobnie działałoby to szybciej niż robienie wszystkiego w tej samej instancji, ale myślę, że może być lepszy sposób.

Więc pytanie brzmi; Czy istnieje sposób na wyczyszczenie jednostek załadowanych wcześniej w kontekście bazy danych?

hazimdikenli
źródło
7
Możesz po prostu zadzwonić context.Entry(entity).State = EntityState.Detachedi przestanie śledzić tę konkretną jednostkę.
Ben Robinson,
2
Dlaczego po prostu nie utworzysz nowego kontekstu? Naprawdę nie ma dużego narzutu, chyba że potrzebujesz bardzo zoptymalizowanego kodu.
Adrian Nasui,
Struktura jednostek trafia na serwer bazy danych tylko dla zmienionych jednostek, nie masz żadnych obaw dotyczących wydajności. ale możesz utworzyć nowy kontekst składający się tylko z tabel, z którymi pracujesz, aby przyspieszyć.
İsmet Alkan,
1
@IsThatWięc wykrycie zmian zajmuje trochę czasu, nie martwię się o DbPerformance.
hazimdikenli
czy faktycznie debugowałeś i śledziłeś wąskie gardło wydajności, czy po prostu to założyłeś?
İsmet Alkan

Odpowiedzi:

120

Możesz dodać metodę do swojej DbContextlub metody rozszerzenia, która używa ChangeTracker do odłączenia wszystkich jednostek Added, Modified i Deleted:

public void DetachAllEntities()
{
    var changedEntriesCopy = this.ChangeTracker.Entries()
        .Where(e => e.State == EntityState.Added ||
                    e.State == EntityState.Modified ||
                    e.State == EntityState.Deleted)
        .ToList();

    foreach (var entry in changedEntriesCopy)
        entry.State = EntityState.Detached;
}
David Sherret
źródło
5
Pamiętaj, aby wywołać „ToList” po „Gdzie”. W przeciwnym razie zgłasza System.InvalidOperationException: „Kolekcja została zmodyfikowana; operacja wyliczenia może nie zostać wykonana. '
mabead
6
W moim teście jednostkowym stan wpisów to „Niezmodyfikowany”, może to oznaczać, że używam transakcji, którą wycofuję pod koniec metody testowej. Oznaczało to, że musiałem ustawić stan śledzonych wpisów na „Odłączony” bez sprawdzania bieżącego stanu, aby moje testy działały poprawnie od razu. Wywołuję powyższy kod zaraz po wycofaniu transakcji, ale go otrzymałem, wycofanie z pewnością oznacza stan niezmodyfikowany.
barbara.post
2
(a także var entitypowinno być var entrytak, jak jest to wpis, a nie rzeczywisty Podmiot)
oatsoda Sierpnia
2
@DavidSherret Pomyślałem, że tak może być! Złapałem to, ponieważ w mojej aplikacji testowej przechodzenie przez 1000 elementów i oznaczanie jako Odłączony zajęło około 6000 ms z istniejącym kodem. Około 15ms z nowym :)
owsoda 28.08.18
3
Czy nie powinieneś również używać e.State == EntityState.Unchanged? Chociaż jednostka jest niezmieniona, nadal jest śledzona w kontekście i jest częścią zestawu jednostek, które są uwzględniane podczas DetectChanges. Np. Dodajesz nową jednostkę (jest w stanie Dodane), wywołujesz SaveChanges i dodana jednostka ma teraz stan Niezmieniony (jest to sprzeczne ze wzorcem UnitOfWork, ale op zapytał: Zapisuję zmiany na końcu każdej iteracji ).
jahav
28

1. Możliwość: wypięcie wjazdu

dbContext.Entry(entity).State = EntityState.Detached;

Po odłączeniu wpisu moduł śledzenia zmian przestanie go śledzić (i powinien skutkować lepszą wydajnością)

Zobacz: http://msdn.microsoft.com/de-de/library/system.data.entitystate(v=vs.110).aspx

2. Możliwość: praca z własnym Statuspolem + rozłączonymi kontekstami

Może chcesz niezależnie kontrolować status swojej encji, aby móc używać odłączonych wykresów. Dodaj właściwość dla statusu jednostki i przekształć ten status w dbContext.Entry(entity).Statepodczas wykonywania operacji (użyj do tego repozytorium)

public class Foo
{
    public EntityStatus EntityStatus { get; set; }
}

public enum EntityStatus
{
    Unmodified,
    Modified,
    Added
}

Zobacz poniższy link, aby zapoznać się z przykładem: https://www.safaribooksonline.com/library/view/programming-entity-framework/9781449331825/ch04s06.html

droga242
źródło
Myślę, że dodanie metody rozszerzenia i uruchomienie wszystkich encji w ChangeTracker i odłączenie ich powinno działać.
hazimdikenli
15

Używam usługi systemu Windows, która aktualizuje wartości co minutę i miałem ten sam problem. Próbowałem uruchomić rozwiązanie @DavidSherrets, ale po kilku godzinach to również zwolniło. Moim rozwiązaniem było po prostu utworzenie nowego kontekstu, takiego jak ten dla każdego nowego przebiegu. Proste, ale działa.

_dbContext = new DbContext();

Ogglas
źródło
5
To nie jest „proste, ale działa” rozwiązanie dla Twojego celu. To jedyna poprawna. Kontekst powinien żyć jak najmniej, 1 kontekst na 1 transakcję to najlepsza opcja.
Jegor Androsov
2
Uzgodniony z @pwrigshihanomoronimo, kontekst jest zgodny ze wzorcem projektowym UnitOfWork. Zgodnie z definicją Martina Fowlera:> Utrzymuje listę obiektów, na które ma wpływ transakcja biznesowa i> koordynuje wypisywanie zmian i rozwiązywanie współbieżności> problemów.
Michel
Wydawało się, że to mi wystarczy. Synchronizuję dane, mając około połowę miliona transakcji (wstawianie i aktualizowanie) w tabelach z kilkoma milionami wierszy. Więc po pewnym czasie (lub kilku operacjach) zmagałem się z OutOfMemoryException. Zostało to rozwiązane, gdy utworzyłem nowy DbContext na X liczby pętli, co było naturalnym miejscem do ponownego wystąpienia kontekstu. To prawdopodobnie wyzwoliło GC w lepszy sposób, bez konieczności myślenia o możliwym przecieku pamięci w EF i długotrwałych operacjach. Dziękuję Ci!
Mats Magnem
nie jestem pewien, czy to zadziała z zastrzykiem zależności
Sabrina Leggett
@SabrinaLeggett Powinno działać bez względu na to, skąd pochodzi kontekst
Ogglas,
4

Właśnie natknąłem się na ten problem i ostatecznie natknąłem się na lepsze rozwiązanie dla osób korzystających z typowego wstrzykiwania zależności .NET Core. Dla każdej operacji można użyć zakresu DbContext. To zresetuje się, DbContext.ChangeTrackeraby SaveChangesAsync()nie ugrzęznąć w sprawdzaniu jednostek z poprzednich iteracji. Oto przykładowa metoda kontrolera ASP.NET Core:

    /// <summary>
    /// An endpoint that processes a batch of records.
    /// </summary>
    /// <param name="provider">The service provider to create scoped DbContexts.
    /// This is injected by DI per the FromServices attribute.</param>
    /// <param name="records">The batch of records.</param>
    public async Task<IActionResult> PostRecords(
        [FromServices] IServiceProvider provider,
        Record[] records)
    {
        // The service scope factory is used to create a scope per iteration
        var serviceScopeFactory =
            provider.GetRequiredService<IServiceScopeFactory>();

        foreach (var record in records)
        {
            // At the end of the using block, scope.Dispose() will be called,
            // release the DbContext so it can be disposed/reset
            using (var scope = serviceScopeFactory.CreateScope())
            {
                var context = scope.ServiceProvider.GetService<MainDbContext>();

                // Query and modify database records as needed

                await context.SaveChangesAsync();
            }
        }

        return Ok();
    }

Biorąc pod uwagę, że projekty ASP.NET Core zwykle używają DbContextPool, nie powoduje to nawet tworzenia / niszczenia obiektów DbContext. (Na wypadek, gdybyś był zainteresowany, DbContextPool faktycznie wywołuje DbContext.ResetState()i DbContext.Resurrect(), ale nie polecałbym wywoływać ich bezpośrednio z twojego kodu, ponieważ prawdopodobnie zmienią się w przyszłych wersjach.) Https://github.com/aspnet/EntityFrameworkCore/blob/v2 .2.1 / src / EFCore / Internal / DbContextPool.cs # L157

Matt
źródło
1

Z EF Core 3,0 istnieje wewnętrzny interfejs API, który może zresetować ChangeTracker. Nie używaj tego w kodzie produkcyjnym, wspominam o tym, ponieważ może to pomóc komuś w testowaniu w zależności od scenariusza.

using Microsoft.EntityFrameworkCore.Internal;

_context.GetDependencies().StateManager.ResetState();

Jak mówi komentarz do kodu;

Jest to wewnętrzny interfejs API, który obsługuje infrastrukturę Entity Framework Core i nie podlega tym samym standardom zgodności, co publiczne interfejsy API. Może zostać zmieniony lub usunięty bez uprzedzenia w każdym wydaniu. Należy go używać bezpośrednio w kodzie, zachowując szczególną ostrożność i wiedząc, że może to spowodować awarie aplikacji podczas aktualizacji do nowej wersji Entity Framework Core.

Stuart Hallows
źródło
-2

Cóż, moim zdaniem, z mojego doświadczenia wynika, że ​​EF lub bycie jakimkolwiek orm nie działa dobrze pod zbyt dużą presją lub złożonym modelem.

Jeśli nie chcesz śledzić, naprawdę powiedziałbym, dlaczego w ogóle robisz orm?

Jeśli główną siłą jest szybkość, nic nie przebije procedur składowanych i dobrego indeksowania.

A poza tym, jeśli twoje zapytania są zawsze według identyfikatora, rozważ użycie nosql lub sql z tylko kluczem i json. Pozwoliłoby to uniknąć problemu impedancji między klasami i tabelami.

W twoim przypadku ładowanie rzeczy w obiektach w ten sposób wydaje mi się bardzo powolne. Naprawdę w twoim przypadku procedury składowane są lepsze, ponieważ unikasz przesyłania danych przez sieć, a sql jest znacznie szybszy i zoptymalizowany pod kątem zarządzania agregacją i tym podobnymi.

Kat Lim Ruiz
źródło