Dati locali

L'esecuzione di una query LINQ direttamente su un DbSet invierà sempre una query al database, ma è possibile accedere ai dati attualmente in memoria usando la proprietà DbSet.Local. È anche possibile accedere alle informazioni aggiuntive monitorate da Entity Framework usando i metodi DbContext.Entry e DbContext.ChangeTracker.Entries. Le tecniche illustrate in questo argomento si applicano in modo analogo ai modelli creati con Code First ed EF Designer.

Uso di Local per esaminare i dati locali

La proprietà Local di DbSet consente di accedere in modo semplice alle entità del set attualmente monitorate dal contesto e che non sono state contrassegnate come Eliminate. L'accesso alla proprietà Local non comporta mai l'invio di una query al database. Ciò significa che viene in genere usato dopo l'esecuzione di una query. Il metodo di estensione Load può essere usato per eseguire una query in modo che il contesto tenga traccia dei risultati. Ad esempio:

using (var context = new BloggingContext())
{
    // Load all blogs from the database into the context
    context.Blogs.Load();

    // Add a new blog to the context
    context.Blogs.Add(new Blog { Name = "My New Blog" });

    // Mark one of the existing blogs as Deleted
    context.Blogs.Remove(context.Blogs.Find(1));

    // Loop over the blogs in the context.
    Console.WriteLine("In Local: ");
    foreach (var blog in context.Blogs.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            blog.BlogId,  
            blog.Name,
            context.Entry(blog).State);
    }

    // Perform a query against the database.
    Console.WriteLine("\nIn DbSet query: ");
    foreach (var blog in context.Blogs)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            blog.BlogId,  
            blog.Name,
            context.Entry(blog).State);
    }
}

Se nel database sono presenti due blog: "ADO.NET Blog" con blog di 1 e "Blog di Visual Studio" con blogid 2, è possibile prevedere l'output seguente:If we had two blogs in the database- 'ADO.NET Blog' with a BlogId of 1 and 'The Visual Studio Blog' with a BlogId of 2 - we could expect the following output:

In Local:
Found 0: My New Blog with state Added
Found 2: The Visual Studio Blog with state Unchanged

In DbSet query:
Found 1: ADO.NET Blog with state Deleted
Found 2: The Visual Studio Blog with state Unchanged

Questo illustra tre punti:

  • Il nuovo blog "My New Blog" è incluso nella raccolta Local anche se non è ancora stato salvato nel database. Questo blog ha una chiave primaria pari a zero perché il database non ha ancora generato una chiave reale per l'entità.
  • Il "blog ADO.NET" non è incluso nella raccolta locale anche se è ancora monitorato dal contesto. Ciò è dovuto al fatto che è stato rimosso dal DbSet contrassegnandolo come eliminato.
  • Quando DbSet viene usato per eseguire una query il blog contrassegnato per l'eliminazione (ADO.NET Blog) viene incluso nei risultati e il nuovo blog (My New Blog) che non è ancora stato salvato nel database non è incluso nei risultati. Il motivo è che DbSet esegue una query sul database e i risultati restituiti riflettono sempre ciò che si trova nel database.

Uso di Local per aggiungere e rimuovere entità dal contesto

La proprietà Local in DbSet restituisce un oggetto ObservableCollection con eventi associati in modo che rimanga sincronizzato con il contenuto del contesto. Ciò significa che le entità possono essere aggiunte o rimosse dalla raccolta Local o da DbSet. Significa anche che le query che portano nuove entità nel contesto determineranno l'aggiornamento della raccolta Locale con tali entità. Ad esempio:

using (var context = new BloggingContext())
{
    // Load some posts from the database into the context
    context.Posts.Where(p => p.Tags.Contains("entity-framework")).Load();  

    // Get the local collection and make some changes to it
    var localPosts = context.Posts.Local;
    localPosts.Add(new Post { Name = "What's New in EF" });
    localPosts.Remove(context.Posts.Find(1));  

    // Loop over the posts in the context.
    Console.WriteLine("In Local after entity-framework query: ");
    foreach (var post in context.Posts.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            post.Id,  
            post.Title,
            context.Entry(post).State);
    }

    var post1 = context.Posts.Find(1);
    Console.WriteLine(
        "State of post 1: {0} is {1}",
        post1.Name,  
        context.Entry(post1).State);  

    // Query some more posts from the database
    context.Posts.Where(p => p.Tags.Contains("asp.net")).Load();  

    // Loop over the posts in the context again.
    Console.WriteLine("\nIn Local after asp.net query: ");
    foreach (var post in context.Posts.Local)
    {
        Console.WriteLine(
            "Found {0}: {1} with state {2}",
            post.Id,  
            post.Title,
            context.Entry(post).State);
    }
}

Supponendo di avere alcuni post contrassegnati con "entity-framework" e "asp.net", l'output potrebbe essere simile al seguente:

