Accesso alle entità rilevate

Esistono quattro API principali per l'accesso alle entità rilevate da :DbContext

Ognuna di queste è descritta in modo più dettagliato nelle sezioni seguenti.

Suggerimento

Questo documento presuppone che gli stati dell'entità e le nozioni di base del rilevamento delle modifiche di EF Core siano compresi. Per altre informazioni su questi argomenti, vedere Rilevamento modifiche in EF Core.

Suggerimento

È possibile eseguire ed eseguire il debug in tutto il codice di questo documento scaricando il codice di esempio da GitHub.

Uso di istanze DbContext.Entry ed EntityEntry

Per ogni entità rilevata, Entity Framework Core (EF Core) tiene traccia di:

  • Stato complessivo dell'entità. Si tratta di uno di Unchanged, AddedModified, o Deleted. Per altre informazioni, vedere Rilevamento modifiche in EF Core.
  • Relazioni tra entità rilevate. Ad esempio, il blog a cui appartiene un post.
  • Valori correnti delle proprietà.
  • I "valori originali" delle proprietà, quando queste informazioni sono disponibili. I valori originali sono i valori delle proprietà esistenti quando l'entità è stata sottoposta a query dal database.
  • I valori delle proprietà sono stati modificati dopo la query.
  • Altre informazioni sui valori delle proprietà, ad esempio se il valore è temporaneo.

Il passaggio di un'istanza di entità a DbContext.Entry restituisce un accesso EntityEntry<TEntity> a queste informazioni per l'entità specificata. Ad esempio:

using var context = new BlogsContext();

var blog = context.Blogs.Single(e => e.Id == 1);
var entityEntry = context.Entry(blog);

Le sezioni seguenti illustrano come usare EntityEntry per accedere e modificare lo stato dell'entità, nonché lo stato delle proprietà e degli spostamenti dell'entità.

Uso dell'entità

L'uso più comune di è quello di EntityEntry<TEntity> accedere all'oggetto corrente EntityState di un'entità. Ad esempio:

var currentState = context.Entry(blog).State;
if (currentState == EntityState.Unchanged)
{
    context.Entry(blog).State = EntityState.Modified;
}

Il metodo Entry può essere usato anche nelle entità non ancora rilevate. Questo non avvia il rilevamento dell'entità. Lo stato dell'entità è ancora Detached. Tuttavia, è possibile usare EntityEntry restituito per modificare lo stato dell'entità, a questo punto l'entità verrà rilevata nello stato specificato. Ad esempio, il codice seguente inizierà a tenere traccia di un'istanza del blog come Added:

var newBlog = new Blog();
Debug.Assert(context.Entry(newBlog).State == EntityState.Detached);

context.Entry(newBlog).State = EntityState.Added;
Debug.Assert(context.Entry(newBlog).State == EntityState.Added);

Suggerimento

A differenza di EF6, l'impostazione dello stato di una singola entità non causerà il rilevamento di tutte le entità connesse. Ciò rende l'impostazione dello stato in questo modo un'operazione di livello inferiore rispetto alla chiamata Adddi , Attacho Update, che opera su un intero grafico di entità.

La tabella seguente riepiloga i modi per usare entityEntry per lavorare con un'intera entità:

Membro EntityEntry Descrizione
EntityEntry.State Ottiene e imposta l'oggetto EntityState dell'entità.
EntityEntry.Entity Ottiene l'istanza dell'entità.
EntityEntry.Context Oggetto DbContext che esegue il rilevamento di questa entità.
EntityEntry.Metadata IEntityType metadati per il tipo di entità.
EntityEntry.IsKeySet Indica se l'entità ha impostato o meno il valore della chiave.
EntityEntry.Reload() Sovrascrive i valori delle proprietà con valori letti dal database.
EntityEntry.DetectChanges() Forza il rilevamento delle modifiche solo per questa entità; vedere Rilevamento modifiche e notifiche.

Utilizzo di una singola proprietà

