Odłączone jednostki

Wystąpienie DbContext będzie automatycznie śledzić jednostki zwrócone z bazy danych. Zmiany wprowadzone w tych jednostkach zostaną wykryte po wywołaniu funkcji SaveChanges, a baza danych zostanie zaktualizowana zgodnie z potrzebami. Aby uzyskać szczegółowe informacje, zobacz Artykuł Podstawowe zapisywanie i powiązane dane .

Jednak czasami jednostki są odpytywane przy użyciu jednego wystąpienia kontekstu, a następnie zapisywane przy użyciu innego wystąpienia. Dzieje się tak często w scenariuszach "rozłączonych", takich jak aplikacja internetowa, w której są wykonywane zapytania o jednostki, wysyłane do klienta, modyfikowane, wysyłane z powrotem do serwera w żądaniu, a następnie zapisywane. W takim przypadku drugie wystąpienie kontekstu musi wiedzieć, czy jednostki są nowe (należy wstawić) czy istniejące (powinny zostać zaktualizowane).

Napiwek

Przykład z tego artykułu można zobaczyć w witrynie GitHub.

Napiwek

Program EF Core może śledzić tylko jedno wystąpienie dowolnej jednostki z daną wartością klucza podstawowego. Najlepszym sposobem uniknięcia tego problemu jest użycie krótkotrwałego kontekstu dla każdej jednostki pracy, tak aby kontekst był pusty, zawiera dołączone do niego jednostki, zapisuje te jednostki, a następnie kontekst jest usuwany i odrzucany.

Identyfikowanie nowych jednostek

Klient identyfikuje nowe jednostki

Najprostszym przypadkiem do rozwiązania jest to, że klient informuje serwer, czy jednostka jest nowa, czy istniejąca. Na przykład często żądanie wstawienia nowej jednostki różni się od żądania aktualizacji istniejącej jednostki.

W pozostałej części tej sekcji opisano przypadki, w których konieczne jest określenie w inny sposób, czy należy wstawić, czy zaktualizować.

Za pomocą automatycznie generowanych kluczy

Wartość automatycznie wygenerowanego klucza może być często używana do określenia, czy jednostka musi zostać wstawiona, czy zaktualizowana. Jeśli klucz nie został ustawiony (oznacza to, że nadal ma domyślną wartość CLR o wartości null, zero itp.), jednostka musi być nowa i wymaga wstawiania. Z drugiej strony, jeśli została ustawiona wartość klucza, musi ona zostać wcześniej zapisana i teraz wymaga aktualizacji. Innymi słowy, jeśli klucz ma wartość, jednostka została zapytana, wysłana do klienta i teraz wróci do aktualizacji.

Jeśli typ jednostki jest znany, można łatwo sprawdzić, czy klucz nie został zdenerwowany:

public static bool IsItNew(Blog blog)
    => blog.BlogId == 0;

Jednak ef ma również wbudowany sposób, aby to zrobić dla dowolnego typu jednostki i typu klucza:

public static bool IsItNew(DbContext context, object entity)
    => !context.Entry(entity).IsKeySet;

Napiwek

Klucze są ustawiane tak szybko, jak jednostki są śledzone przez kontekst, nawet jeśli jednostka jest w stanie Dodano. Pomaga to w przechodzeniu przez graf jednostek i podejmowaniu decyzji o tym, co należy zrobić z poszczególnymi elementami, na przykład podczas korzystania z interfejsu API TrackGraph. Wartość klucza powinna być używana tylko w sposób pokazany tutaj przed wykonaniem dowolnego wywołania w celu śledzenia jednostki.

Z innymi kluczami

Do identyfikowania nowych jednostek potrzebny jest inny mechanizm, gdy wartości kluczy nie są generowane automatycznie. Istnieją dwa ogólne podejścia do tego:

  • Zapytanie dotyczące jednostki
  • Przekazywanie flagi z klienta

Aby wysłać zapytanie do jednostki, wystarczy użyć metody Find:

public static bool IsItNew(BloggingContext context, Blog blog)
    => context.Blogs.Find(blog.BlogId) == null;

Wykracza poza zakres tego dokumentu, aby pokazać pełny kod przekazywania flagi z klienta. W aplikacji internetowej zwykle oznacza to wykonywanie różnych żądań dla różnych akcji lub przekazywanie stanu w żądaniu, a następnie wyodrębnianie go w kontrolerze.

