Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Wystąpienie DbContext automatycznie będzie ś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 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 "rozłączonych" scenariuszach, takich jak aplikacja internetowa, gdzie jednostki są pobierane, wysyłane do klienta, modyfikowane, a następnie odsyłane na serwer i zapisywane. W takim przypadku drugie wystąpienie kontekstu musi wiedzieć, czy jednostki są nowe (należy wstawić) czy istniejące (powinny zostać zaktualizowane).
Wskazówka
Przykład z tego artykułu można zobaczyć w witrynie GitHub.
Wskazówka
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 zaczynał się pusty, ma do siebie dołączone jednostki, zapisuje te jednostki, a następnie 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ł ustawiony.
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;
Wskazówka
Klucze są ustawiane, gdy tylko 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 async Task<bool> IsItNew(BloggingContext context, Blog blog)
=> (await context.Blogs.FindAsync(blog.BlogId)) == null;
Niniejszy dokument nie obejmuje pełnego kodu 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 elementów
Jeśli wiadomo, czy jest wymagana wstawienie lub aktualizacja, można użyć odpowiednio opcji Dodaj lub Aktualizuj:
public static async Task Insert(DbContext context, object entity)
{
context.Add(entity);
await context.SaveChangesAsync();
}
public static async Task Update(DbContext context, object entity)
{
context.Update(entity);
await context.SaveChangesAsync();
}
Jeśli jednak jednostka używa wartości klucza generowanego automatycznie, można użyć metody Update w obu przypadkach:
public static async Task InsertOrUpdate(DbContext context, object entity)
{
context.Update(entity);
await context.SaveChangesAsync();
}
Metoda Update zwykle oznacza jednostkę do aktualizacji, a nie do wstawienia. 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 async Task InsertOrUpdate(BloggingContext context, Blog blog)
{
var existingBlog = await context.Blogs.FindAsync(blog.BlogId);
if (existingBlog == null)
{
context.Add(blog);
}
else
{
context.Entry(existingBlog).CurrentValues.SetValues(blog);
}
await context.SaveChangesAsync();
}
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.
Wskazówka
SetValues będzie oznaczać jako zmodyfikowane tylko te właściwości, które mają inne wartości od 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ć graf tak, aby ta niezmienność była utrzymana, a kontekst powinien być używany tylko do jednej jednostki zadaniowej. 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 być niełatwe, gdy instancje mają sprzeczne wartości i relacje, dlatego konsolidacja duplikatów powinna być wykonywana jak najszybciej w procesie 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 async Task InsertGraph(DbContext context, object rootEntity)
{
context.Add(rootEntity);
await context.SaveChangesAsync();
}
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 async Task UpdateGraph(DbContext context, object rootEntity)
{
context.Update(rootEntity);
await context.SaveChangesAsync();
}
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 async Task InsertOrUpdateGraph(DbContext context, object rootEntity)
{
context.Update(rootEntity);
await context.SaveChangesAsync();
}
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 async Task InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
var existingBlog = await context.Blogs
.Include(b => b.Posts)
.FirstOrDefaultAsync(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);
}
}
}
await context.SaveChangesAsync();
}
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. Usunięcia stają się tym samym co aktualizacje. Miękkie usunięcia można zaimplementować za pomocą 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 async Task InsertUpdateOrDeleteGraph(BloggingContext context, Blog blog)
{
var existingBlog = await context.Blogs
.Include(b => b.Posts)
.FirstOrDefaultAsync(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);
}
}
}
await context.SaveChangesAsync();
}
TrackGraph
Wewnętrznie Dodaj, Dołącz i Aktualizuj używają przechodzenia grafu z określeniem wykonanym dla każdej encji, czy ma być oznaczona jako Dodana (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 async Task 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;
});
await context.SaveChangesAsync();
}
Flagi są wyświetlane tylko jako część jednostki dla uproszczenia przykładu. Zazwyczaj flagi są częścią obiektu Data Transfer Object (DTO) lub innego stanu zawartego w żądaniu.