Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Elk DbContext exemplaar houdt wijzigingen bij die in entiteiten zijn aangebracht. Deze bijgehouden entiteiten sturen op hun beurt de wijzigingen in de database aan wanneer SaveChanges wordt aangeroepen. Dit wordt behandeld in Wijzigingen bijhouden in EF Core. In dit document wordt ervan uitgegaan dat entiteitsstatussen en de basisprincipes van Het bijhouden van wijzigingen in Entity Framework Core (EF Core) worden begrepen.
Het bijhouden van eigenschaps- en relatiewijzigingen vereist dat dbContext deze wijzigingen kan detecteren. In dit document wordt beschreven hoe deze detectie plaatsvindt, en hoe u eigenschapsmeldingen of wijzigingsproxy's gebruikt om onmiddellijke detectie van wijzigingen af te dwingen.
Aanbeveling
U kunt alle code in dit document uitvoeren en fouten opsporen door de voorbeeldcode van GitHub te downloaden.
Wijzigingen bijhouden van momentopnamen
Standaard maakt EF Core een momentopname van de eigenschapswaarden van elke entiteit wanneer deze voor het eerst wordt bijgehouden door een DbContext-exemplaar. De waarden die in deze momentopname zijn opgeslagen, worden vervolgens vergeleken met de huidige waarden van de entiteit om te bepalen welke eigenschapswaarden zijn gewijzigd.
Deze detectie van wijzigingen vindt plaats wanneer SaveChanges wordt aangeroepen om ervoor te zorgen dat alle gewijzigde waarden worden gedetecteerd voordat updates naar de database worden verzonden. De detectie van wijzigingen vindt echter ook op andere momenten plaats om ervoor te zorgen dat de toepassing werkt met up-to-datumtraceringsgegevens. Detectie van wijzigingen kan op elk gewenst moment worden afgedwongen door aan te roepen ChangeTracker.DetectChanges().
Wanneer wijzigingsdetectie nodig is
Detectie van wijzigingen is nodig wanneer een eigenschap of navigatie is gewijzigd zonder EF Core te gebruiken om deze wijziging aan te brengen. U kunt bijvoorbeeld blogs en berichten laden en vervolgens wijzigingen aanbrengen in deze entiteiten:
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);
Als u de foutopsporingsweergave voor wijzigingentrackers bekijkt voordat u aanroept ChangeTracker.DetectChanges() , ziet u dat de aangebrachte wijzigingen niet zijn gedetecteerd en daarom niet worden weergegeven in de entiteitsstatussen en gewijzigde eigenschapsgegevens:
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}
De status van het blogbericht is nog steeds Unchangeden het nieuwe bericht wordt niet weergegeven als een bijgehouden entiteit. (De astute ziet dat eigenschappen hun nieuwe waarden rapporteren, ook al zijn deze wijzigingen nog niet gedetecteerd door EF Core. Dit komt doordat de foutopsporingsweergave huidige waarden rechtstreeks vanuit het entiteitsexemplaar leest.)
Vergelijk dit met de foutopsporingsweergave na het aanroepen van 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}
De blog is nu correct gemarkeerd als Modified en het nieuwe bericht is gedetecteerd en wordt bijgehouden als Added.
Aan het begin van deze sectie hebben we aangegeven dat het detecteren van wijzigingen nodig is wanneer u EF Core niet gebruikt om de wijziging aan te brengen. Dit gebeurt in de bovenstaande code. Dat wil gezegd: de wijzigingen in de eigenschap en navigatie worden rechtstreeks op de entiteitsexemplaren aangebracht en niet met behulp van EF Core-methoden.
Vergelijk dit met de volgende code die de entiteiten op dezelfde manier wijzigt, maar deze keer met EF Core-methoden:
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);
In dit geval geeft de foutopsporingsweergave voor wijzigingentracker aan dat alle entiteitsstatussen en eigenschapswijzigingen bekend zijn, ook al is de detectie van wijzigingen niet gebeurd. Dit komt doordat PropertyEntry.CurrentValue het een EF Core-methode is, wat betekent dat EF Core onmiddellijk weet wat de wijziging is die door deze methode is aangebracht. DbContext.Add Op dezelfde manier kan EF Core onmiddellijk op de hoogte zijn van de nieuwe entiteit en deze op de juiste manier bijhouden.
Aanbeveling
Probeer geen wijzigingen te detecteren door altijd EF Core-methoden te gebruiken om entiteitswijzigingen aan te brengen. Dit is vaak lastiger en presteert minder goed dan het op de normale manier aanbrengen van wijzigingen aan entiteiten. Het doel van dit document is om u te informeren over het detecteren van wijzigingen en wanneer dit niet het doel is. Het is de bedoeling om het vermijden van wijzigingsdetectie niet aan te moedigen.
Methoden waarmee wijzigingen automatisch worden gedetecteerd
DetectChanges() wordt automatisch aangeroepen door methoden waarbij dit waarschijnlijk van invloed is op de resultaten. Deze methoden zijn:
- DbContext.SaveChanges en DbContext.SaveChangesAsync, om ervoor te zorgen dat alle wijzigingen worden gedetecteerd voordat de database wordt bijgewerkt.
- ChangeTracker.Entries() en ChangeTracker.Entries<TEntity>(), om ervoor te zorgen dat de entiteitsstatussen en gewijzigde eigenschappen up-tobijgewerkt zijn.
- ChangeTracker.HasChanges(), om ervoor te zorgen dat het resultaat nauwkeurig is.
- ChangeTracker.CascadeChanges(), om ervoor te zorgen dat de juiste entiteitsstatussen voor hoofd-/oudere entiteiten correct zijn voordat er wordt gecascadeerd.
- DbSet<TEntity>.Localom ervoor te zorgen dat de bijgehouden grafiek up-to-date is.
Er zijn ook enkele plaatsen waar de detectie van wijzigingen plaatsvindt op slechts één entiteitexemplaar, in plaats van in de hele grafiek van bijgehouden entiteiten. Dit zijn de volgende plaatsen:
- Wanneer u DbContext.Entry gebruikt, moet u ervoor zorgen dat de staat en gewijzigde eigenschappen van de entiteit up-to-datum zijn.
- Wanneer u EntityEntry methoden zoals
Property,CollectionofReferenceMemberom ervoor te zorgen dat eigenschapswijzigingen, huidige waarden enzovoort worden up-to-datum. - Wanneer een afhankelijke/onderliggende entiteit wordt verwijderd omdat er een vereiste relatie is verbroken. Hiermee wordt gedetecteerd wanneer een entiteit niet mag worden verwijderd omdat deze opnieuw is gekoppeld.
Lokale detectie van wijzigingen voor één entiteit kan expliciet worden geactiveerd door aan te roepen EntityEntry.DetectChanges().
Opmerking
Lokale detectiewijzigingen kunnen enkele wijzigingen missen die een volledige detectie zou vinden. Dit gebeurt wanneer trapsgewijze acties als gevolg van niet-gedetecteerde wijzigingen in andere entiteiten invloed hebben op de betreffende entiteit. In dergelijke situaties moet de toepassing mogelijk een volledige scan van alle entiteiten afdwingen door expliciet aan te roepen ChangeTracker.DetectChanges().
Automatische wijzigingsdetectie uitschakelen
De prestaties van het detecteren van wijzigingen zijn geen knelpunt voor de meeste toepassingen. Het detecteren van wijzigingen kan echter een prestatieprobleem worden voor sommige toepassingen die duizenden entiteiten bijhouden. (Het exacte getal is afhankelijk van veel dingen, zoals het aantal eigenschappen in de entiteit.) Daarom kan de automatische detectie van wijzigingen worden uitgeschakeld met behulp van ChangeTracker.AutoDetectChangesEnabled. Denk bijvoorbeeld aan het verwerken van join-entiteiten in een veel-op-veel-relatie met gegevens:
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;
}
}
Zoals we uit de vorige sectie weten, detecteren zowel ChangeTracker.Entries<TEntity>() als DbContext.SaveChanges automatisch wijzigingen. Na het aanroepen van invoer past de code daarna echter geen wijzigingen toe in de status van een entiteit of eigenschap. (Het instellen van normale eigenschapswaarden voor toegevoegde entiteiten veroorzaakt geen statuswijzigingen.) De code schakelt daarom onnodige automatische wijzigingsdetectie uit bij het aanroepen naar de basismethode SaveChanges. De code maakt ook gebruik van een try/finally-blok om ervoor te zorgen dat de standaardinstelling wordt hersteld, zelfs als SaveChanges mislukt.
Aanbeveling
Neem niet aan dat uw code automatische wijzigingsdetectie moet uitschakelen om goed te presteren. Dit is alleen nodig wanneer uit het profileren van een toepassing die veel entiteiten bijhoudt blijkt dat de prestaties van veranderingen detectie een probleem zijn.
Wijzigingen en waardeconversies detecteren
Als u het bijhouden van momentopnamen wilt gebruiken met een entiteitstype, moet EF Core het volgende kunnen doen:
- Een momentopname maken van elke eigenschapswaarde wanneer de entiteit wordt bijgehouden
- Deze waarde vergelijken met de huidige waarde van de eigenschap
- Een hashcode genereren voor de waarde
Dit wordt automatisch verwerkt door EF Core voor typen die rechtstreeks aan de database kunnen worden toegewezen. Wanneer echter een waardeconversieprogramma wordt gebruikt om een eigenschap toe te wijzen, moet dat conversieprogramma opgeven hoe deze acties moeten worden uitgevoerd. Dit wordt bereikt met een waarde-comparer en wordt uitgebreid beschreven in de documentatie van Value Comparers .
Meldingsentiteiten
Het bijhouden van wijzigingen in momentopnamen wordt aanbevolen voor de meeste toepassingen. Toepassingen die veel entiteiten bijhouden en/of veel wijzigingen aanbrengen in deze entiteiten, kunnen echter profiteren van het implementeren van entiteiten die EF Core automatisch waarschuwen wanneer hun eigenschap en navigatiewaarden veranderen. Deze worden 'meldingsentiteiten' genoemd.
Meldingsentiteiten implementeren
Meldingsentiteiten maken gebruik van de INotifyPropertyChanging en INotifyPropertyChanged interfaces, die deel uitmaken van de .NET Base Class Library (BCL). Deze interfaces definiëren gebeurtenissen die vóór en na het wijzigen van een eigenschapswaarde moeten worden geactiveerd. Voorbeeld:
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>();
}
Bovendien moeten alle verzamelingsnavigaties worden geïmplementeerd INotifyCollectionChanged; in het bovenstaande voorbeeld wordt hieraan voldaan door het gebruik van een ObservableCollection<T> van berichten. EF Core wordt ook geleverd met een ObservableHashSet<T> implementatie die efficiëntere zoekopdrachten biedt ten koste van een stabiele volgorde.
De meeste van deze meldingscode wordt doorgaans verplaatst naar een niet-toegewezen basisklasse. Voorbeeld:
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));
}
Meldingsentiteiten configureren
Ef Core kan niet valideren dat INotifyPropertyChanging of INotifyPropertyChanged volledig zijn geïmplementeerd voor gebruik met EF Core. Sommige toepassingen van deze interfaces doen dit met name met meldingen alleen voor bepaalde eigenschappen, in plaats van op alle eigenschappen (inclusief navigaties) zoals vereist door EF Core. Daarom koppelt EF Core niet automatisch aan deze gebeurtenissen.
In plaats daarvan moet EF Core worden geconfigureerd voor het gebruik van deze meldingsentiteiten. Dit wordt meestal gedaan voor alle entiteitstypen door aan te roepen ModelBuilder.HasChangeTrackingStrategy. Voorbeeld:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications);
}
(De strategie kan ook anders worden ingesteld voor verschillende entiteitstypen, EntityTypeBuilder.HasChangeTrackingStrategymaar dit is meestal contraproductief omdat DetectChanges nog steeds vereist is voor die typen die geen meldingsentiteiten zijn.)
Voor het bijhouden van wijzigingen in volledige meldingen is vereist dat zowel INotifyPropertyChanging als INotifyPropertyChanged worden geïmplementeerd. Hierdoor kunnen oorspronkelijke waarden worden opgeslagen vlak voordat de eigenschapswaarde wordt gewijzigd, waardoor EF Core geen momentopname hoeft te maken bij het bijhouden van de entiteit. Entiteitstypen die alleen INotifyPropertyChanged worden geïmplementeerd, kunnen ook worden gebruikt met EF Core. In dit geval maakt EF nog steeds een momentopname bij het bijhouden van een entiteit om de oorspronkelijke waarden bij te houden, maar gebruikt vervolgens de meldingen om wijzigingen onmiddellijk te detecteren, in plaats van DetectChanges aan te roepen.
De verschillende ChangeTrackingStrategy waarden worden samengevat in de volgende tabel.
| Wijzigingsvolgstrategie | Er zijn interfaces nodig | Heeft DetectChanges nodig | Oorspronkelijke waarden voor momentopnamen |
|---|---|---|---|
| Momentopname | Geen | Ja | Ja |
| GewijzigdeMeldingen | INotifyPropertyChanged | Nee. | Ja |
| MeldingenVanWijzigingenEnGewijzigdeStaten | INotifyPropertyChanged en INotifyPropertyChanging | Nee. | Nee. |
| Meldingen van Wijzigen en Gewijzigde Waarden met Oorspronkelijke Waarden | INotifyPropertyChanged en INotifyPropertyChanging | Nee. | Ja |
Meldingsentiteiten gebruiken
Meldingsentiteiten gedragen zich net als andere entiteiten, behalve dat je wijzigingen in de entiteitexemplaren kunt aanbrengen zonder een aanroep van ChangeTracker.DetectChanges() nodig te hebben om deze wijzigingen te detecteren. Voorbeeld:
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);
Met normale entiteiten heeft de foutopsporingsweergave voor wijzigingentrackers aangetoond dat deze wijzigingen pas zijn gedetecteerd nadat DetectChanges werd aangeroepen. Als u de foutopsporingsweergave bekijkt wanneer meldingsentiteiten worden gebruikt, ziet u dat deze wijzigingen onmiddellijk zijn gedetecteerd:
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}
Proxies voor het volgen van wijzigingen
EF Core kan dynamisch proxytypen genereren die implementeren INotifyPropertyChanging en INotifyPropertyChanged. Hiervoor moet u het NuGet-pakket Microsoft.EntityFrameworkCore.Proxies installeren en proxy's voor het bijhouden van wijzigingen inschakelen met UseChangeTrackingProxies bijvoorbeeld:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseChangeTrackingProxies();
Het maken van een dynamische proxy omvat het maken van een nieuw, dynamisch .NET-type (met behulp van de implementatie van Castle.Core-proxy's ), die overgaat van het entiteitstype en vervolgens alle eigenschappensetters overschrijft. Entiteitstypen voor proxy's moeten daarom typen zijn waaruit ze kunnen worden overgenomen en die eigenschappen moeten hebben die kunnen worden overschreven. Ook moeten verzamelingsnavigaties die expliciet zijn gemaakt INotifyCollectionChanged implementeren, bijvoorbeeld:
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; }
}
Een belangrijk nadeel van proxy's voor het bijhouden van wijzigingen is dat EF Core altijd exemplaren van de proxy moet bijhouden, nooit exemplaren van het onderliggende entiteitstype. Dit komt doordat exemplaren van het onderliggende entiteitstype geen meldingen genereren, wat betekent dat wijzigingen in deze entiteiten worden gemist.
EF Core maakt automatisch proxy-exemplaren bij het uitvoeren van query's op de database, dus dit nadeel is over het algemeen beperkt tot het bijhouden van nieuwe entiteitsexemplaren. Deze exemplaren moeten worden gemaakt met behulp van de CreateProxy extensiemethoden en niet op de normale manier met new. Dit betekent dat de code uit de vorige voorbeelden nu gebruik moet maken van 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);
Wijzigingen bijhouden van gebeurtenissen
EF Core activeert de ChangeTracker.Tracked gebeurtenis wanneer een entiteit voor de eerste keer wordt bijgehouden. Toekomstige wijzigingen in entiteitsstatussen resulteren in ChangeTracker.StateChanged gebeurtenissen. Zie .NET-gebeurtenissen in EF Core voor meer informatie.
Opmerking
De StateChanged gebeurtenis wordt niet geactiveerd wanneer een entiteit voor het eerst wordt bijgehouden, ook al is de status gewijzigd van Detached naar een van de andere statussen. Zorg ervoor dat u luistert naar zowel StateChanged als Tracked gebeurtenissen om alle relevante meldingen te ontvangen.