Jawne śledzenie jednostek
Każde wystąpienie obiektu DbContext śledzi zmiany wprowadzane w jednostkach. Te śledzone jednostki sterują z kolei zmianami w bazie danych przy wywoływaniu metody SaveChanges.
Śledzenie zmian platformy Entity Framework Core (EF Core) działa najlepiej, gdy to samo DbContext wystąpienie jest używane do wykonywania zapytań dotyczących jednostek i aktualizowania ich przez wywołanie metody SaveChanges. Jest tak dlatego, że rozwiązanie EF Core automatycznie śledzi stan jednostek określonych w zapytaniu, a następnie przy wywołaniu metody SaveChanges wykrywa wszelkie zmiany wprowadzone w tych jednostkach. Takie podejście zostało omówione w rozwiązaniu Change Tracking w programie EF Core.
Napiwek
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 ).
Napiwek
Możesz uruchomić i debugować cały kod podany w tym dokumencie, pobierając przykładowy kod z serwisu GitHub.
Napiwek
Dla uproszczenia w tym dokumencie są używane metody synchroniczne, takie jak SaveChanges, a nie ich odpowiedniki asynchroniczne, takie jak SaveChangesAsync. Alternatywnie można wywoływać metodę asynchroniczną i oczekiwać na nią, chyba że określono inaczej.
Wprowadzenie
Jednostki mogą być jawnie "dołączone" do DbContext takiego, ż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 przez większość aplikacji i jest obsługiwana DbContext.Add głównie przez metody.
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ż były one pierwotnie wysyłane z obiektu DbContext, ale następnie zostały odłączone od tego kontekstu podczas wysyłania do klienta.
Aplikacja internetowa musi teraz ponownie dołączyć te jednostki, aby były ponownie śledzone i wskazywać wprowadzone zmiany, które SaveChanges mogą wprowadzać odpowiednie aktualizacje bazy danych. Jest to obsługiwane głównie przez DbContext.Attach metody i DbContext.Update .
Napiwek
Dołączanie jednostek do tego samego wystąpienia dbContext, z którego zostały one zapytane, nie powinno być zwykle 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 śledzenia i może również spowodować problemy, takie jak brakujące wartości właściwości w tle, co utrudnia uzyskanie prawidłowego dostępu.
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 Added
stanie, który ma zostać wstawiony przez SaveChangeselement . Jednostki są zwykle umieszczane w stanie Dodano przez wywołanie jednej z DbContext.Addmetod , DbContext.AddRange, DbContext.AddAsync, DbContext.AddRangeAsynclub równoważnych w elemencie DbSet<TEntity>.
Napiwek
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 monitora zmian po tym wywołaniu pokazuje, że kontekst śledzi nową jednostkę 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 rozpocząć śledzenie całego grafu powiązanych jednostek, umieszczając je wszystkie w Added
stanie. 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 Add
należ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 po zakończeniu Unchanged
zapisywania zmian, ponieważ te jednostki istnieją teraz 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 klucza całkowitego i identyfikatora GUID są domyślnie skonfigurowane 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 wpisów 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 klucza, 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.
Napiwek
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 Tożsamości, nie obsługują takich wstawiania i zgłaszają wyjątek (zobacz te dokumenty, aby obejść ten problem).
Dołączanie istniejących jednostek
Jawne wartości klucza
Jednostki zwracane z zapytań są śledzone w Unchanged
stanie . Stan Unchanged
oznacza, że jednostka nie została zmodyfikowana, ponieważ została odpytowana. 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.Attachmetod , lub DbContext.AttachRangeró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
W tym przykładzie jednostki są tworzone jawnie dla new
uproszczenia. Zwykle wystąpienia jednostek pochodzą z innego źródła, takiego jak deserializowane z klienta lub tworzone na podstawie danych w poście HTTP.
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 wykres połączonych jednostek ze stanem 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 klucza całkowitego i identyfikatora GUID są domyślnie skonfigurowane 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 monitor zmian automatycznie wykrywa nowe jednostki i umieszcza je w Added
stanie. Rozważ na przykład dołączenie tego grafu w blogu 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.UpdateRangei równoważne metody DbSet<TEntity> zachowują się dokładnie tak, jak Attach
metody opisane powyżej, z tą różnicą, że jednostki są umieszczane w Modified
stanie zamiast Unchanged
. Aby na przykład rozpocząć śledzenie istniejącego bloga jako Modified
:
context.Update(
new Blog { Id = 1, Name = ".NET Blog", });
Inspekcja widoku debugowania śledzenia zmian po tym wywołaniu pokazuje, że kontekst śledzi tę jednostkę w Modified
stanie:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog' Modified
Posts: []
Podobnie jak w przypadku i , faktycznie oznacza cały wykres powiązanych jednostek jako Modified
. Update
Attach
Add
Aby na przykład dołączyć istniejący blog i skojarzone istniejące wpisy jako 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
elementu , umożliwia to usłudze DbContext automatyczne wykrywanie nowych jednostek i umieszczanie ich w Added
stanie. 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 ustawiony na Added
stan. 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 jednostka została usunięta przez polecenie SaveChanges, musi być śledzona w Deleted
stanie . Jednostki są zwykle umieszczane w Deleted
stanie przez wywołanie jednej z DbContext.Removemetod , DbContext.RemoveRangelub równoważnych w pliku DbSet<TEntity>. Aby na przykład oznaczyć istniejący wpis jako Deleted
:
context.Remove(
new Post { Id = 2 });
Inspekcja 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. W związku z tym 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 wywołanie Remove
jednostki utworzonej za pomocą new
polecenia . Ponadto w przeciwieństwie do Add
, Attach
i Update
rzadko wywołuje Remove
jednostkę, która nie jest jeszcze śledzona w Unchanged
stanie lub Modified
. Zamiast tego typowe jest śledzenie pojedynczej jednostki lub grafu powiązanych jednostek, a następnie wywoływanie Remove
jednostek, które powinny zostać usunięte. Ten wykres śledzonych jednostek jest zwykle tworzony przez:
- Uruchamianie zapytania dla jednostek
Attach
Używanie metod lubUpdate
na grafie odłączonych jednostek, zgodnie z opisem w poprzednich sekcjach.
Na przykład kod w poprzedniej sekcji jest bardziej prawdopodobne, aby uzyskać wpis od klienta, a następnie wykonać podobne czynności:
context.Attach(post);
context.Remove(post);
Zachowuje się to dokładnie tak samo jak w poprzednim przykładzie, ponieważ wywołanie Remove
nieśledzenia jednostki powoduje, że najpierw zostanie dołączony, a następnie oznaczony 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 jednostek podmiotów zabezpieczeń/nadrzędnych
Każda relacja łącząca dwa typy jednostek ma główną lub nadrzędną końcu oraz zależny lub podrzędny koniec. Jednostka zależna/podrzędna jest jednostką z właściwością klucza obcego. W relacji jeden do wielu podmiot zabezpieczeń/nadrzędny znajduje się po stronie "jeden", a element 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ą/podrzędną w relacji "jeden do wielu" w 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 zależności/elementy podrzędne nie są już powiązane z żadnym podmiotem zabezpieczeń/elementem nadrzędnym. 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 zależności/elementów podrzędnych. 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 jest dopuszczana do wartości null w modelu, z których korzystaliśmy. 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, aby pokazaćRemove
, ż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 niepusta, relacja między blogami i wpisami staje się "wymagana". W takiej sytuacji program EF Core domyślnie usunie jednostki zależne/podrzędne po usunięciu podmiotu zabezpieczeń/elementu nadrzędnego. 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);
Sprawdź widok debugowania monitora zmian po wywołaniu, aby pokazaćRemove
, że zgodnie z oczekiwaniami blog zostanie 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
Ten dokument zawiera tylko rysowanie powierzchni podczas pracy z relacjami w programie 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
z tą różnicą, że generuje wywołanie zwrotne dla każdego wystąpienia jednostki przed jego śledzeniem. 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 void 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}");
});
context.SaveChanges();
}
Dla każdej jednostki na grafie powyższy kod sprawdza wartość klucza podstawowego przed śledzeniem jednostki. W przypadku nieustawionych (zero) wartości klucza kod wykonuje zwykle operacje 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
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 odwiedzanie nowych powiązanych jednostek z danej jednostki, gdy ta jednostka jest już śledzona lub gdy wywołanie zwrotne nie rozpoczyna śledzenia jednostki.
Zaawansowane przeciążenie , ChangeTracker.TrackGraph<TState>(Object, TState, Func<EntityEntryGraphNode<TState>,Boolean>)ma wywołanie zwrotne, które zwraca wartość logiczną. 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ż podanie stanu do usługi TrackGraph, a ten stan jest następnie przekazywany do każdego wywołania zwrotnego.