Zapisywanie pojedynczych jednostek

Jeśli wiadomo, czy jest wymagana wstawienie lub aktualizacja, można użyć odpowiednio opcji Dodaj lub Aktualizuj:

public static void Insert(DbContext context, object entity)
{
    context.Add(entity);
    context.SaveChanges();
}

public static void Update(DbContext context, object entity)
{
    context.Update(entity);
    context.SaveChanges();
}

Jeśli jednak jednostka używa wartości klucza generowanego automatycznie, można użyć metody Update w obu przypadkach:

public static void InsertOrUpdate(DbContext context, object entity)
{
    context.Update(entity);
    context.SaveChanges();
}

Metoda Update zwykle oznacza jednostkę aktualizacji, a nie wstawia. Jeśli jednak jednostka ma automatycznie wygenerowany klucz i nie ustawiono żadnej wartości klucza, jednostka jest zamiast tego automatycznie oznaczona do wstawiania.

Jeśli jednostka nie korzysta z kluczy generowanych automatycznie, aplikacja musi zdecydować, czy jednostka ma zostać wstawiona, czy zaktualizowana: Na przykład:

public static void InsertOrUpdate(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs.Find(blog.BlogId);
    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
    }

    context.SaveChanges();
}

Poniżej przedstawiono następujące kroki:

  • Jeśli funkcja Find zwraca wartość null, baza danych nie zawiera jeszcze bloga o tym identyfikatorze, dlatego wywołujemy metodę Dodaj oznacz ją jako wstawioną.
  • Jeśli funkcja Find zwraca jednostkę, istnieje ona w bazie danych, a kontekst śledzi teraz istniejącą jednostkę
    • Następnie użyjemy polecenia SetValues, aby ustawić wartości dla wszystkich właściwości tej jednostki na te, które pochodzą z klienta.
    • Wywołanie SetValues oznaczy jednostkę do zaktualizowania zgodnie z potrzebami.

Napiwek

Właściwość SetValues będzie oznaczać tylko jako zmodyfikowane właściwości, które mają różne wartości do tych w śledzonej jednostce. Oznacza to, że po wysłaniu aktualizacji zostaną zaktualizowane tylko te kolumny, które rzeczywiście uległy zmianie. (A jeśli nic się nie zmieniło, w ogóle nie zostanie wysłana żadna aktualizacja).

Praca z grafami

Rozpoznawanie tożsamości

Jak wspomniano powyżej, program EF Core może śledzić tylko jedno wystąpienie dowolnej jednostki z daną wartością klucza podstawowego. Podczas pracy z grafami najlepiej utworzyć wykres tak, aby ten niezmienny był utrzymywany, a kontekst powinien być używany tylko do jednej jednostki pracy. Jeśli wykres zawiera duplikaty, konieczne będzie przetworzenie grafu przed wysłaniem go do programu EF w celu skonsolidowania wielu wystąpień w jednym. Może to nie być proste, gdy wystąpienia mają sprzeczne wartości i relacje, więc konsolidacja duplikatów powinna być wykonywana tak szybko, jak to możliwe w potoku aplikacji, aby uniknąć rozwiązywania konfliktów.

Wszystkie nowe/wszystkie istniejące jednostki

Przykładem pracy z grafami jest wstawianie lub aktualizowanie bloga wraz z kolekcją skojarzonych wpisów. Jeśli wszystkie jednostki na grafie powinny zostać wstawione lub wszystkie powinny zostać zaktualizowane, proces jest taki sam jak opisany powyżej dla pojedynczych jednostek. Na przykład wykres blogów i wpisów utworzonych w następujący sposób:

var blog = new Blog
{
    Url = "http://sample.com", Posts = new List<Post> { new Post { Title = "Post 1" }, new Post { Title = "Post 2" }, }
};

można wstawić w następujący sposób:

public static void InsertGraph(DbContext context, object rootEntity)
{
    context.Add(rootEntity);
    context.SaveChanges();
}

Wywołanie polecenia Dodaj spowoduje oznaczenie bloga i wszystkich wpisów do wstawienia.

Podobnie, jeśli wszystkie jednostki w grafie muszą zostać zaktualizowane, można użyć aktualizacji:

public static void UpdateGraph(DbContext context, object rootEntity)
{
    context.Update(rootEntity);
    context.SaveChanges();
}

Blog i wszystkie jego wpisy zostaną oznaczone jako zaktualizowane.

Kombinacja nowych i istniejących jednostek

W przypadku automatycznie generowanych kluczy można ponownie użyć aktualizacji zarówno w przypadku wstawiania, jak i aktualizacji, nawet jeśli wykres zawiera kombinację jednostek wymagających wstawiania i tych, które wymagają aktualizacji:

public static void InsertOrUpdateGraph(DbContext context, object rootEntity)
{
    context.Update(rootEntity);
    context.SaveChanges();
}

Aktualizacja oznaczy dowolną jednostkę na grafie, blogu lub wpisie w celu wstawiania, jeśli nie ma ustawionej wartości klucza, podczas gdy wszystkie inne jednostki są oznaczone do aktualizacji.

Tak jak wcześniej, jeśli nie używasz kluczy generowanych automatycznie, można użyć zapytania i niektórych operacji przetwarzania:

public static void InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs
        .Include(b => b.Posts)
        .FirstOrDefault(b => b.BlogId == blog.BlogId);

    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
        foreach (var post in blog.Posts)
        {
            var existingPost = existingBlog.Posts
                .FirstOrDefault(p => p.PostId == post.PostId);

            if (existingPost == null)
            {
                existingBlog.Posts.Add(post);
            }
            else
            {
                context.Entry(existingPost).CurrentValues.SetValues(post);
            }
        }
    }

    context.SaveChanges();
}

Obsługa usuwania

Usuwanie może być trudne do obsługi, ponieważ często brak jednostki oznacza, że należy go usunąć. Jednym ze sposobów radzenia sobie z tym jest użycie "usuwania nietrwałego", tak aby jednostka została oznaczona jako usunięta, a nie została faktycznie usunięta. Następnie usunięcie staje się takie samo jak aktualizacje. Usuwanie nietrwałe można zaimplementować przy użyciu filtrów zapytań.

W przypadku prawdziwych operacji usuwania typowy wzorzec polega na użyciu rozszerzenia wzorca zapytania w celu wykonania różnic w grafie. Przykład:

public static void InsertUpdateOrDeleteGraph(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs
        .Include(b => b.Posts)
        .FirstOrDefault(b => b.BlogId == blog.BlogId);

    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
        foreach (var post in blog.Posts)
        {
            var existingPost = existingBlog.Posts
                .FirstOrDefault(p => p.PostId == post.PostId);

            if (existingPost == null)
            {
                existingBlog.Posts.Add(post);
            }
            else
            {
                context.Entry(existingPost).CurrentValues.SetValues(post);
            }
        }

        foreach (var post in existingBlog.Posts)
        {
            if (!blog.Posts.Any(p => p.PostId == post.PostId))
            {
                context.Remove(post);
            }
        }
    }

    context.SaveChanges();
}

TrackGraph

Wewnętrznie dodaj, dołącz i aktualizuj użyj przechodzenia grafu z określeniem wykonanym dla każdej jednostki, czy ma być oznaczona jako Dodano (aby wstawić), Zmodyfikowana (w celu aktualizacji), Bez zmian (nic nie robić) lub Usunięta (aby usunąć). Ten mechanizm jest uwidaczniony za pośrednictwem interfejsu API TrackGraph. Załóżmy na przykład, że gdy klient wysyła graf jednostek, ustawia na każdej jednostce flagę wskazującą, jak powinna być obsługiwana. Następnie można użyć funkcji TrackGraph do przetworzenia tej flagi:

public static void SaveAnnotatedGraph(DbContext context, object rootEntity)
{
    context.ChangeTracker.TrackGraph(
        rootEntity,
        n =>
        {
            var entity = (EntityBase)n.Entry.Entity;
            n.Entry.State = entity.IsNew
                ? EntityState.Added
                : entity.IsChanged
                    ? EntityState.Modified
                    : entity.IsDeleted
                        ? EntityState.Deleted
                        : EntityState.Unchanged;
        });

    context.SaveChanges();
}

Flagi są wyświetlane tylko jako część jednostki dla uproszczenia przykładu. Zazwyczaj flagi są częścią obiektu DTO lub innego stanu zawartego w żądaniu.