Diversi overload di consentono l'accesso EntityEntry<TEntity>.Property alle informazioni su una singola proprietà di un'entità. Ad esempio, usando un'API fluent-like fortemente tipizzata:

PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property(e => e.Name);

Il nome della proprietà può invece essere passato come stringa. Ad esempio:

PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property<string>("Name");

L'oggetto restituito PropertyEntry<TEntity,TProperty> può quindi essere utilizzato per accedere alle informazioni sulla proprietà . Ad esempio, può essere usato per ottenere e impostare il valore corrente della proprietà in questa entità:

string currentValue = context.Entry(blog).Property(e => e.Name).CurrentValue;
context.Entry(blog).Property(e => e.Name).CurrentValue = "1unicorn2";

Entrambi i metodi Property usati in precedenza restituiscono un'istanza generica PropertyEntry<TEntity,TProperty> fortemente tipizzata. L'uso di questo tipo generico è preferibile perché consente l'accesso ai valori delle proprietà senza tipi valore boxing. Tuttavia, se il tipo di entità o proprietà non è noto in fase di compilazione, è possibile ottenere un non generico PropertyEntry :

PropertyEntry propertyEntry = context.Entry(blog).Property("Name");

In questo modo è possibile accedere alle informazioni sulle proprietà per qualsiasi proprietà indipendentemente dal tipo, a scapito dei tipi valore boxing. Ad esempio:

object blog = context.Blogs.Single(e => e.Id == 1);

object currentValue = context.Entry(blog).Property("Name").CurrentValue;
context.Entry(blog).Property("Name").CurrentValue = "1unicorn2";

La tabella seguente riepiloga le informazioni sulle proprietà esposte da PropertyEntry:

Membro PropertyEntry Descrizione
PropertyEntry<TEntity,TProperty>.CurrentValue Ottiene e imposta il valore corrente della proprietà.
PropertyEntry<TEntity,TProperty>.OriginalValue Ottiene e imposta il valore originale della proprietà, se disponibile.
PropertyEntry<TEntity,TProperty>.EntityEntry Riferimento all'oggetto EntityEntry<TEntity> per l'entità.
PropertyEntry.Metadata IProperty metadati per la proprietà .
PropertyEntry.IsModified Indica se questa proprietà è contrassegnata come modificata e consente di modificare questo stato.
PropertyEntry.IsTemporary Indica se questa proprietà è contrassegnata come temporanea e consente di modificare questo stato.

Note:

  • Il valore originale di una proprietà è il valore che la proprietà aveva quando l'entità è stata sottoposta a query dal database. Tuttavia, i valori originali non sono disponibili se l'entità è stata disconnessa e quindi collegata in modo esplicito a un altro Oggetto DbContext, ad esempio con Attach o Update. In questo caso, il valore originale restituito sarà uguale al valore corrente.
  • SaveChanges aggiornerà solo le proprietà contrassegnate come modificate. Impostare IsModified su true per forzare EF Core ad aggiornare un determinato valore della proprietà o impostarlo su false per impedire a EF Core di aggiornare il valore della proprietà.
  • I valori temporanei vengono in genere generati dai generatori di valori di EF Core. L'impostazione del valore corrente di una proprietà sostituirà il valore temporaneo con il valore specificato e contrassegnerà la proprietà come non temporanea. Impostare IsTemporary su true per forzare il temporaneo di un valore anche dopo che è stato impostato in modo esplicito.

Uso di un'unica navigazione

Diversi overload di EntityEntry<TEntity>.Reference, EntityEntry<TEntity>.Collectione EntityEntry.Navigation consentono l'accesso alle informazioni su una singola navigazione.

Gli spostamenti di riferimento a una singola entità correlata sono accessibili tramite i Reference metodi . Gli spostamenti di riferimento puntano ai lati "uno" delle relazioni uno-a-molti e a entrambi i lati delle relazioni uno-a-uno. Ad esempio:

ReferenceEntry<Post, Blog> referenceEntry1 = context.Entry(post).Reference(e => e.Blog);
ReferenceEntry<Post, Blog> referenceEntry2 = context.Entry(post).Reference<Blog>("Blog");
ReferenceEntry referenceEntry3 = context.Entry(post).Reference("Blog");

Gli spostamenti possono anche essere raccolte di entità correlate quando vengono usate per i lati "molti" delle relazioni uno-a-molti e molti-a-molti. I Collection metodi vengono utilizzati per accedere agli spostamenti della raccolta. Ad esempio:

CollectionEntry<Blog, Post> collectionEntry1 = context.Entry(blog).Collection(e => e.Posts);
CollectionEntry<Blog, Post> collectionEntry2 = context.Entry(blog).Collection<Post>("Posts");
CollectionEntry collectionEntry3 = context.Entry(blog).Collection("Posts");

Alcune operazioni sono comuni per tutti gli spostamenti. È possibile accedervi sia per gli spostamenti di riferimento che per gli spostamenti nella raccolta usando il EntityEntry.Navigation metodo . Si noti che solo l'accesso non generico è disponibile quando si accede a tutte le operazioni di spostamento. Ad esempio:

NavigationEntry navigationEntry = context.Entry(blog).Navigation("Posts");

La tabella seguente riepiloga i modi per usare ReferenceEntry<TEntity,TProperty>, CollectionEntry<TEntity,TRelatedEntity>e NavigationEntry:

Membro NavigationEntry Descrizione
MemberEntry.CurrentValue Ottiene e imposta il valore corrente della struttura di spostamento. Questa è l'intera raccolta per gli spostamenti delle raccolte.
NavigationEntry.Metadata INavigationBase metadati per la navigazione.
NavigationEntry.IsLoaded Ottiene o imposta un valore che indica se l'entità o la raccolta correlata è stata completamente caricata dal database.
NavigationEntry.Load() Carica l'entità o la raccolta correlata dal database; vedere Caricamento esplicito dei dati correlati.
NavigationEntry.Query() La query EF Core userebbe per caricare questo spostamento come elemento IQueryable che può essere ulteriormente composto. Vedere Caricamento esplicito di dati correlati.

Utilizzo di tutte le proprietà di un'entità

EntityEntry.Properties restituisce un oggetto IEnumerable<T> di PropertyEntry per ogni proprietà dell'entità. Può essere usato per eseguire un'azione per ogni proprietà dell'entità. Ad esempio, per impostare qualsiasi proprietà DateTime su DateTime.Now:

foreach (var propertyEntry in context.Entry(blog).Properties)
{
    if (propertyEntry.Metadata.ClrType == typeof(DateTime))
    {
        propertyEntry.CurrentValue = DateTime.Now;
    }
}

EntityEntry contiene inoltre diversi metodi per ottenere e impostare tutti i valori delle proprietà contemporaneamente. Questi metodi usano la PropertyValues classe , che rappresenta una raccolta di proprietà e i relativi valori. È possibile ottenere PropertyValues per i valori correnti o originali oppure per i valori attualmente archiviati nel database. Ad esempio:

var currentValues = context.Entry(blog).CurrentValues;
var originalValues = context.Entry(blog).OriginalValues;
var databaseValues = context.Entry(blog).GetDatabaseValues();

Questi oggetti PropertyValues non sono molto utili autonomamente. Tuttavia, possono essere combinati per eseguire operazioni comuni necessarie durante la modifica delle entità. Ciò è utile quando si lavora con gli oggetti di trasferimento dei dati e quando si risolve i conflitti di concorrenza ottimistica. Le sezioni seguenti illustrano alcuni esempi.

Impostazione dei valori correnti o originali da un'entità o un DTO

I valori correnti o originali di un'entità possono essere aggiornati copiando i valori da un altro oggetto. Si consideri, ad esempio, un BlogDto oggetto DTO (Data Transfer Object) con le stesse proprietà del tipo di entità:

