Condividi tramite


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 che Entity Framework monitora sui suoi enti usando i metodi DbContext.Entry e DbContext.ChangeTracker.Entries. Le tecniche illustrate in questo argomento si applicano allo stesso modo ai modelli creati con Code First e 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. Questo significa che viene generalmente utilizzato dopo che una query è stata già eseguita. Il metodo di estensione Load può essere usato per eseguire una query in modo che il contesto tenga traccia dei risultati. Per 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 avessimo nel database due blog: "ADO.NET Blog" con un BlogId di 1 e "Blog di Visual Studio" con un BlogId di 2, potremmo aspettarci il seguente 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à. Per 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 collezione Local viene tracciato 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 ultimo aspetto da considerare su Local è che, essendo un'ObservableCollection, le sue prestazioni non sono ottimali per un numero elevato di entità. Pertanto, se si gestiscono migliaia di entità nel contesto, potrebbe non essere consigliabile usare Local.

Uso di Local per il data binding in 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. Per 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à pendenti e contrassegnarle come eliminate prima di chiamare SaveChanges o come parte di un SaveChanges sottoposto a override. Per 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 il data binding di Windows Form

Windows Forms non supporta il data binding ad alta fedeltà usando direttamente 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 completo di data binding di 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 di ingresso funge quindi da punto di partenza per raccogliere informazioni sull'entità, come il suo stato corrente, nonché per eseguire operazioni sull'entità, come il caricamento esplicito di un'entità correlata.

I metodi Entries restituiscono oggetti DbEntityEntry per molte o tutte le entità rilevate dal contesto. Ciò consente di raccogliere informazioni o eseguire operazioni su molte entità anziché su una singola voce. Per 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'
Autore con AuthorId = 1 e Nome = 'Joe Bloggs'
Lettore con ID lettore = 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. Confronta questo con Local 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 utilizzato in precedenza per ottenere entrate 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.