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. Opisano to w temacie Change Tracking in EF Core (Śledzenie zmian w programie EF Core), a w tym dokumencie założono, że stany jednostek i podstawy śledzenia zmian platformy Entity Framework Core (EF Core) są zrozumiałe.
Śledzenie zmian właściwości i relacji wymaga, aby obiekt DbContext mógł wykryć te zmiany. W tym dokumencie opisano, jak to wykrywanie się odbywa, a także jak używać powiadomień dotyczących właściwości lub serwisów śledzenia zmian w celu wymuszenia natychmiastowego wykrywania zmian.
Wskazówka
Możesz uruchomić i debugować cały kod w tym dokumencie, pobierając przykładowy kod z usługi GitHub.
Śledzenie zmian w migawce
Domyślnie program EF Core tworzy migawkę wartości właściwości każdej jednostki, gdy jest ona najpierw śledzona przez wystąpienie DbContext. Wartości przechowywane w tej migawce są następnie porównywane z bieżącymi wartościami jednostki w celu określenia, które wartości właściwości uległy zmianie.
To wykrywanie zmian występuje po wywołaniu metody SaveChanges w celu upewnienia się, że wszystkie zmienione wartości zostaną wykryte przed wysłaniem aktualizacji do bazy danych. Jednak wykrywanie zmian odbywa się również w innym czasie, aby upewnić się, że aplikacja współpracuje z up-to-date tracking information. Wykrywanie zmian można wymusić w dowolnym momencie przez wywołanie metody ChangeTracker.DetectChanges().
Gdy jest wymagane wykrywanie zmian
Wykrywanie zmian jest wymagane, gdy właściwość lub nawigacja została zmieniona bez używania programu EF Core do wprowadzenia tej zmiany. Rozważ na przykład ładowanie blogów i wpisów, a następnie wprowadzanie zmian w następujących jednostkach:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
blog.Name = ".NET Blog (Updated!)";
// Add a new entity to a navigation
blog.Posts.Add(
new Post
{
Title = "What’s next for System.Text.Json?", Content = ".NET 5.0 was released recently and has come with many..."
});
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Patrząc na widok debugowania śledzenia zmian przed wywołaniem ChangeTracker.DetectChanges() , widać, że wprowadzone zmiany nie zostały wykryte, dlatego nie są odzwierciedlane w stanach jednostki i zmodyfikowanych danych właściwości:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog (Updated!)' Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, <not found>]
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}
W szczególności stan wpisu na blogu pozostaje jako Unchanged, a nowy wpis nie pojawia się jako śledzona jednostka. (Właściwości raportują swoje nowe wartości, mimo że te zmiany nie zostały jeszcze wykryte przez EF Core. Dzieje się tak, ponieważ widok debugowania odczytuje bieżące wartości bezpośrednio z jednostki.)
Porównaj to z widokiem debugowania po wywołaniu funkcji DetectChanges:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482643}]
Post {Id: -2147482643} Added
Id: -2147482643 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 was released recently and has come with many...'
Title: 'What's next for System.Text.Json?'
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...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Teraz blog jest poprawnie oznaczony jako Modified i nowy wpis został wykryty i jest śledzony jako Added.
Na początku tej sekcji stwierdziliśmy, że wykrywanie zmian jest wymagane, gdy nie jest używane program EF Core do wprowadzenia zmiany. Dzieje się tak w powyższym kodzie. Oznacza to, że zmiany w właściwościach i nawigacji są wprowadzane bezpośrednio na instancjach encji, a nie przy użyciu żadnych metod EF Core.
Porównaj to z następującym kodem, który modyfikuje jednostki w ten sam sposób, ale tym razem przy użyciu metod EF Core:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
context.Entry(blog).Property(e => e.Name).CurrentValue = ".NET Blog (Updated!)";
// Add a new entity to the DbContext
context.Add(
new Post
{
Blog = blog,
Title = "What’s next for System.Text.Json?",
Content = ".NET 5.0 was released recently and has come with many..."
});
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
W tym przypadku widok debugowania monitora zmian pokazuje, że wszystkie stany jednostki i modyfikacje właściwości są znane, mimo że nie nastąpiło wykrywanie zmian. Wynika to z faktu PropertyEntry.CurrentValue , że jest to metoda EF Core, co oznacza, że program EF Core natychmiast wie o zmianie wprowadzonej przez tę metodę. Podobnie wywołanie DbContext.Add umożliwia programowi EF Core natychmiastowe poznanie nowej jednostki i odpowiednie śledzenie jej.
Wskazówka
Nie próbuj unikać wykrywania zmian, zawsze używając metod EF Core do wprowadzania zmian jednostek. Takie działanie jest często bardziej kłopotliwe i działa mniej dobrze niż wprowadzanie zmian w jednostkach w normalny sposób. Celem tego dokumentu jest informowanie o tym, kiedy jest konieczne wykrywanie zmian, a kiedy nie. Celem nie jest zachęcanie do unikania wykrywania zmian.
Metody, które automatycznie wykrywają zmiany
DetectChanges() jest wywoływane automatycznie przez metody, w których może to wpływać na wyniki. Oto następujące metody:
- DbContext.SaveChanges i DbContext.SaveChangesAsync, aby upewnić się, że wszystkie zmiany zostaną wykryte przed zaktualizowaniem bazy danych.
- ChangeTracker.Entries() i ChangeTracker.Entries<TEntity>(), aby upewnić się, że stany jednostek i zmodyfikowane właściwości są up-to-date.
- ChangeTracker.HasChanges(), aby upewnić się, że wynik jest dokładny.
- ChangeTracker.CascadeChanges(), aby zapewnić prawidłowe stany jednostek dla jednostek głównych/nadrzędnych przed kaskadą.
- DbSet<TEntity>.Local, aby upewnić się, że śledzony wykres jest up-to-date.
Istnieją również pewne miejsca, w których wykrywanie zmian odbywa się tylko w pojedynczym wystąpieniu jednostki, a nie na całym grafie śledzonych jednostek. Te miejsca to:
- W przypadku używania polecenia DbContext.Entryupewnij się, że stan i zmodyfikowane właściwości jednostki są up-to-date.
- W przypadku używania EntityEntry metod, takich jak
Property,Collection,ReferencelubMemberw celu zapewnienia modyfikacji właściwości, bieżące wartości itp. są up-to-date. - Gdy jednostka zależna/podrzędna zostanie usunięta, ponieważ wymagana relacja została zerwana. Wykrywa, kiedy nie należy usuwać jednostki, ponieważ została przypisana do nowego nadrzędnego.
Lokalne wykrywanie zmian dla pojedynczej jednostki może być wyzwalane jawnie przez wywołanie metody EntityEntry.DetectChanges().
Uwaga / Notatka
Lokalne wykrywanie zmian może przegapić niektóre zmiany, które można znaleźć w pełnym wykryciu. Dzieje się tak, gdy akcje kaskadowe wynikające z niezakrytych zmian w innych jednostkach mają wpływ na określoną jednostkę. W takich sytuacjach aplikacja może wymagać wymuszenia pełnego skanowania wszystkich jednostek przez jawne wywołanie metody ChangeTracker.DetectChanges().
Wyłączanie automatycznego wykrywania zmian
Wydajność wykrywania zmian nie jest wąskim gardłem dla większości aplikacji. Jednak wykrywanie zmian może stać się problemem z wydajnością niektórych aplikacji, które śledzą tysiące jednostek. (Dokładna liczba będzie zależeć od wielu elementów, takich jak liczba właściwości w jednostce). Z tego powodu automatyczne wykrywanie zmian można wyłączyć przy użyciu polecenia ChangeTracker.AutoDetectChangesEnabled. Rozważmy na przykład przetwarzanie jednostek sprzężenia w relacji wiele-do-wielu z ładunkami:
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
foreach (var entityEntry in ChangeTracker.Entries<PostTag>()) // Detects changes automatically
{
if (entityEntry.State == EntityState.Added)
{
entityEntry.Entity.TaggedBy = "ajcvickers";
entityEntry.Entity.TaggedOn = DateTime.Now;
}
}
try
{
ChangeTracker.AutoDetectChangesEnabled = false;
return await base.SaveChangesAsync(cancellationToken); // Avoid automatically detecting changes again here
}
finally
{
ChangeTracker.AutoDetectChangesEnabled = true;
}
}
Jak wiemy z poprzedniej sekcji, zarówno ChangeTracker.Entries<TEntity>() i DbContext.SaveChanges automatycznie wykrywają zmiany. Jednak po wywołaniu funkcji Entries kod nie wprowadza żadnych zmian stanu encji ani właściwości. (Ustawienie normalnych wartości właściwości w dodanych jednostkach nie powoduje żadnych zmian stanu). W związku z tym kod wyłącza niepotrzebne automatyczne wykrywanie zmian podczas wywoływania do podstawowej metody SaveChanges. Kod korzysta również z bloku try/finally, aby upewnić się, że ustawienie domyślne zostanie przywrócone, nawet jeśli funkcja SaveChanges zakończy się niepowodzeniem.
Wskazówka
Nie zakładaj, że kod musi wyłączyć automatyczne wykrywanie zmian w celu zapewnienia dobrego działania. Jest to konieczne tylko wtedy, gdy profilowanie aplikacji śledzącej wiele jednostek wskazuje, że wydajność wykrywania zmian jest problemem.
Wykrywanie zmian i konwersji wartości
Aby korzystać ze śledzenia zmian migawki z typem jednostki, program EF Core musi mieć następujące możliwości:
- Zrób migawkę każdej wartości właściwości, gdy obiekt jest śledzony.
- Porównaj tę wartość z bieżącą wartością właściwości
- Generowanie kodu skrótu dla wartości
Jest to obsługiwane automatycznie przez program EF Core dla typów, które można bezpośrednio zamapować do bazy danych. Jednak gdy konwerter wartości jest używany do mapowania właściwości, ten konwerter musi określić sposób wykonywania tych akcji. Jest to osiągane za pomocą komparatora wartości i jest szczegółowo opisane w dokumentacji Porównywarek Wartości.
Jednostki powiadomień
Zalecane jest śledzenie zmian za pomocą migawek dla większości aplikacji. Jednak aplikacje, które śledzą wiele jednostek i/lub wprowadzają wiele zmian w tych jednostkach, mogą korzystać z implementacji jednostek, które automatycznie powiadamiają program EF Core o zmianie ich właściwości i wartości nawigacji. Są one nazywane "jednostkami powiadomień".
Implementowanie jednostek powiadomień
Jednostki powiadomień korzystają z interfejsów INotifyPropertyChanging i INotifyPropertyChanged, które są częścią biblioteki klas bazowych platformy .NET (BCL). Te interfejsy definiują zdarzenia, które muszą zostać wyzwolone przed i po zmianie wartości właściwości. Przykład:
public class Blog : INotifyPropertyChanging, INotifyPropertyChanged
{
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
public int Id
{
get => _id;
set
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Id)));
_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Id)));
}
}
private string _name;
public string Name
{
get => _name;
set
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Name)));
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public IList<Post> Posts { get; } = new ObservableCollection<Post>();
}
Ponadto wszystkie nawigacje kolekcji muszą implementować INotifyCollectionChanged; w powyższym przykładzie spełnia to poprzez użycie ObservableCollection<T> w ramach postów. Program EF Core jest również dostarczany z implementacją ObservableHashSet<T> , która ma bardziej wydajne wyszukiwanie kosztem stabilnego zamawiania.
Większość tego kodu powiadomień jest zwykle przenoszona do niezamapowanej klasy bazowej. Przykład:
public class Blog : NotifyingEntity
{
private int _id;
public int Id
{
get => _id;
set => SetWithNotify(value, out _id);
}
private string _name;
public string Name
{
get => _name;
set => SetWithNotify(value, out _name);
}
public IList<Post> Posts { get; } = new ObservableCollection<Post>();
}
public abstract class NotifyingEntity : INotifyPropertyChanging, INotifyPropertyChanged
{
protected void SetWithNotify<T>(T value, out T field, [CallerMemberName] string propertyName = "")
{
NotifyChanging(propertyName);
field = value;
NotifyChanged(propertyName);
}
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private void NotifyChanging(string propertyName)
=> PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
}
Konfigurowanie jednostek powiadomień
Nie ma sposobu, aby EF Core mógł zweryfikować, czy INotifyPropertyChanging lub INotifyPropertyChanged są w pełni zaimplementowane do użytku z EF Core. Niektóre zastosowania tych interfejsów polegają na używaniu powiadomień tylko dla pewnych właściwości, zamiast dla wszystkich właściwości, w tym nawigacji, zgodnie z wymaganiami EF Core. Z tego powodu program EF Core nie jest automatycznie podłączany do tych zdarzeń.
Zamiast tego należy skonfigurować program EF Core do korzystania z tych jednostek powiadomień. Zwykle odbywa się to dla wszystkich typów jednostek przez wywołanie metody ModelBuilder.HasChangeTrackingStrategy. Przykład:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications);
}
(Strategię można również ustawić inaczej dla różnych typów jednostek za pomocą EntityTypeBuilder.HasChangeTrackingStrategy, ale zazwyczaj jest to nieproduktywne, ponieważ funkcja DetectChanges jest wciąż wymagana dla tych typów, które nie są jednostkami powiadomienia.)
Pełne śledzenie zmian powiadomień wymaga zaimplementowania obu INotifyPropertyChanging oraz INotifyPropertyChanged. Umożliwia to zapisanie oryginalnych wartości tuż przed zmianą wartości właściwości, co pozwala uniknąć konieczności utworzenia migawki przez program EF Core podczas śledzenia jednostki. Typy jednostek implementujące tylko INotifyPropertyChanged mogą być również używane z platformą EF Core. W tym przypadku EF nadal tworzy migawkę podczas śledzenia encji w celu monitorowania oryginalnych wartości, ale następnie używa powiadomień do natychmiastowego wykrywania zmian, bez potrzeby wywoływania funkcji DetectChanges.
Różne ChangeTrackingStrategy wartości zostały podsumowane w poniższej tabeli.
| Strategia Śledzenia Zmian | Wymagane interfejsy | Wymaga wykrywania zmian | Migawki oryginalnych wartości |
|---|---|---|---|
| Migawka | Żaden | Tak | Tak |
| Powiadomienia o zmianach | INotifyPropertyChanged | Nie. | Tak |
| ZmianaandChangedNotifications | INotifyPropertyChanged i INotifyPropertyChanging | Nie. | Nie. |
| Powiadomienia o zmianach i zmienionych wartościach z oryginalnymi wartościami | INotifyPropertyChanged i INotifyPropertyChanging | Nie. | Tak |
Korzystanie z jednostek powiadomień
Jednostki powiadomień zachowują się jak inne jednostki, z tą różnicą, że zmiany w instancjach jednostek nie wymagają wywołania do ChangeTracker.DetectChanges() w celu ich wykrycia. Przykład:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
blog.Name = ".NET Blog (Updated!)";
// Add a new entity to a navigation
blog.Posts.Add(
new Post
{
Title = "What’s next for System.Text.Json?", Content = ".NET 5.0 was released recently and has come with many..."
});
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
W przypadku normalnych jednostek widok debugowania monitora zmian pokazał, że te zmiany nie zostały wykryte do momentu wywołania funkcji DetectChanges. Patrząc na widok debugowania, gdy używane są jednostki powiadomień, widać, że te zmiany zostały wykryte natychmiast:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482643}]
Post {Id: -2147482643} Added
Id: -2147482643 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 was released recently and has come with many...'
Title: 'What's next for System.Text.Json?'
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...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Proxy śledzenia zmian
Program EF Core może dynamicznie generować typy serwerów proxy implementujące INotifyPropertyChanging i INotifyPropertyChanged. Wymaga to zainstalowania pakietu NuGet Microsoft.EntityFrameworkCore.Proxies i włączenia proxy śledzenia zmian. Na przykład:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseChangeTrackingProxies();
Tworzenie dynamicznego serwera proxy obejmuje utworzenie nowego, dynamicznego typu platformy .NET (przy użyciu implementacji serwerów proxy Castle.Core ), który dziedziczy z typu jednostki, a następnie zastępuje wszystkie zestawy właściwości. W związku z tym typy jednostek dla serwerów proxy muszą być typami, które mogą być dziedziczone z i muszą mieć właściwości, które mogą być zastępowane. Ponadto utworzone jawnie nawigacje kolekcji muszą implementować INotifyCollectionChanged, na przykład:
public class Blog
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Post> Posts { get; } = new ObservableCollection<Post>();
}
public class Post
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual string Content { get; set; }
public virtual int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
Jednym z znaczących wad śledzenia zmian serwerów proxy jest to, że program EF Core musi zawsze śledzić wystąpienia serwera proxy, nigdy wystąpienia bazowego typu jednostki. Dzieje się tak, ponieważ wystąpienia bazowego typu jednostki nie będą generować powiadomień, co oznacza, że zmiany wprowadzone w tych jednostkach zostaną pominięte.
EF Core automatycznie tworzy archiwa zastępcze podczas zapytań do bazy danych, więc ta wada jest zazwyczaj ograniczona do śledzenia nowych jednostek. Te wystąpienia muszą być tworzone przy użyciu metod rozszerzeń CreateProxy, a nie w normalny sposób przy użyciu new. Oznacza to, że kod z poprzednich przykładów musi teraz używać elementu CreateProxy:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
blog.Name = ".NET Blog (Updated!)";
// Add a new entity to a navigation
blog.Posts.Add(
context.CreateProxy<Post>(
p =>
{
p.Title = "What’s next for System.Text.Json?";
p.Content = ".NET 5.0 was released recently and has come with many...";
}));
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Zdarzenia śledzenia zmian
EF Core uruchamia zdarzenie ChangeTracker.Tracked, gdy jednostka jest śledzona po raz pierwszy. Przyszłe zmiany stanu jednostki powodują zdarzenia ChangeTracker.StateChanged . Aby uzyskać więcej informacji, zobacz Zdarzenia platformy .NET w programie EF Core .
Uwaga / Notatka
Zdarzenie StateChanged nie jest uruchamiane, kiedy obiekt jest po raz pierwszy śledzony, mimo że jego stan zmienił się z Detached na jeden z pozostałych stanów. Pamiętaj, aby nasłuchiwać zarówno zdarzeń StateChanged, jak i Tracked w celu otrzymywania wszystkich odpowiednich powiadomień.