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.
Een DbContext-exemplaar houdt automatisch entiteiten bij die worden geretourneerd uit de database. Wijzigingen in deze entiteiten worden vervolgens gedetecteerd wanneer SaveChanges wordt aangeroepen en de database indien nodig wordt bijgewerkt. Zie Basisgegevens opslaan en Gerelateerde gegevens voor meer informatie.
Soms worden entiteiten echter opgevraagd met één contextinstantie en vervolgens opgeslagen met een andere instance. Dit gebeurt vaak in 'niet-verbonden' scenario's, zoals een webtoepassing waarop de entiteiten worden opgevraagd, verzonden naar de client, gewijzigd, teruggestuurd naar de server in een aanvraag en vervolgens worden opgeslagen. In dit geval moet de tweede contextinstantie weten of de entiteiten nieuw zijn (moeten worden ingevoegd) of bestaand (moeten worden bijgewerkt).
Aanbeveling
U kunt het voorbeeld van dit artikel bekijken op GitHub.
Aanbeveling
EF Core kan slechts één exemplaar van een entiteit bijhouden met een bepaalde primaire-sleutelwaarde. De beste manier om te voorkomen dat dit een probleem wordt, is door voor elke werkeenheid een kortdurende context te gebruiken, zodat de context leeg start, entiteiten worden eraan gekoppeld, deze entiteiten worden opgeslagen en vervolgens wordt de context opgeruimd en verwijderd.
Nieuwe entiteiten identificeren
Client identificeert nieuwe entiteiten
Het eenvoudigste geval is wanneer de client de server informeert of de entiteit nieuw of bestaand is. De aanvraag voor het invoegen van een nieuwe entiteit verschilt bijvoorbeeld vaak van de aanvraag om een bestaande entiteit bij te werken.
De rest van deze sectie behandelt de gevallen waarin het noodzakelijk is om op een andere manier te bepalen of u wilt invoegen of bijwerken.
Met automatisch gegenereerde sleutels
De waarde van een automatisch gegenereerde sleutel kan vaak worden gebruikt om te bepalen of een entiteit moet worden ingevoegd of bijgewerkt. Als de sleutel niet is ingesteld (dat wil zeggen, het heeft nog de CLR-standaardwaarde zoals null, nul, enzovoort), dan moet de entiteit nieuw zijn en ingevoegd worden. Als de sleutelwaarde daarentegen is ingesteld, moet deze al eerder zijn opgeslagen en moet deze nu worden bijgewerkt. Met andere woorden, als de sleutel een waarde heeft, is de entiteit opgevraagd, verzonden naar de client en is nu weer bijgewerkt.
Het is eenvoudig om te controleren op een niet-ingestelde sleutel wanneer het entiteitstype bekend is.
public static bool IsItNew(Blog blog)
=> blog.BlogId == 0;
EF heeft echter ook een ingebouwde manier om dit te doen voor elk entiteitstype en sleuteltype:
public static bool IsItNew(DbContext context, object entity)
=> !context.Entry(entity).IsKeySet;
Aanbeveling
Sleutels worden ingesteld zodra entiteiten worden bijgehouden door de context, zelfs als de entiteit de status Toegevoegd heeft. Dit helpt bij het doorlopen van een grafiek met entiteiten en het bepalen wat u ermee moet doen, zoals bij het gebruik van de TrackGraph-API. De sleutelwaarde mag alleen worden gebruikt op de manier die hier wordt weergegeven voordat er een aanroep wordt gedaan om de entiteit bij te houden.
Met andere sleutels
Er is een ander mechanisme nodig om nieuwe entiteiten te identificeren wanneer sleutelwaarden niet automatisch worden gegenereerd. Er zijn twee algemene benaderingen:
- Query uitvoeren op de entiteit
- Een flag doorgeven vanuit de client
Als u een query wilt uitvoeren op de entiteit, gebruikt u de methode Zoeken:
public static async Task<bool> IsItNew(BloggingContext context, Blog blog)
=> (await context.Blogs.FindAsync(blog.BlogId)) == null;
Het valt buiten de reikwijdte van dit document om de volledige code weer te geven voor het doorgeven van een flag van een client. In een web-app betekent dit meestal dat er verschillende aanvragen voor verschillende acties worden ingediend of dat een bepaalde status in de aanvraag wordt doorgegeven en vervolgens in de controller wordt geëxtraheerd.
Eén entiteit opslaan
Als het bekend is of er een invoeging of update nodig is, kan toevoegen of bijwerken op de juiste manier worden gebruikt:
public static async Task Insert(DbContext context, object entity)
{
context.Add(entity);
await context.SaveChangesAsync();
}
public static async Task Update(DbContext context, object entity)
{
context.Update(entity);
await context.SaveChangesAsync();
}
Als de entiteit echter gebruikmaakt van automatisch gegenereerde sleutelwaarden, kan de updatemethode worden gebruikt voor beide gevallen:
public static async Task InsertOrUpdate(DbContext context, object entity)
{
context.Update(entity);
await context.SaveChangesAsync();
}
De updatemethode markeert normaal gesproken de entiteit voor update, niet invoegen. Als de entiteit echter een automatisch gegenereerde sleutel heeft en er geen sleutelwaarde is ingesteld, wordt de entiteit in plaats daarvan automatisch gemarkeerd voor invoegen.
Als de entiteit geen automatisch gegenereerde sleutels gebruikt, moet de toepassing beslissen of de entiteit moet worden ingevoegd of bijgewerkt: bijvoorbeeld:
public static async Task InsertOrUpdate(BloggingContext context, Blog blog)
{
var existingBlog = await context.Blogs.FindAsync(blog.BlogId);
if (existingBlog == null)
{
context.Add(blog);
}
else
{
context.Entry(existingBlog).CurrentValues.SetValues(blog);
}
await context.SaveChangesAsync();
}
De volgende stappen zijn:
- Als Find null retourneert, bevat de database het blog met deze id nog niet, dus roepen we Add aan om het te markeren voor invoeging.
- Als Zoeken een entiteit retourneert, bestaat deze in de database en wordt de bestaande entiteit nu bijgehouden in de context
- Vervolgens gebruiken we SetValues om de waarden voor alle eigenschappen van deze entiteit in te stellen op de waarden die afkomstig zijn van de client.
- Met de aanroep SetValues wordt de entiteit gemarkeerd die indien nodig moet worden bijgewerkt.
Aanbeveling
SetValues markeert alleen de eigenschappen als gewijzigd die andere waarden hebben dan die in de bijgehouden entiteit. Dit betekent dat wanneer de update wordt verzonden, alleen de kolommen die daadwerkelijk zijn gewijzigd, worden bijgewerkt. (En als er niets is gewijzigd, wordt er helemaal geen update verzonden.)
Werken met grafieken
Identiteitsoplossing
Zoals hierboven vermeld, kan EF Core slechts één exemplaar van een entiteit bijhouden met een bepaalde primaire-sleutelwaarde. Wanneer u met grafieken werkt, moet de grafiek idealiter zo worden gemaakt dat deze invariant wordt gehandhaafd en de context voor slechts één werkeenheid moet worden gebruikt. Als de grafiek duplicaten bevat, moet u de grafiek verwerken voordat deze naar EF wordt verzonden om meerdere exemplaren in één te consolideren. Dit is mogelijk niet triviaal wanneer exemplaren conflicterende waarden en relaties hebben. Het consolideren van duplicaten moet dus zo snel mogelijk in uw toepassingspijplijn worden uitgevoerd om conflictoplossing te voorkomen.
Alle nieuwe/alle bestaande entiteiten
Een voorbeeld van het werken met grafieken is het invoegen of bijwerken van een blog samen met de verzameling gekoppelde berichten. Als alle entiteiten in de grafiek moeten worden ingevoegd of alle entiteiten moeten worden bijgewerkt, is het proces hetzelfde als hierboven beschreven voor afzonderlijke entiteiten. Bijvoorbeeld een grafiek van blogs en berichten die als volgt zijn gemaakt:
var blog = new Blog
{
Url = "http://sample.com", Posts = new List<Post> { new Post { Title = "Post 1" }, new Post { Title = "Post 2" }, }
};
kan als volgt worden ingevoegd:
public static async Task InsertGraph(DbContext context, object rootEntity)
{
context.Add(rootEntity);
await context.SaveChangesAsync();
}
De aanroep om toe te voegen markeert de blog en alle berichten die moeten worden ingevoegd.
Als alle entiteiten in een grafiek moeten worden bijgewerkt, kan Update ook worden gebruikt:
public static async Task UpdateGraph(DbContext context, object rootEntity)
{
context.Update(rootEntity);
await context.SaveChangesAsync();
}
De blog en alle bijbehorende berichten worden gemarkeerd om te worden bijgewerkt.
Combinatie van nieuwe en bestaande entiteiten
Met automatisch gegenereerde sleutels kan Update opnieuw worden gebruikt voor zowel invoegingen als updates, zelfs als de grafiek een combinatie van entiteiten bevat die moeten worden ingevoegd en entiteiten die moeten worden bijgewerkt:
public static async Task InsertOrUpdateGraph(DbContext context, object rootEntity)
{
context.Update(rootEntity);
await context.SaveChangesAsync();
}
Bijwerken markeert een entiteit in de grafiek, blog of post voor invoeging als er geen sleutelwaarde is ingesteld, terwijl alle andere entiteiten zijn gemarkeerd voor update.
Net als voorheen kunnen bij het niet gebruiken van automatisch gegenereerde sleutels een query en een aantal verwerkingen worden gebruikt:
public static async Task InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
var existingBlog = await context.Blogs
.Include(b => b.Posts)
.FirstOrDefaultAsync(b => b.BlogId == blog.BlogId);
if (existingBlog == null)
{
context.Add(blog);
}
else
{
context.Entry(existingBlog).CurrentValues.SetValues(blog);
foreach (var post in blog.Posts)
{
var existingPost = existingBlog.Posts
.FirstOrDefault(p => p.PostId == post.PostId);
if (existingPost == null)
{
existingBlog.Posts.Add(post);
}
else
{
context.Entry(existingPost).CurrentValues.SetValues(post);
}
}
}
await context.SaveChangesAsync();
}
Omgaan met verwijderingen
Verwijderen kan lastig zijn, omdat het ontbreken van een entiteit vaak betekent dat deze moet worden verwijderd. Een manier om hiermee om te gaan, is door 'voorlopig verwijderen' te gebruiken, zodat de entiteit wordt gemarkeerd als verwijderd in plaats van daadwerkelijk te worden verwijderd. Verwijderen resulteert dan in hetzelfde als bijwerken. Voorlopig verwijderen kan worden geïmplementeerd met behulp van queryfilters.
Voor echte verwijderingen is een gemeenschappelijk patroon het gebruik van een extensie van het querypatroon om uit te voeren wat in feite een grafiekverschil is. Voorbeeld:
public static async Task InsertUpdateOrDeleteGraph(BloggingContext context, Blog blog)
{
var existingBlog = await context.Blogs
.Include(b => b.Posts)
.FirstOrDefaultAsync(b => b.BlogId == blog.BlogId);
if (existingBlog == null)
{
context.Add(blog);
}
else
{
context.Entry(existingBlog).CurrentValues.SetValues(blog);
foreach (var post in blog.Posts)
{
var existingPost = existingBlog.Posts
.FirstOrDefault(p => p.PostId == post.PostId);
if (existingPost == null)
{
existingBlog.Posts.Add(post);
}
else
{
context.Entry(existingPost).CurrentValues.SetValues(post);
}
}
foreach (var post in existingBlog.Posts)
{
if (!blog.Posts.Any(p => p.PostId == post.PostId))
{
context.Remove(post);
}
}
}
await context.SaveChangesAsync();
}
TrackGraph
Bij intern gebruik van Toevoegen, Bijvoegen en Bijwerken wordt de graafdoorloop gebruikt waarbij voor iedere entiteit wordt bepaald of deze moet worden gemarkeerd als Toegevoegd (om in te voegen), Gewijzigd (bij te werken), Ongewijzigd (niets doen) of Verwijderd (te verwijderen). Dit mechanisme wordt beschikbaar gesteld via de TrackGraph-API. Stel dat wanneer de client een grafiek met entiteiten terugstuurt, er een vlag wordt ingesteld op elke entiteit die aangeeft hoe deze moet worden verwerkt. TrackGraph kan vervolgens worden gebruikt om deze vlag te verwerken:
public static async Task SaveAnnotatedGraph(DbContext context, object rootEntity)
{
context.ChangeTracker.TrackGraph(
rootEntity,
n =>
{
var entity = (EntityBase)n.Entry.Entity;
n.Entry.State = entity.IsNew
? EntityState.Added
: entity.IsChanged
? EntityState.Modified
: entity.IsDeleted
? EntityState.Deleted
: EntityState.Unchanged;
});
await context.SaveChangesAsync();
}
De vlaggen worden alleen weergegeven als onderdeel van de entiteit voor het gemak van het voorbeeld. Normaal gesproken maken de vlaggen deel uit van een DTO of een andere status die is opgenomen in de aanvraag.