Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Každá instance DbContext sleduje změny provedené u entit. Tyto sledované entity následně řídí změny v databázi při volání SaveChanges. Toto je pokryto v Change Tracking v EF Core a tento dokument předpokládá, že jsou pochopeny ty stavy entit a základy sledování změn v Entity Framework Core (EF Core).
Sledování změn vlastností a relací vyžaduje, aby dbContext mohl tyto změny rozpoznat. Tento dokument popisuje, jak k této detekci dochází, a také o tom, jak používat oznámení vlastností nebo proxy servery pro sledování změn k vynucení okamžité detekce změn.
Návod
Veškerý kód v tomto dokumentu můžete spustit a ladit stažením ukázkového kódu z GitHubu.
Sledování změn snímků
Ef Core ve výchozím nastavení vytvoří snímek hodnot vlastností každé entity při prvním sledování instancí DbContext. Hodnoty uložené v tomto snímku se pak porovnávají s aktuálními hodnotami entity, aby bylo možné určit, které hodnoty vlastností se změnily.
K této detekci změn dochází, když je volána funkce SaveChanges, aby se před odesláním aktualizací do databáze zjistily všechny změněné hodnoty. Detekce změn se ale provádí i v jiných časech, aby aplikace pracovala s daty sledování up-to-data. Detekce změn může být vynucena kdykoli voláním ChangeTracker.DetectChanges().
Pokud je potřeba detekce změn
Detekce změn je nutná v případě, že došlo ke změně vlastnosti nebo navigace bez použití EF Core k provedení této změny. Zvažte například načtení blogů a příspěvků a následné změny těchto entit:
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);
Zobrazení ladicího zobrazení sledování změn před voláním ChangeTracker.DetectChanges() ukazuje, že provedené změny nebyly identifikovány, a proto se neprojevují ve stavech entit a údajích upravených vlastností.
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}
Konkrétně stav příspěvku blogu je stále Unchanged a nový příspěvek se nezobrazuje jako sledovaná entita. (Pozorní si všimnou, že vlastnosti hlásí nové hodnoty, i když EF Core ještě tyto změny nezjistil. Důvodem je to, že ladicí pohled čte aktuální hodnoty přímo z instance entity.)
Porovnejte to se zobrazením ladění po volání 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}
Nyní blog je správně označen jako Modified a nový příspěvek byl zjištěn a je sledován jako Added.
Na začátku této části jsme uvedli, že zjišťování změn je potřeba, když k provedení této změny nepoužíváte EF Core. To se děje ve výše uvedeném kódu. To znamená, že změny vlastnosti a navigace jsou provedeny přímo na instancích entit, a ne pomocí žádné metody EF Core.
Porovnejte to s následujícím kódem, který upravuje entity stejným způsobem, ale tentokrát pomocí 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);
V tomto případě ladící zobrazení trackování změn ukazuje, že jsou známy všechny stavy entit a úpravy vlastností, i když změny nebyly detekovány. Důvodem je to, že PropertyEntry.CurrentValue se jedná o metodu EF Core, což znamená, že EF Core okamžitě ví o změně provedené touto metodou. Podobně volání DbContext.Add umožňuje EF Core okamžitě vědět o nové entitě a správně ji sledovat.
Návod
Nepokoušejte se vyhnout detekci změn tím, že byste vždy používali jen metody EF Core ke změnám entit. To je často těžkopádnější a funguje méně dobře než provádění změn entit běžným způsobem. Záměrem tohoto dokumentu je informovat o tom, kdy je potřeba zjišťovat změny a kdy ne. Záměrem není podporovat vyhýbání se detekci změn.
Metody, které automaticky detekují změny
DetectChanges() se volá automaticky metodami, ve kterých to pravděpodobně ovlivní výsledky. Tyto metody jsou:
- DbContext.SaveChanges a DbContext.SaveChangesAsync, aby se zajistilo, že se před aktualizací databáze zjistí všechny změny.
- ChangeTracker.Entries() a ChangeTracker.Entries<TEntity>(), aby se zajistilo, že stavy entit a změněné vlastnosti jsou up-to-date.
- ChangeTracker.HasChanges(), aby byl výsledek přesný.
- ChangeTracker.CascadeChanges(), aby se zajistily správné stavy entit pro hlavní/nadřazené entity před zahájením procesu kaskádování.
- DbSet<TEntity>.Local, aby se zajistilo, že sledovaný graf je up-to-date.
Existuje také několik míst, kde se detekce změn odehrává pouze u jedné instance entity, nikoli v celém grafu sledovaných entit. Tato místa jsou:
- Při použití DbContext.Entryzajistíte, aby stav a změněné vlastnosti entity byly up-to-date.
- Při použití metod, jako EntityEntry,
Property,CollectionneboReference, k zajištění úprav vlastností, aktuálních hodnot atd., jsou up-to-date aktuální. - Když má být závislá nebo podřízená entita odstraněna, protože požadovaná relace byla přerušena. Zjistí se, kdy by se entita neměla odstranit, protože byla znovu nadřazená.
Místní detekce změn pro jednu entitu se dá aktivovat explicitně voláním EntityEntry.DetectChanges().
Poznámka:
Místní detekce změn může vynechat některé změny, které by bylo možné najít v úplné detekci. K tomu dochází v případě, že kaskádové akce vyplývající z nedetekovaných změn jiných entit mají vliv na danou entitu. V takových situacích může aplikace muset vynutit úplné prohledávání všech entit explicitním voláním ChangeTracker.DetectChanges().
Zakázání automatické detekce změn
Výkon detekce změn není kritickým bodem většiny aplikací. Detekce změn se ale může stát problémem s výkonem u některých aplikací, které sledují tisíce entit. (Přesné číslo bude záviset na mnoha věcech, například na počtu vlastností v entitě.) Z tohoto důvodu lze automatickou detekci změn zakázat pomocí ChangeTracker.AutoDetectChangesEnabled. Zvažte například zpracování entit spojení ve vztahu typu mnoho na mnoho s datovými částmi.
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 víme z předchozí části, obě ChangeTracker.Entries<TEntity>() a DbContext.SaveChanges automaticky detekují změny. Po volání položek však kód neprovádí žádné změny stavu entity nebo vlastnosti. (Nastavení normálních hodnot vlastností u přidaných entit nezpůsobí žádné změny stavu.) Kód proto zakáže zbytečné automatické zjišťování změn při volání do základní metody SaveChanges. Kód také používá blok try/finally, aby se zajistilo, že se obnoví výchozí nastavení i v případě, že se funkce SaveChanges nezdaří.
Návod
Nepředpokládáte, že váš kód musí zakázat automatické zjišťování změn, aby fungoval dobře. To je potřeba jenom v případě, že profilace aplikace sledující mnoho entit značí, že je problém s výkonem detekce změn.
Detekce změn a převodů hodnot
Pokud chcete použít sledování změn snímků s typem entity, musí být EF Core schopné:
- Udělejte snímek každé hodnoty vlastnosti, když je entita sledována.
- Porovnejte tuto hodnotu s aktuální hodnotou vlastnosti.
- Vygenerování kódu hash pro hodnotu
Ef Core to zpracovává automaticky pro typy, které je možné přímo namapovat na databázi. Pokud se však k mapování vlastnosti používá převaděč hodnot, musí tento převaděč určit, jak tyto akce provést. Toho dosáhnete pomocí porovnávače hodnot a podrobně je popsáno v dokumentaci k porovnání hodnot .
Subjekty oznámení
Sledování změn snímků se doporučuje pro většinu aplikací. Aplikace, které sledují mnoho entit nebo provádějí mnoho změn těchto entit, ale můžou mít prospěch z implementace entit, které ef Core automaticky upozorňují na změnu jejich vlastností a navigačních hodnot. Tyto entity se označují jako "entity oznámení".
Implementace oznamovacích entit
Oznamovací entity využívají rozhraní INotifyPropertyChanging a INotifyPropertyChanged, která jsou součástí knihovny základních tříd .NET (BCL). Tato rozhraní definují události, které se musí aktivovat před a po změně hodnoty vlastnosti. Například:
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>();
}
Kromě toho musí být implementovány INotifyCollectionChanged všechny navigace kolekce; v příkladu výše je tento požadavek splněn použitím ObservableCollection<T> příspěvků. EF Core se také dodává s ObservableHashSet<T> implementací, která má efektivnější vyhledávání na úkor stabilního řazení.
Většina tohoto kódu oznámení se obvykle přesune do nemapované základní třídy. Například:
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));
}
Konfigurace entit oznámení
Ef Core neexistuje způsob, jak ověřit, že INotifyPropertyChanging nebo INotifyPropertyChanged jsou plně implementovány pro použití s EF Core. Zejména některé použití těchto rozhraní používají oznámení pouze na určitých vlastnostech, nikoli na všech vlastnostech (včetně navigací), jak vyžaduje EF Core. Z tohoto důvodu se EF Core automaticky nepřichytá k těmto událostem.
Místo toho musí být EF Core nakonfigurované tak, aby používaly tyto entity oznámení. To se obvykle provádí pro všechny typy entit voláním ModelBuilder.HasChangeTrackingStrategy. Například:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications);
}
(Strategii lze také nastavit jinak pro různé typy entit, které používají EntityTypeBuilder.HasChangeTrackingStrategy, ale obvykle je to kontraproduktivní, protože Funkce DetectChanges je stále nutná pro tyto typy, které nejsou entitami oznámení.)
Sledování změn oznámení vyžaduje, aby byly implementovány obě INotifyPropertyChanging i INotifyPropertyChanged. Toto umožňuje ukládání původních hodnot bezprostředně před změnou hodnoty vlastnosti, čímž se odstraní nutnost, aby EF Core při sledování entity vytvářel snímek. Typy entit, které implementují pouze INotifyPropertyChanged, lze také použít s EF Core. V tomto případě EF stále vytvoří snímek při sledování entity, aby bylo možné sledovat původní hodnoty, ale pak pomocí oznámení detekuje změny okamžitě, a nemusí být volána funkce DetectChanges.
Různé ChangeTrackingStrategy hodnoty jsou shrnuty v následující tabulce.
| Strategie sledování změn | Potřebná rozhraní | Vyžaduje funkci DetectChanges | Snímky původních hodnot |
|---|---|---|---|
| Snímek | Žádné | Ano | Ano |
| ZměněnáOznámení | INotifyPropertyChanged | Ne | Ano |
| Oznámení o změnách a změněná oznámení | INotifyPropertyChanged a INotifyPropertyChanging | Ne | Ne |
| OznámeníOZměnáchAZměněnýchHodnotáchSPůvodnímiHodnotami | INotifyPropertyChanged a INotifyPropertyChanging | Ne | Ano |
Použití entit oznámení
Oznamovací entity se chovají stejně jako jakékoli jiné entity, s tím rozdílem, že změny instancí entit nevyžadují volání ChangeTracker.DetectChanges() k detekci těchto změn. Například:
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);
U normálních entit zobrazení ladění sledování změn ukázalo, že tyto změny nebyly zjištěny, dokud nebyla vyvolána funkce DetectChanges. Při pohledu na způsob ladění při použití entit oznámení se zobrazí, že tyto změny byly okamžitě zjištěny.
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 servery pro sledování změn
EF Core může dynamicky generovat typy proxy, které implementují INotifyPropertyChanging a INotifyPropertyChanged. Toto vyžaduje instalaci balíčku NuGet Microsoft.EntityFrameworkCore.Proxies a povolení proxy pro sledování změn, UseChangeTrackingProxies například:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseChangeTrackingProxies();
Vytvoření dynamického proxy serveru zahrnuje vytvoření nového dynamického typu .NET (pomocí implementace proxy serverů Castle.Core ), který dědí z typu entity a pak přepíše všechny setter vlastností. Typy entit pro proxy proto musí být typy, ze kterých lze dědit, a musí mít vlastnosti, které lze přepsat. Také navigace kolekcí vytvořené explicitně musí implementovat INotifyCollectionChanged například:
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; }
}
Jednou z významných nevýhod proxy serverů pro sledování změn je, že EF Core musí vždy sledovat instance proxy serveru, nikdy instance základního typu entity. Důvodem je to, že instance základního typu entity nebudou generovat oznámení, což znamená, že změny provedené v těchto entitách budou zmeškané.
EF Core vytváří proxy instance automaticky při dotazování databáze, takže tato nevýhoda je obecně omezená na sledování nových instancí entit. Tyto instance musí být vytvořeny pomocí CreateProxy rozšiřujících metod, a ne normálním způsobem pomocí new. To znamená, že kód z předchozích příkladů teď musí používat 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);
Události sledování změn
EF Core vyvolá událost ChangeTracker.Tracked, když je entita poprvé sledována. Budoucí změny stavu entity vedou k událostem ChangeTracker.StateChanged . Další informace najdete v tématu Události .NET v EF Core .
Poznámka:
Událost StateChanged se neaktivuje, když je entita poprvé sledována, i když se stav změnil z Detached jednoho z dalších stavů. Ujistěte se, že nasloucháte jak událostem StateChanged, tak Tracked, abyste získali všechna relevantní oznámení.