public class BlogDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Può essere usato per impostare i valori correnti di un'entità rilevata usando PropertyValues.SetValues:

var blogDto = new BlogDto { Id = 1, Name = "1unicorn2" };

context.Entry(blog).CurrentValues.SetValues(blogDto);

Questa tecnica viene talvolta usata durante l'aggiornamento di un'entità con valori ottenuti da una chiamata al servizio o da un client in un'applicazione a più livelli. Si noti che l'oggetto usato non deve essere dello stesso tipo dell'entità, purché disponga di proprietà i cui nomi corrispondono a quelli dell'entità. Nell'esempio precedente viene usata un'istanza del DTO BlogDto per impostare i valori correnti di un'entità rilevata Blog .

Si noti che le proprietà verranno contrassegnate come modificate solo se il set di valori è diverso dal valore corrente.

Impostazione dei valori correnti o originali da un dizionario

L'esempio precedente imposta i valori da un'entità o da un'istanza DTO. Lo stesso comportamento è disponibile quando i valori delle proprietà vengono archiviati come coppie nome/valore in un dizionario. Ad esempio:

var blogDictionary = new Dictionary<string, object> { ["Id"] = 1, ["Name"] = "1unicorn2" };

context.Entry(blog).CurrentValues.SetValues(blogDictionary);

Impostazione dei valori correnti o originali dal database

I valori correnti o originali di un'entità possono essere aggiornati con i valori più recenti del database chiamando GetDatabaseValues() o GetDatabaseValuesAsync e usando l'oggetto restituito per impostare i valori correnti o originali o entrambi. Ad esempio:

var databaseValues = context.Entry(blog).GetDatabaseValues();
context.Entry(blog).CurrentValues.SetValues(databaseValues);
context.Entry(blog).OriginalValues.SetValues(databaseValues);

Creazione di un oggetto clonato contenente valori correnti, originali o di database

L'oggetto PropertyValues restituito da CurrentValues, OriginalValues o GetDatabaseValues può essere usato per creare un clone dell'entità usando PropertyValues.ToObject(). Ad esempio:

var clonedBlog = context.Entry(blog).GetDatabaseValues().ToObject();

Si noti che ToObject restituisce una nuova istanza non rilevata da DbContext. L'oggetto restituito non dispone inoltre di relazioni impostate su altre entità.

L'oggetto clonato può essere utile per risolvere i problemi relativi agli aggiornamenti simultanei al database, soprattutto quando si esegue il data binding a oggetti di un determinato tipo. Per altre informazioni, vedere Concorrenza ottimistica.

Uso di tutti gli spostamenti di un'entità

EntityEntry.Navigations restituisce un oggetto IEnumerable<T> di NavigationEntry per ogni navigazione dell'entità. EntityEntry.References e EntityEntry.Collections eseguire la stessa operazione, ma limitata rispettivamente agli spostamenti di riferimento o raccolta. Può essere usato per eseguire un'azione per ogni navigazione dell'entità. Ad esempio, per forzare il caricamento di tutte le entità correlate:

foreach (var navigationEntry in context.Entry(blog).Navigations)
{
    navigationEntry.Load();
}

Utilizzo di tutti i membri di un'entità

Le proprietà regolari e le proprietà di navigazione hanno uno stato e un comportamento diversi. È quindi comune elaborare gli spostamenti e gli spostamenti non separatamente, come illustrato nelle sezioni precedenti. Tuttavia, a volte può essere utile eseguire operazioni con qualsiasi membro dell'entità, indipendentemente dal fatto che si tratti di una normale proprietà o di una navigazione. EntityEntry.Member e EntityEntry.Members vengono forniti a questo scopo. Ad esempio:

foreach (var memberEntry in context.Entry(blog).Members)
{
    Console.WriteLine(
        $"Member {memberEntry.Metadata.Name} is of type {memberEntry.Metadata.ClrType.ShortDisplayName()} and has value {memberEntry.CurrentValue}");
}