In Local after entity-framework query:
Found 3: EF Designer Basics with state Unchanged
Found 5: EF Code First Basics with state Unchanged
Found 0: What's New in EF with state Added
State of post 1: EF Beginners Guide is Deleted

In Local after asp.net query:
Found 3: EF Designer Basics with state Unchanged
Found 5: EF Code First Basics with state Unchanged
Found 0: What's New in EF with state Added
Found 4: ASP.NET Beginners Guide with state Unchanged

Questo illustra tre punti:

  • Il nuovo post "What's New in EF" aggiunto alla raccolta Local viene rilevato dal contesto nello stato Aggiunto. Verrà quindi inserito nel database quando viene chiamato SaveChanges.
  • Il post rimosso dalla raccolta Local (EF Beginners Guide) è ora contrassegnato come eliminato nel contesto. Verrà quindi eliminato dal database quando viene chiamato SaveChanges.
  • Il post aggiuntivo (ASP.NET Beginners Guide) caricato nel contesto con la seconda query viene aggiunto automaticamente alla raccolta Local.

Un aspetto finale da notare è che poiché si tratta di prestazioni ObservableCollection non è ideale per un numero elevato di entità. Pertanto, se si gestiscono migliaia di entità nel contesto, potrebbe non essere consigliabile usare Local.

Uso del data binding locale per WPF

La proprietà Local in DbSet può essere utilizzata direttamente per il data binding in un'applicazione WPF perché è un'istanza di ObservableCollection. Come descritto nelle sezioni precedenti, ciò significa che rimarrà automaticamente sincronizzato con il contenuto del contesto e il contenuto del contesto rimarrà automaticamente sincronizzato con esso. Si noti che è necessario precompilare la raccolta Locale con i dati perché sia presente qualsiasi elemento a cui eseguire l'associazione, poiché Local non genera mai una query di database.

Questa non è una posizione appropriata per un esempio di data binding WPF completo, ma gli elementi chiave sono:

  • Configurare un'origine di associazione
  • Associarlo alla proprietà Local del set
  • Popolare Local usando una query nel database.

Associazione WPF alle proprietà di navigazione

Se si esegue il data binding master/dettagli, è possibile associare la visualizzazione dettagli a una proprietà di navigazione di una delle entità. Un modo semplice per eseguire questa operazione consiste nell'usare ObservableCollection per la proprietà di navigazione. Ad esempio:

public class Blog
{
    private readonly ObservableCollection<Post> _posts =
        new ObservableCollection<Post>();

    public int BlogId { get; set; }
    public string Name { get; set; }

    public virtual ObservableCollection<Post> Posts
    {
        get { return _posts; }
    }
}

Uso di Local per pulire le entità in SaveChanges

Nella maggior parte dei casi le entità rimosse da una proprietà di navigazione non verranno contrassegnate automaticamente come eliminate nel contesto. Ad esempio, se si rimuove un oggetto Post dall'insieme Blog.Posts, tale post non verrà eliminato automaticamente quando viene chiamato SaveChanges. Se è necessario eliminarlo, potrebbe essere necessario trovare queste entità incerte e contrassegnarle come eliminate prima di chiamare SaveChanges o come parte di un SaveChanges sottoposto a override. Ad esempio:

public override int SaveChanges()
{
    foreach (var post in this.Posts.Local.ToList())
    {
        if (post.Blog == null)
        {
            this.Posts.Remove(post);
        }
    }

    return base.SaveChanges();
}

Il codice precedente usa la raccolta Local per trovare tutti i post e contrassegna tutti i post che non dispongono di un riferimento al blog come eliminato. La chiamata ToList è obbligatoria perché in caso contrario la raccolta verrà modificata dalla chiamata Remove durante l'enumerazione. Nella maggior parte delle altre situazioni è possibile eseguire query direttamente sulla proprietà Local senza prima usare ToList.

Uso di Local e ToBindingList per Windows Form data binding

Windows Form non supporta direttamente il data binding con fedeltà completa usando ObservableCollection. Tuttavia, è comunque possibile usare la proprietà DbSet Local per il data binding per ottenere tutti i vantaggi descritti nelle sezioni precedenti. Questo risultato viene ottenuto tramite il metodo di estensione ToBindingList che crea un'implementazione IBindingList supportata da Local ObservableCollection.

Questa non è una posizione appropriata per un esempio di data binding completo Windows Form, ma gli elementi chiave sono:

  • Configurare un'origine di associazione di oggetti
  • Associarlo alla proprietà Local del set usando Local.ToBindingList()
  • Popolare localmente usando una query nel database

Ottenere informazioni dettagliate sulle entità rilevate

Molti degli esempi di questa serie usano il metodo Entry per restituire un'istanza dbEntityEntry per un'entità. Questo oggetto voce funge quindi da punto di partenza per raccogliere informazioni sull'entità, ad esempio il relativo stato corrente, nonché per l'esecuzione di operazioni sull'entità, ad esempio il caricamento esplicito di un'entità correlata.

