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.
Każde DbContext wystąpienie śledzi zmiany wprowadzone w elementach. Te śledzone encje z kolei powodują zmiany w bazie danych, gdy wywoływana jest funkcja SaveChanges.
Śledzenie zmian w platformie Entity Framework Core (EF Core) działa najlepiej, gdy to samo DbContext wystąpienie jest używane do zapytania o jednostki oraz ich aktualizowania za pomocą SaveChanges. Dzieje się tak, ponieważ program EF Core automatycznie śledzi stan zapytanych jednostek, a następnie wykrywa wszelkie zmiany wprowadzone w tych jednostkach po wywołaniu funkcji SaveChanges. Takie podejście zostało omówione w rozwiązaniu Change Tracking w programie EF Core.
Wskazówka
W tym dokumencie przyjęto założenie, że stany jednostki i podstawy śledzenia zmian platformy EF Core są zrozumiałe. Aby uzyskać więcej informacji na temat tych tematów, zobacz Change Tracking in EF Core (Śledzenie zmian w programie EF Core ).
Wskazówka
Możesz uruchomić i debugować cały kod w tym dokumencie, pobierając przykładowy kod z usługi GitHub.
Wprowadzenie
Jednostki mogą być jawnie przypisane do DbContext, tak że kontekst następnie śledzi te jednostki. Jest to szczególnie przydatne w następujących przypadkach:
- Tworzenie nowych jednostek, które zostaną wstawione do bazy danych.
- Ponowne dołączanie odłączonych jednostek, które wcześniej były odpytywane przez inne wystąpienie dbContext.
Pierwsza z nich będzie potrzebna większości aplikacji i jest przede wszystkim obsługiwana przez metody DbContext.Add.
Drugi jest wymagany tylko przez aplikacje, które zmieniają jednostki lub ich relacje , gdy jednostki nie są śledzone. Na przykład aplikacja internetowa może wysyłać jednostki do klienta internetowego, w którym użytkownik wprowadza zmiany i wysyła jednostki z powrotem. Te jednostki są określane jako "rozłączone", ponieważ pierwotnie były pobierane z kontekstu DbContext, ale zostały od niego odłączone, gdy zostały wysłane do klienta.
Aplikacja internetowa musi teraz ponownie dołączyć te jednostki, aby były ponownie śledzone i wskazywały wprowadzone zmiany, tak aby SaveChanges mogło dokonywać odpowiednich aktualizacji bazy danych. Jest to obsługiwane głównie przez metody DbContext.Attach i DbContext.Update.
Wskazówka
Dołączanie jednostek do tego samego wystąpienia DbContext, z którego zostały one zapytane, zwykle nie powinno być potrzebne. Nie wykonuj rutynowo zapytania bez śledzenia, a następnie dołączaj zwrócone jednostki do tego samego kontekstu. Będzie to wolniejsze niż użycie zapytania monitorującego i może również spowodować problemy, takie jak brakujące wartości ukrytych właściwości, co utrudnia uzyskanie poprawnych wyników.
Generowane a jawne wartości klucza
Domyślnie właściwości klucza całkowitego i identyfikatora GUID są skonfigurowane do używania automatycznie generowanych wartości kluczy. Ma to główną zaletę śledzenia zmian: nieustawiona wartość klucza wskazuje, że jednostka jest "nowa". Przez "nowy" oznaczamy, że nie został on jeszcze wstawiony do bazy danych.
Dwa modele są używane w poniższych sekcjach. Pierwszy jest skonfigurowany do nieużytowania wygenerowanych wartości klucza:
public class Blog
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
public IList<Post> Posts { get; } = new List<Post>();
}
public class Post
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int? BlogId { get; set; }
public Blog Blog { get; set; }
}
Niegenerowane (tj. jawnie ustawione) wartości kluczy są wyświetlane jako pierwsze w każdym przykładzie, ponieważ wszystko jest bardzo jawne i łatwe do naśladowania. Następnie następuje przykład, w którym są używane wygenerowane wartości klucza:
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Post> Posts { get; } = new List<Post>();
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int? BlogId { get; set; }
public Blog Blog { get; set; }
}
Zwróć uwagę, że właściwości klucza w tym modelu nie wymagają dodatkowej konfiguracji, ponieważ użycie wygenerowanych wartości klucza jest wartością domyślną dla prostych kluczy całkowitych.
Wstawianie nowych jednostek
Jawne wartości klucza
Jednostka musi być śledzona w stanie Added do wstawienia przez SaveChanges. Jednostki są zwykle umieszczane w stanie dodawania przez wywołanie jednej z metod: DbContext.Add, DbContext.AddRange, DbContext.AddAsync, DbContext.AddRangeAsync lub ich odpowiedników w DbSet<TEntity>.
Wskazówka
Wszystkie te metody działają w taki sam sposób w kontekście śledzenia zmian. Aby uzyskać więcej informacji, zobacz Dodatkowe funkcje śledzenia zmian .
Aby na przykład rozpocząć śledzenie nowego bloga:
context.Add(
new Blog { Id = 1, Name = ".NET Blog", });
Inspekcja widoku debugowania śledzenia zmian pokazuje po tym wywołaniu, że kontekst śledzi nowy obiekt w Added stanie:
Blog {Id: 1} Added
Id: 1 PK
Name: '.NET Blog'
Posts: []
Jednak metody Dodaj nie działają tylko na pojedynczej jednostce. Oni rzeczywiście rozpoczynają śledzenie całego grafu powiązanych jednostek, przenosząc je wszystkie do Added stanu. Aby na przykład wstawić nowy blog i skojarzone nowe wpisy:
context.Add(
new Blog
{
Id = 1,
Name = ".NET Blog",
Posts =
{
new Post
{
Id = 1,
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Id = 2,
Title = "Announcing F# 5",
Content = "F# 5 is the latest version of F#, the functional programming language..."
}
}
});
Kontekst śledzi teraz wszystkie te jednostki jako Added:
Blog {Id: 1} Added
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Added
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Added
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Zwróć uwagę, że jawne wartości zostały ustawione dla Id właściwości klucza w powyższych przykładach. Jest to spowodowane tym, że model w tym miejscu został skonfigurowany do używania jawnie ustawionych wartości kluczy, a nie automatycznie generowanych wartości kluczy. Jeśli nie używasz wygenerowanych kluczy, przed wywołaniem metody Addnależy jawnie ustawić właściwości klucza . Te wartości klucza są następnie wstawiane po wywołaniu funkcji SaveChanges. Na przykład w przypadku korzystania z biblioteki SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET Blog' (Size = 9)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Blogs" ("Id", "Name")
VALUES (@p0, @p1);
-- Executed DbCommand (0ms) [Parameters=[@p2='1' (DbType = String), @p3='1' (DbType = String), @p4='Announcing the release of EF Core 5.0, a full featured cross-platform...' (Size = 72), @p5='Announcing the Release of EF Core 5.0' (Size = 37)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("Id", "BlogId", "Content", "Title")
VALUES (@p2, @p3, @p4, @p5);
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String), @p1='1' (DbType = String), @p2='F# 5 is the latest version of F#, the functional programming language...' (Size = 72), @p3='Announcing F# 5' (Size = 15)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("Id", "BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2, @p3);
Wszystkie te jednostki są śledzone w stanie Unchanged po zakończeniu zapisywania zmian, ponieważ te jednostki już teraz istnieją w bazie danych.
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Wygenerowane wartości klucza
Jak wspomniano powyżej, właściwości kluczy typu integer i identyfikatorów GUID są skonfigurowane domyślnie do używania automatycznie generowanych wartości kluczy. Oznacza to, że aplikacja nie może jawnie ustawić żadnej wartości klucza. Aby na przykład wstawić nowy blog i wszystkie wpisy z wygenerowanymi wartościami klucza:
context.Add(
new Blog
{
Name = ".NET Blog",
Posts =
{
new Post
{
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Title = "Announcing F# 5",
Content = "F# 5 is the latest version of F#, the functional programming language..."
}
}
});
Podobnie jak w przypadku jawnych wartości kluczowych, kontekst śledzi teraz wszystkie te jednostki jako Added.
Blog {Id: -2147482644} Added
Id: -2147482644 PK Temporary
Name: '.NET Blog'
Posts: [{Id: -2147482637}, {Id: -2147482636}]
Post {Id: -2147482637} Added
Id: -2147482637 PK Temporary
BlogId: -2147482644 FK Temporary
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: -2147482644}
Post {Id: -2147482636} Added
Id: -2147482636 PK Temporary
BlogId: -2147482644 FK Temporary
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: -2147482644}
Zwróć uwagę, że w tym przypadku wartości klucza tymczasowego zostały wygenerowane dla każdej jednostki. Te wartości są używane przez program EF Core do momentu wywołania funkcji SaveChanges, w którym rzeczywiste wartości klucza są odczytywane z bazy danych. Na przykład w przypadku korzystania z biblioteki SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p0='.NET Blog' (Size = 9)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Blogs" ("Name")
VALUES (@p0);
SELECT "Id"
FROM "Blogs"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p2='Announcing the release of EF Core 5.0, a full featured cross-platform...' (Size = 72), @p3='Announcing the Release of EF Core 5.0' (Size = 37)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p1, @p2, @p3);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='F# 5 is the latest version of F#, the functional programming language...' (Size = 72), @p2='Announcing F# 5' (Size = 15)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
Po zakończeniu operacji SaveChanges wszystkie jednostki zostały zaktualizowane przy użyciu ich rzeczywistych wartości klucza i są śledzone w Unchanged stanie, ponieważ są teraz zgodne ze stanem w bazie danych:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Jest to dokładnie taki sam stan końcowy jak w poprzednim przykładzie, który używał jawnych wartości kluczy.
Wskazówka
Jawna wartość klucza może być nadal ustawiana nawet w przypadku używania wygenerowanych wartości klucza. Następnie program EF Core podejmie próbę wstawienia przy użyciu tej wartości klucza. Niektóre konfiguracje bazy danych, w tym program SQL Server z kolumnami identyfikacyjnymi, nie obsługują takich operacji wstawiania i zgłaszają wyjątek (zobacz dokumentację, aby uzyskać obejście tego problemu).
Dołączanie istniejących jednostek
Jawne wartości klucza
Jednostki zwracane z zapytań są śledzone w stanie Unchanged. Stan Unchanged oznacza, że obiekt nie został zmodyfikowany od czasu, gdy został odpytany. Jednostka odłączona, być może zwrócona z klienta internetowego w żądaniu HTTP, może zostać umieszczona w tym stanie przy użyciu DbContext.Attach, DbContext.AttachRange lub równoważnych metod w elemencie DbSet<TEntity>. Aby na przykład rozpocząć śledzenie istniejącego bloga:
context.Attach(
new Blog { Id = 1, Name = ".NET Blog", });
Uwaga / Notatka
Przykłady tutaj polegają na jawnym tworzeniu jednostek za pomocą new dla uproszczenia. Zwykle wystąpienia jednostek pochodzą z innego źródła, na przykład są deserializowane z danych klienta lub tworzone z danych przesłanych w żądaniu HTTP POST.
Inspekcja widoku debugowania monitora zmian po tym wywołaniu pokazuje, że jednostka jest śledzona w Unchanged stanie:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: []
Podobnie jak Add, Attach faktycznie ustawia cały graf połączonych jednostek do stanu Unchanged. Aby na przykład dołączyć istniejący blog i skojarzone istniejące wpisy:
context.Attach(
new Blog
{
Id = 1,
Name = ".NET Blog",
Posts =
{
new Post
{
Id = 1,
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Id = 2,
Title = "Announcing F# 5",
Content = "F# 5 is the latest version of F#, the functional programming language..."
}
}
});
Kontekst śledzi teraz wszystkie te jednostki jako Unchanged:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Wywołanie metody SaveChanges w tym momencie nie będzie miało żadnego wpływu. Wszystkie jednostki są oznaczone jako Unchanged, więc nie ma nic do zaktualizowania w bazie danych.
Wygenerowane wartości klucza
Jak wspomniano powyżej, właściwości kluczy typu integer i identyfikatorów GUID są skonfigurowane domyślnie do używania automatycznie generowanych wartości kluczy. Ma to główną zaletę podczas pracy z odłączonymi jednostkami: niezwiązana wartość klucza wskazuje, że jednostka nie została jeszcze wstawiona do bazy danych. Dzięki temu śledzenie zmian automatycznie wykrywa nowe jednostki i umieszcza je w stanie Added. Rozważ na przykład dołączenie tego wykresu do bloga i wpisów.
context.Attach(
new Blog
{
Id = 1,
Name = ".NET Blog",
Posts =
{
new Post
{
Id = 1,
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Id = 2,
Title = "Announcing F# 5",
Content = "F# 5 is the latest version of F#, the functional programming language..."
},
new Post
{
Title = "Announcing .NET 5.0",
Content = ".NET 5.0 includes many enhancements, including single file applications, more..."
},
}
});
W blogu znajduje się wartość klucza 1 wskazująca, że już istnieje w bazie danych. Dwa wpisy mają również ustawione wartości klucza, ale trzeci nie. Platforma EF Core zobaczy tę wartość klucza jako 0— domyślną wartość clR dla liczby całkowitej. Spowoduje to oznaczenie nowej jednostki przez program EF Core jako Added zamiast Unchanged:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482636}]
Post {Id: -2147482636} Added
Id: -2147482636 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 includes many enhancements, including single file a...'
Title: 'Announcing .NET 5.0'
Blog: {Id: 1}
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Wywołanie metody SaveChanges w tym momencie nie powoduje niczego z Unchanged jednostkami, ale wstawia nową jednostkę do bazy danych. Na przykład w przypadku korzystania z biblioteki SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET 5.0 includes many enhancements, including single file applications, more...' (Size = 80), @p2='Announcing .NET 5.0' (Size = 19)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
Należy zauważyć, że przy wygenerowanych wartościach kluczy program EF Core może automatycznie odróżnić nowe jednostki od istniejących jednostek w rozłączonym grafie. W skrócie podczas korzystania z wygenerowanych kluczy program EF Core zawsze wstawia jednostkę, gdy ta jednostka nie ma ustawionej wartości klucza.
Aktualizowanie istniejących jednostek
Jawne wartości klucza
DbContext.Update, DbContext.UpdateRange, i równoważne metody na DbSet<TEntity> zachowują się dokładnie tak, jak Attach opisane powyżej, z tą różnicą, że jednostki są umieszczane w stanie Modified zamiast stanu Unchanged. Na przykład, aby rozpocząć śledzenie istniejącego bloga jako Modified:
context.Update(
new Blog { Id = 1, Name = ".NET Blog", });
Sprawdzenie widoku debugowania śledzenia zmian pokazuje po tym wywołaniu, że kontekst śledzi ten obiekt w Modified stanie.
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog' Modified
Posts: []
Podobnie jak w przypadku Add i Attach, Update faktycznie oznacza cały wykres powiązanych jednostek jako Modified. Aby na przykład dołączyć istniejący blog i powiązane z nim istniejące wpisy w formie Modified:
context.Update(
new Blog
{
Id = 1,
Name = ".NET Blog",
Posts =
{
new Post
{
Id = 1,
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Id = 2,
Title = "Announcing F# 5",
Content = "F# 5 is the latest version of F#, the functional programming language..."
}
}
});
Kontekst śledzi teraz wszystkie te jednostki jako Modified:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog' Modified
Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Modified
Id: 1 PK
BlogId: 1 FK Modified Originally <null>
Content: 'Announcing the release of EF Core 5.0, a full featured cross...' Modified
Title: 'Announcing the Release of EF Core 5.0' Modified
Blog: {Id: 1}
Post {Id: 2} Modified
Id: 2 PK
BlogId: 1 FK Modified Originally <null>
Content: 'F# 5 is the latest version of F#, the functional programming...' Modified
Title: 'Announcing F# 5' Modified
Blog: {Id: 1}
Wywołanie metody SaveChanges w tym momencie spowoduje wysłanie aktualizacji do bazy danych dla wszystkich tych jednostek. Na przykład w przypadku korzystania z biblioteki SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog' (Size = 9)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p3='1' (DbType = String), @p0='1' (DbType = String), @p1='Announcing the release of EF Core 5.0, a full featured cross-platform...' (Size = 72), @p2='Announcing the Release of EF Core 5.0' (Size = 37)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0, "Content" = @p1, "Title" = @p2
WHERE "Id" = @p3;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p3='2' (DbType = String), @p0='1' (DbType = String), @p1='F# 5 is the latest version of F#, the functional programming language...' (Size = 72), @p2='Announcing F# 5' (Size = 15)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0, "Content" = @p1, "Title" = @p2
WHERE "Id" = @p3;
SELECT changes();
Wygenerowane wartości klucza
Podobnie jak w przypadku Attach, wygenerowane wartości klucza mają taką samą główną korzyść dla Update: niezstawiona wartość klucza wskazuje, że jednostka jest nowa i nie została jeszcze wstawiona do bazy danych. Podobnie jak w przypadku Attach, umożliwia to DbContext automatyczne wykrywanie nowych jednostek i umieszczanie ich w stanie Added. Rozważ na przykład wywołanie Update za pomocą tego grafu bloga i wpisów:
context.Update(
new Blog
{
Id = 1,
Name = ".NET Blog",
Posts =
{
new Post
{
Id = 1,
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Id = 2,
Title = "Announcing F# 5",
Content = "F# 5 is the latest version of F#, the functional programming language..."
},
new Post
{
Title = "Announcing .NET 5.0",
Content = ".NET 5.0 includes many enhancements, including single file applications, more..."
},
}
});
Tak jak w przykładzie Attach, wpis bez wartości klucza jest wykrywany jako nowy i ustawiany w stanie Added. Pozostałe jednostki są oznaczone jako Modified:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog' Modified
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482633}]
Post {Id: -2147482633} Added
Id: -2147482633 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 includes many enhancements, including single file a...'
Title: 'Announcing .NET 5.0'
Blog: {Id: 1}
Post {Id: 1} Modified
Id: 1 PK
BlogId: 1 FK Modified Originally <null>
Content: 'Announcing the release of EF Core 5.0, a full featured cross...' Modified
Title: 'Announcing the Release of EF Core 5.0' Modified
Blog: {Id: 1}
Post {Id: 2} Modified
Id: 2 PK
BlogId: 1 FK Modified Originally <null>
Content: 'F# 5 is the latest version of F#, the functional programming...' Modified
Title: 'Announcing F# 5' Modified
Blog: {Id: 1}
Wywołanie SaveChanges w tym momencie spowoduje wysłanie aktualizacji do bazy danych dla wszystkich istniejących jednostek, podczas gdy nowa jednostka zostanie wstawiona. Na przykład w przypadku korzystania z biblioteki SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog' (Size = 9)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p3='1' (DbType = String), @p0='1' (DbType = String), @p1='Announcing the release of EF Core 5.0, a full featured cross-platform...' (Size = 72), @p2='Announcing the Release of EF Core 5.0' (Size = 37)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0, "Content" = @p1, "Title" = @p2
WHERE "Id" = @p3;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p3='2' (DbType = String), @p0='1' (DbType = String), @p1='F# 5 is the latest version of F#, the functional programming language...' (Size = 72), @p2='Announcing F# 5' (Size = 15)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0, "Content" = @p1, "Title" = @p2
WHERE "Id" = @p3;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET 5.0 includes many enhancements, including single file applications, more...' (Size = 80), @p2='Announcing .NET 5.0' (Size = 19)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
Jest to bardzo prosty sposób generowania aktualizacji i wstawiania z odłączonego grafu. Jednak powoduje to, że aktualizacje lub wstawki są wysyłane do bazy danych dla każdej właściwości każdej śledzonej jednostki, nawet jeśli niektóre wartości właściwości mogły nie zostać zmienione. Nie obawiaj się tego; w przypadku wielu aplikacji z małymi grafami może to być łatwy i pragmatyczny sposób generowania aktualizacji. Oznacza to, że inne bardziej złożone wzorce mogą czasami powodować bardziej wydajne aktualizacje, zgodnie z opisem w temacie Identity Resolution in EF Core (Rozpoznawanie tożsamości w programie EF Core).
Usuwanie istniejących jednostek
Aby usunąć jednostkę za pomocą polecenia SaveChanges, musi być śledzona w stanie Deleted. Jednostki są zwykle umieszczane w stanie Deleted przez wywołanie jednej z metod DbContext.Remove, DbContext.RemoveRange lub równoważnych na DbSet<TEntity>. Aby na przykład oznaczyć istniejący wpis jako Deleted:
context.Remove(
new Post { Id = 2 });
Sprawdzanie widoku debugowania śledzenia zmian po tym wywołaniu pokazuje, że kontekst śledzi jednostkę w Deleted stanie.
Post {Id: 2} Deleted
Id: 2 PK
BlogId: <null> FK
Content: <null>
Title: <null>
Blog: <null>
Ta jednostka zostanie usunięta po wywołaniu metody SaveChanges. Na przykład w przypadku korzystania z biblioteki SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();
Po zakończeniu operacji SaveChanges usunięta jednostka jest odłączona od obiektu DbContext, ponieważ nie istnieje już w bazie danych. Dlatego widok debugowania jest pusty, ponieważ nie są śledzone żadne jednostki.
Usuwanie jednostek zależnych/podrzędnych
Usuwanie jednostek zależnych/podrzędnych z grafu jest prostsze niż usuwanie jednostek głównych/nadrzędnych. Aby uzyskać więcej informacji, zobacz następną sekcję i Zmienianie kluczy obcych i nawigacji .
Nietypowe jest wywoływanie Remove na jednostce utworzonej za pomocą new. Ponadto, w przeciwieństwie do Add, Attach i Update, rzadko wywołuje się Remove na encję, która nie jest jeszcze śledzona w stanie Unchanged lub Modified. Zamiast tego typowe jest śledzenie pojedynczej jednostki lub grafu powiązanych jednostek, a następnie wywoływanie Remove dla jednostek, które powinny zostać usunięte. Ten wykres śledzonych jednostek jest zwykle tworzony przez:
- Uruchamianie zapytania dla jednostek
- Używanie metod
AttachlubUpdatena grafie odłączonych jednostek, jak opisano w poprzednich sekcjach.
Na przykład kod z poprzedniej sekcji prawdopodobnie uzyska post od klienta i wykona coś takiego jak:
context.Attach(post);
context.Remove(post);
Zachowuje się to dokładnie tak samo jak w poprzednim przykładzie, ponieważ wywołanie Remove na nieśledzonej jednostce powoduje, że najpierw zostanie dołączona, a następnie oznaczona jako Deleted.
W bardziej realistycznych przykładach wykres jednostek jest najpierw dołączony, a następnie niektóre z tych jednostek są oznaczone jako usunięte. Przykład:
// Attach a blog and associated posts
context.Attach(blog);
// Mark one post as Deleted
context.Remove(blog.Posts[1]);
Wszystkie jednostki są oznaczone jako Unchanged, z wyjątkiem tej, na której Remove została wywołana:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Deleted
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Ta jednostka zostanie usunięta po wywołaniu metody SaveChanges. Na przykład w przypadku korzystania z biblioteki SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();
Po zakończeniu operacji SaveChanges usunięta jednostka jest odłączona od obiektu DbContext, ponieważ nie istnieje już w bazie danych. Inne jednostki pozostają w Unchanged stanie:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}]
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Usuwanie podmiotów głównych/nadrzędnych
Każda relacja łącząca dwa typy jednostek ma główny lub nadrzędny koniec oraz zależny lub podrzędny koniec. Encja zależna/podrzędna jest encją z właściwością klucza obcego. W relacji jeden do wielu, główny/nadrzędny znajduje się po stronie „jeden”, a zależny/podrzędny znajduje się po stronie „wiele”. Aby uzyskać więcej informacji, zobacz Relacje .
W poprzednich przykładach usuwaliśmy wpis, który jest jednostką zależną/potomną w relacji jeden-do-wielu wewnątrz postów na blogu. Jest to stosunkowo proste, ponieważ usunięcie jednostki zależnej/podrzędnej nie ma żadnego wpływu na inne jednostki. Z drugiej strony usunięcie jednostki głównej/nadrzędnej musi mieć również wpływ na wszystkie jednostki zależne/podrzędne. Nie spowoduje to pozostawienia wartości klucza obcego odwołującej się do wartości klucza podstawowego, która już nie istnieje. Jest to nieprawidłowy stan modelu i powoduje wystąpienie błędu ograniczenia odwołania w większości baz danych.
Ten nieprawidłowy stan modelu można obsłużyć na dwa sposoby:
- Ustawianie wartości FK na null. Oznacza to, że osoby zależne/dzieci nie są już powiązane z żadnym głównym/rodzicami. Jest to ustawienie domyślne dla relacji opcjonalnych, w których klucz obcy musi mieć wartość null. Ustawienie klucza FK na wartość null jest nieprawidłowe dla wymaganych relacji, gdzie klucz obcy jest zwykle niepusty.
- Usuwanie dzieci/zależnych osób. Jest to wartość domyślna dla wymaganych relacji i jest również prawidłowa w przypadku relacji opcjonalnych.
Aby uzyskać szczegółowe informacje na temat śledzenia zmian i relacji, zobacz Zmienianie kluczy obcych i nawigacji .
Relacje opcjonalne
Właściwość Post.BlogId klucza obcego może przyjmować wartość null w modelu, którego używamy. Oznacza to, że relacja jest opcjonalna, dlatego domyślne zachowanie platformy EF Core polega na ustawieniu BlogId właściwości klucza obcego na wartość null po usunięciu bloga. Przykład:
// Attach a blog and associated posts
context.Attach(blog);
// Mark the blog as deleted
context.Remove(blog);
Inspekcja widoku debugowania monitora zmian po wywołaniu Remove pokazuje, że zgodnie z oczekiwaniami blog jest teraz oznaczony jako Deleted:
Blog {Id: 1} Deleted
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Modified
Id: 1 PK
BlogId: <null> FK Modified Originally 1
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: <null>
Post {Id: 2} Modified
Id: 2 PK
BlogId: <null> FK Modified Originally 1
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: <null>
Co ciekawe, wszystkie powiązane wpisy są teraz oznaczone jako Modified. Jest to spowodowane tym, że właściwość klucza obcego w każdej jednostce została ustawiona na wartość null. Wywołanie polecenia SaveChanges aktualizuje wartość klucza obcego dla każdego wpisu na wartość null w bazie danych, a następnie usuwa blog:
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0=NULL], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p1='2' (DbType = String), @p0=NULL], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p2='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Blogs"
WHERE "Id" = @p2;
SELECT changes();
Po zakończeniu operacji SaveChanges usunięta jednostka jest odłączona od obiektu DbContext, ponieważ nie istnieje już w bazie danych. Inne jednostki są teraz oznaczone jako Unchanged z wartościami klucza obcego o wartości null, które są zgodne ze stanem bazy danych:
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: <null> FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: <null>
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: <null> FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: <null>
Wymagane relacje
Jeśli właściwość klucza obcego Post.BlogId jest nienulowalna, relacja między blogami i wpisami staje się obowiązkowa. W takiej sytuacji EF Core domyślnie usuwa jednostki zależne/podrzędne, gdy zostaną usunięte jednostki główne/macierzyste. Na przykład usunięcie bloga z powiązanymi wpisami, jak w poprzednim przykładzie:
// Attach a blog and associated posts
context.Attach(blog);
// Mark the blog as deleted
context.Remove(blog);
Sprawdzenie widoku debugowania monitora zmian po wywołaniu Remove pokazuje, że zgodnie z oczekiwaniami blog jest ponownie oznaczony jako Deleted:
Blog {Id: 1} Deleted
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Deleted
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Deleted
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
W tym przypadku bardziej interesujące jest to, że wszystkie powiązane wpisy również zostały oznaczone jako Deleted. Wywołanie polecenia SaveChanges powoduje usunięcie bloga i wszystkich powiązanych wpisów z bazy danych:
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Blogs"
WHERE "Id" = @p1;
Po zakończeniu operacji SaveChanges wszystkie usunięte jednostki są odłączone od obiektu DbContext, ponieważ nie istnieją już w bazie danych. W związku z tym dane wyjściowe z widoku debugowania są puste.
Uwaga / Notatka
Ten dokument porusza tylko powierzchownie temat pracy z relacjami w EF Core. Zobacz Relacje , aby uzyskać więcej informacji na temat modelowania relacji oraz Zmienianie kluczy obcych i nawigacji , aby uzyskać więcej informacji na temat aktualizowania/usuwania jednostek zależnych/podrzędnych podczas wywoływania funkcji SaveChanges.
Śledzenie niestandardowe za pomocą usługi TrackGraph
ChangeTracker.TrackGraph działa jak Add, Attach i Update, ale generuje wywołanie zwrotne dla każdego wystąpienia jednostki, zanim zacznie je śledzić. Dzięki temu można używać logiki niestandardowej podczas określania sposobu śledzenia poszczególnych jednostek na grafie.
Rozważmy na przykład użycie reguły programu EF Core podczas śledzenia jednostek z wygenerowanymi wartościami klucza: jeśli wartość klucza wynosi zero, jednostka jest nowa i powinna zostać wstawiona. Rozszerzmy tę regułę, aby powiedzieć, czy wartość klucza jest ujemna, a następnie jednostka powinna zostać usunięta. Dzięki temu możemy zmienić wartości klucza podstawowego w jednostkach rozłączonego grafu, aby oznaczyć usunięte jednostki:
blog.Posts.Add(
new Post
{
Title = "Announcing .NET 5.0",
Content = ".NET 5.0 includes many enhancements, including single file applications, more..."
}
);
var toDelete = blog.Posts.Single(e => e.Title == "Announcing F# 5");
toDelete.Id = -toDelete.Id;
Ten odłączony graf można następnie śledzić przy użyciu usługi TrackGraph:
public static async Task UpdateBlog(Blog blog)
{
using var context = new BlogsContext();
context.ChangeTracker.TrackGraph(
blog, node =>
{
var propertyEntry = node.Entry.Property("Id");
var keyValue = (int)propertyEntry.CurrentValue;
if (keyValue == 0)
{
node.Entry.State = EntityState.Added;
}
else if (keyValue < 0)
{
propertyEntry.CurrentValue = -keyValue;
node.Entry.State = EntityState.Deleted;
}
else
{
node.Entry.State = EntityState.Modified;
}
Console.WriteLine($"Tracking {node.Entry.Metadata.DisplayName()} with key value {keyValue} as {node.Entry.State}");
});
await context.SaveChangesAsync();
}
Dla każdej jednostki na grafie powyższy kod sprawdza wartość klucza podstawowego przed śledzeniem jednostki. W przypadku nieustawionych (zero) wartości klucza kod działa tak, jak normalnie robiłby to EF Core. Oznacza to, że jeśli klucz nie jest ustawiony, jednostka jest oznaczona jako Added. Jeśli klucz jest ustawiony, a wartość jest nieujemna, jednostka jest oznaczona jako Modified. Jeśli jednak zostanie znaleziona wartość klucza ujemnego, zostanie przywrócona rzeczywista, nie ujemna wartość, a jednostka zostanie śledzona jako Deleted.
Dane wyjściowe z uruchamiania tego kodu to:
Tracking Blog with key value 1 as Modified
Tracking Post with key value 1 as Modified
Tracking Post with key value -2 as Deleted
Tracking Post with key value 0 as Added
Uwaga / Notatka
Dla uproszczenia ten kod zakłada, że każda jednostka ma właściwość klucza podstawowego liczby całkowitej o nazwie Id. Może to być skodyfikowane w abstrakcyjnej klasie bazowej lub interfejsie. Alternatywnie można uzyskać właściwość lub właściwości klucza podstawowego z IEntityType metadanych, tak aby ten kod działał z dowolnym typem jednostki.
TrackGraph ma dwa przeciążenia. W przypadku prostego przeciążenia użytego powyżej program EF Core określa, kiedy zatrzymać przechodzenie grafu. W szczególności zatrzymuje przeglądanie nowych powiązanych jednostek z określonej jednostki, gdy ta jednostka jest już śledzona albo gdy wywołanie zwrotne nie inicjuje jej śledzenia.
Zaawansowane przeciążenie, ChangeTracker.TrackGraph<TState>(Object, TState, Func<EntityEntryGraphNode<TState>,Boolean>) ma wywołanie zwrotne, które zwraca wartość typu 'bool'. Jeśli wywołanie zwrotne zwraca wartość false, przechodzenie grafu zostanie zatrzymane, w przeciwnym razie będzie kontynuowane. Należy zachować ostrożność, aby uniknąć nieskończonych pętli podczas korzystania z tego przeciążenia.
Zaawansowane przeciążenie umożliwia również przekazanie stanu do TrackGraph, a stan ten jest następnie przekazywany do każdego wywołania zwrotnego.