L'esecuzione di questo codice in un blog dell'esempio genera l'output seguente:

Member Id is of type int and has value 1
Member Name is of type string and has value .NET Blog
Member Posts is of type IList<Post> and has value System.Collections.Generic.List`1[Post]

Suggerimento

La visualizzazione debug di Rilevamento modifiche mostra informazioni simili a questa. La visualizzazione di debug per l'intero strumento di rilevamento delle modifiche viene generata dall'utente di EntityEntry.DebugView ogni entità rilevata.

Trova e TrovaAsync

DbContext.Find, DbContext.FindAsync, DbSet<TEntity>.Finde DbSet<TEntity>.FindAsync sono progettati per una ricerca efficiente di una singola entità quando è nota la relativa chiave primaria. Trovare prima di tutto verifica se l'entità è già rilevata e, in tal caso, restituisce immediatamente l'entità. Viene eseguita una query di database solo se l'entità non viene rilevata localmente. Si consideri ad esempio questo codice che chiama Trova due volte per la stessa entità:

using var context = new BlogsContext();

Console.WriteLine("First call to Find...");
var blog1 = context.Blogs.Find(1);

Console.WriteLine($"...found blog {blog1.Name}");

Console.WriteLine();
Console.WriteLine("Second call to Find...");
var blog2 = context.Blogs.Find(1);
Debug.Assert(blog1 == blog2);

Console.WriteLine("...returned the same instance without executing a query.");

L'output di questo codice (inclusa la registrazione di EF Core) quando si usa SQLite è:

First call to Find...
info: 12/29/2020 07:45:53.682 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (1ms) [Parameters=[@__p_0='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
      SELECT "b"."Id", "b"."Name"
      FROM "Blogs" AS "b"
      WHERE "b"."Id" = @__p_0
      LIMIT 1
...found blog .NET Blog

Second call to Find...
...returned the same instance without executing a query.

Si noti che la prima chiamata non trova l'entità in locale e quindi esegue una query di database. Al contrario, la seconda chiamata restituisce la stessa istanza senza eseguire query sul database perché è già in corso il rilevamento.

Find restituisce Null se un'entità con la chiave specificata non viene rilevata localmente e non esiste nel database.

Chiavi composte

È anche possibile usare Find con chiavi composite. Si consideri, ad esempio, un'entità OrderLine con una chiave composta costituita dall'ID ordine e dall'ID prodotto:

public class OrderLine
{
    public int OrderId { get; set; }
    public int ProductId { get; set; }

    //...
}

La chiave composita deve essere configurata in DbContext.OnModelCreating per definire le parti chiave e il relativo ordine. Ad esempio:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<OrderLine>()
        .HasKey(e => new { e.OrderId, e.ProductId });
}

Si noti che OrderId è la prima parte della chiave ed ProductId è la seconda parte della chiave. Questo ordine deve essere utilizzato quando si passano i valori di chiave a Trova. Ad esempio:

var orderline = context.OrderLines.Find(orderId, productId);

Uso di ChangeTracker.Entries per accedere a tutte le entità rilevate

Finora abbiamo avuto accesso solo a un singolo EntityEntry alla volta. ChangeTracker.Entries() restituisce un entityEntry per ogni entità attualmente rilevata da DbContext. Ad esempio:

using var context = new BlogsContext();
var blogs = context.Blogs.Include(e => e.Posts).ToList();

foreach (var entityEntry in context.ChangeTracker.Entries())
{
    Console.WriteLine($"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property("Id").CurrentValue}");
}

Questo codice genera l'output seguente:

Found Blog entity with ID 1
Found Post entity with ID 1
Found Post entity with ID 2

Si noti che vengono restituite voci sia per i blog che per i post. I risultati possono invece essere filtrati in base a un tipo di entità specifico usando l'overload ChangeTracker.Entries<TEntity>() generico:

foreach (var entityEntry in context.ChangeTracker.Entries<Post>())
{
    Console.WriteLine(
        $"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}

L'output di questo codice mostra che vengono restituiti solo i post:

Found Post entity with ID 1
Found Post entity with ID 2

Inoltre, l'uso dell'overload generico restituisce istanze generice EntityEntry<TEntity> . Questo è ciò che consente l'accesso fluent-like alla Id proprietà in questo esempio.

Il tipo generico usato per il filtro non deve essere un tipo di entità mappato; È possibile usare invece un tipo di base o un'interfaccia non mappata. Ad esempio, se tutti i tipi di entità nel modello implementano un'interfaccia che definisce la relativa proprietà chiave:

public interface IEntityWithKey
{
    int Id { get; set; }
}

Questa interfaccia può quindi essere usata per usare la chiave di qualsiasi entità rilevata in modo fortemente tipizzato. Ad esempio:

foreach (var entityEntry in context.ChangeTracker.Entries<IEntityWithKey>())
{
    Console.WriteLine(
        $"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}

Uso di DbSet.Local per eseguire query sulle entità rilevate

Le query EF Core vengono sempre eseguite nel database e restituiscono solo entità salvate nel database. DbSet<TEntity>.Local fornisce un meccanismo per eseguire query su DbContext per le entità locali rilevate.

Poiché DbSet.Local viene usato per eseguire query sulle entità rilevate, è tipico caricare le entità in DbContext e quindi usare tali entità caricate. Ciò vale soprattutto per il data binding, ma può essere utile anche in altre situazioni. Ad esempio, nel codice seguente il database viene prima sottoposto a query per tutti i blog e i post. Il Load metodo di estensione viene usato per eseguire questa query con i risultati rilevati dal contesto senza essere restituiti direttamente all'applicazione. L'uso ToList o simile ha lo stesso effetto, ma con l'overhead della creazione dell'elenco restituito, che non è necessario qui. L'esempio usa DbSet.Local quindi per accedere alle entità rilevate in locale:

using var context = new BlogsContext();

context.Blogs.Include(e => e.Posts).Load();

foreach (var blog in context.Blogs.Local)
{
    Console.WriteLine($"Blog: {blog.Name}");
}

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"Post: {post.Title}");
}

Si noti che, a differenza ChangeTracker.Entries()di , DbSet.Local restituisce direttamente istanze di entità. Un EntityEntry può, naturalmente, essere sempre ottenuto per l'entità restituita chiamando DbContext.Entry.

Visualizzazione locale

DbSet<TEntity>.Local restituisce una visualizzazione delle entità rilevate localmente che riflettono l'oggetto corrente EntityState di tali entità. In particolare, ciò significa che:

  • Added sono incluse le entità. Si noti che questo non è il caso per le normali query EF Core, poiché Added le entità non esistono ancora nel database e pertanto non vengono mai restituite da una query di database.
  • Deleted le entità vengono escluse. Si noti che questo non è ancora il caso per le normali query di EF Core, poiché Deleted le entità sono ancora presenti nel database e quindi vengono restituite dalle query del database.

Tutto questo significa che DbSet.Local viene visualizzata sui dati che riflettono lo stato concettuale corrente del grafo di entità, con Added entità incluse ed Deleted entità escluse. Corrisponde allo stato previsto del database dopo la chiamata a SaveChanges.

Si tratta in genere della visualizzazione ideale per il data binding, poiché presenta all'utente i dati in base alle modifiche apportate dall'applicazione.

Il codice seguente illustra questa operazione contrassegnando un post come Deleted e quindi aggiungendo un nuovo post, contrassegnandolo come Added:

using var context = new BlogsContext();

var posts = context.Posts.Include(e => e.Blog).ToList();

Console.WriteLine("Local view after loading posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

context.Remove(posts[1]);

context.Add(
    new Post
    {
        Title = "What’s next for System.Text.Json?",
        Content = ".NET 5.0 was released recently and has come with many...",
        Blog = posts[0].Blog
    });

Console.WriteLine("Local view after adding and deleting posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

L'output di questo codice è:

Local view after loading posts:
  Post: Announcing the Release of EF Core 5.0
  Post: Announcing F# 5
  Post: Announcing .NET 5.0
Local view after adding and deleting posts:
  Post: What’s next for System.Text.Json?
  Post: Announcing the Release of EF Core 5.0
  Post: Announcing .NET 5.0

Si noti che il post eliminato viene rimosso dalla visualizzazione locale e il post aggiunto è incluso.

Uso di Local per aggiungere e rimuovere entità

DbSet<TEntity>.Local restituisce un'istanza di LocalView<TEntity>. Si tratta di un'implementazione di ICollection<T> che genera e risponde alle notifiche quando le entità vengono aggiunte e rimosse dalla raccolta. Si tratta dello stesso concetto di ObservableCollection<T>, ma implementato come proiezione sulle voci di rilevamento delle modifiche di EF Core esistenti, anziché come raccolta indipendente.

Le notifiche della visualizzazione locale vengono associate al rilevamento delle modifiche dbContext in modo che la visualizzazione locale rimanga sincronizzata con DbContext. In particolare:

  • L'aggiunta di una nuova entità a DbSet.Local fa sì che venga rilevata da DbContext, in genere nello Added stato . Se l'entità ha già un valore di chiave generato, viene invece rilevato come Unchanged .
  • La rimozione di un'entità da DbSet.Local fa sì che venga contrassegnata come Deleted.
  • Un'entità che viene rilevata da DbContext verrà visualizzata automaticamente nella DbSet.Local raccolta. Ad esempio, l'esecuzione di una query per inserire più entità comporta l'aggiornamento automatico della visualizzazione locale.
  • Un'entità contrassegnata come Deleted verrà rimossa automaticamente dalla raccolta locale.

Ciò significa che la visualizzazione locale può essere usata per modificare le entità rilevate semplicemente aggiungendo e rimuovendo dalla raccolta. Ad esempio, modificare il codice di esempio precedente per aggiungere e rimuovere post dalla raccolta locale:

using var context = new BlogsContext();

var posts = context.Posts.Include(e => e.Blog).ToList();

Console.WriteLine("Local view after loading posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

context.Posts.Local.Remove(posts[1]);

context.Posts.Local.Add(
    new Post
    {
        Title = "What’s next for System.Text.Json?",
        Content = ".NET 5.0 was released recently and has come with many...",
        Blog = posts[0].Blog
    });

Console.WriteLine("Local view after adding and deleting posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

L'output rimane invariato rispetto all'esempio precedente perché le modifiche apportate alla visualizzazione locale vengono sincronizzate con DbContext.

Uso della vista locale per Windows Form o il data binding WPF

DbSet<TEntity>.Local costituisce la base per il data binding alle entità di EF Core. Tuttavia, sia Windows Form che WPF funzionano meglio quando vengono usati con il tipo specifico di notifica alla raccolta prevista. La visualizzazione locale supporta la creazione di questi tipi di raccolta specifici:

Ad esempio:

ObservableCollection<Post> observableCollection = context.Posts.Local.ToObservableCollection();
BindingList<Post> bindingList = context.Posts.Local.ToBindingList();

Per altre informazioni sul data binding WPF con EF Core, vedere Introduzione a WPF e Introduzione alle Windows Form per altre informazioni su Windows Form data binding con EF Core.

Suggerimento

La visualizzazione locale per una determinata istanza di DbSet viene creata in modo differire quando si accede per la prima volta e quindi memorizzata nella cache. La creazione di LocalView è veloce e non usa memoria significativa. Tuttavia, chiama DetectChanges, che può essere lento per un numero elevato di entità. Le raccolte create da ToObservableCollection e ToBindingList vengono create anche in modo differire e quindi memorizzate nella cache. Entrambi questi metodi creano nuove raccolte, che possono essere lente e usano molta memoria quando sono coinvolte migliaia di entità.