I metodi Entries restituiscono oggetti DbEntityEntry per molte o tutte le entità rilevate dal contesto. In questo modo è possibile raccogliere informazioni o eseguire operazioni su molte entità anziché una sola voce. Ad esempio:

using (var context = new BloggingContext())
{
    // Load some entities into the context
    context.Blogs.Load();
    context.Authors.Load();
    context.Readers.Load();

    // Make some changes
    context.Blogs.Find(1).Title = "The New ADO.NET Blog";
    context.Blogs.Remove(context.Blogs.Find(2));
    context.Authors.Add(new Author { Name = "Jane Doe" });
    context.Readers.Find(1).Username = "johndoe1987";

    // Look at the state of all entities in the context
    Console.WriteLine("All tracked entities: ");
    foreach (var entry in context.ChangeTracker.Entries())
    {
        Console.WriteLine(
            "Found entity of type {0} with state {1}",
            ObjectContext.GetObjectType(entry.Entity.GetType()).Name,
            entry.State);
    }

    // Find modified entities of any type
    Console.WriteLine("\nAll modified entities: ");
    foreach (var entry in context.ChangeTracker.Entries()
                              .Where(e => e.State == EntityState.Modified))
    {
        Console.WriteLine(
            "Found entity of type {0} with state {1}",
            ObjectContext.GetObjectType(entry.Entity.GetType()).Name,
            entry.State);
    }

    // Get some information about just the tracked blogs
    Console.WriteLine("\nTracked blogs: ");
    foreach (var entry in context.ChangeTracker.Entries<Blog>())
    {
        Console.WriteLine(
            "Found Blog {0}: {1} with original Name {2}",
            entry.Entity.BlogId,  
            entry.Entity.Name,
            entry.Property(p => p.Name).OriginalValue);
    }

    // Find all people (author or reader)
    Console.WriteLine("\nPeople: ");
    foreach (var entry in context.ChangeTracker.Entries<IPerson>())
    {
        Console.WriteLine("Found Person {0}", entry.Entity.Name);
    }
}

Si noterà che si sta introducendo una classe Author e Reader nell'esempio. Entrambe queste classi implementano l'interfaccia IPerson.

public class Author : IPerson
{
    public int AuthorId { get; set; }
    public string Name { get; set; }
    public string Biography { get; set; }
}

public class Reader : IPerson
{
    public int ReaderId { get; set; }
    public string Name { get; set; }
    public string Username { get; set; }
}

public interface IPerson
{
    string Name { get; }
}

Si supponga di avere i dati seguenti nel database:

Blog con BlogId = 1 e Nome = 'ADO.NET Blog'
Blog con BlogId = 2 e nome = 'Blog di Visual Studio'
Blog con BlogId = 3 e nome = '.NET Framework Blog'
Author with AuthorId = 1 and Name = 'Joe Bloggs'
Lettore con ReaderId = 1 e Nome = 'John Doe'

L'output dell'esecuzione del codice sarà:

All tracked entities:
Found entity of type Blog with state Modified
Found entity of type Blog with state Deleted
Found entity of type Blog with state Unchanged
Found entity of type Author with state Unchanged
Found entity of type Author with state Added
Found entity of type Reader with state Modified

All modified entities:
Found entity of type Blog with state Modified
Found entity of type Reader with state Modified

Tracked blogs:
Found Blog 1: The New ADO.NET Blog with original Name ADO.NET Blog
Found Blog 2: The Visual Studio Blog with original Name The Visual Studio Blog
Found Blog 3: .NET Framework Blog with original Name .NET Framework Blog

People:
Found Person John Doe
Found Person Joe Bloggs
Found Person Jane Doe

Questi esempi illustrano diversi punti:

  • I metodi Entries restituiscono voci per le entità in tutti gli stati, incluso Deleted. Confrontarlo con Local (Locale) che esclude le entità eliminate.
  • Le voci per tutti i tipi di entità vengono restituite quando viene usato il metodo delle voci non generiche. Quando viene utilizzato il metodo voci generiche, le voci vengono restituite solo per le entità che sono istanze del tipo generico. Questo è stato usato in precedenza per ottenere voci per tutti i blog. È stato usato anche per ottenere voci per tutte le entità che implementano IPerson. Ciò dimostra che il tipo generico non deve essere un tipo di entità effettivo.
  • È possibile usare LINQ to Objects per filtrare i risultati restituiti. Questa operazione è stata usata in precedenza per trovare le entità di qualsiasi tipo, purché vengano modificate.

Si noti che le istanze dbEntityEntry contengono sempre un'entità non Null. Le voci di relazione e le voci stub non sono rappresentate come istanze di DbEntityEntry, pertanto non è necessario filtrare tali voci.