Données locales

L’exécution d’une requête LINQ directement sur un DbSet envoie toujours une requête à la base de données, mais vous pouvez accéder aux données actuellement en mémoire à l’aide de la propriété DbSet.Local. Vous pouvez également accéder aux informations supplémentaires qu'EF suit pour vos entités en utilisant les méthodes DbContext.Entry et DbContext.ChangeTracker.Entries. Les techniques présentées dans cette rubrique s’appliquent également aux modèles créés avec Code First et EF Designer.

Utilisation de Local pour examiner les données locales

La propriété locale de DbSet fournit un accès simple aux entités de l’ensemble qui sont actuellement suivies par le contexte et qui n’ont pas été marquées comme supprimées. L’accès à la propriété locale n’entraîne jamais l’envoi d’une requête à la base de données. Cela signifie qu’il est généralement utilisé une fois qu’une requête a déjà été effectuée. La méthode d’extension Load peut être utilisée pour exécuter une requête afin que le contexte effectue le suivi des résultats. Par exemple:

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);
    }
}

Si nous avions deux blogs dans la base de données - « ADO.NET Blog » avec un BlogId de 1 et « The Visual Studio Blog » avec un BlogId de 2 - nous pourrions vous attendre à la sortie suivante :

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

Cela illustre trois points :

  • Le nouveau blog « Mon nouveau blog » est inclus dans la collection locale, même s’il n’a pas encore été enregistré dans la base de données. Ce blog a une clé primaire de zéro, car la base de données n’a pas encore généré de clé réelle pour l’entité.
  • Le « blog ADO.NET » n’est pas inclus dans la collection locale, même s’il est toujours suivi par le contexte. Cela est dû au fait que nous l’avons supprimé de DbSet, ce qui l’a marqué comme supprimé.
  • Lorsque DbSet est utilisé pour effectuer une requête du blog marqué pour suppression (ADO.NET Blog) est inclus dans les résultats et le nouveau blog (Mon nouveau blog) qui n’a pas encore été enregistré dans la base de données n’est pas inclus dans les résultats. Cela est dû au fait que DbSet effectue une requête sur la base de données et que les résultats retournés reflètent toujours ce qui se trouve dans la base de données.

Utilisation de Local pour ajouter et supprimer des entités du contexte

La propriété locale sur DbSet retourne un ObservableCollection avec des événements connectés de sorte qu’il reste synchronisé avec le contenu du contexte. Cela signifie que les entités peuvent être ajoutées ou supprimées de la collection locale ou de DbSet. Cela signifie également que les requêtes qui apportent de nouvelles entités dans le contexte entraînent la mise à jour de la collection locale avec ces entités. Par exemple:

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);
    }
}

En supposant que nous avions quelques billets étiquetés avec « entity-framework » et « asp.net » la sortie peut ressembler à ceci :

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

Cela illustre trois points :

  • Le nouveau billet « What’s New in EF » qui a été ajouté à la collection locale est suivi par le contexte dans l’état Ajouté. Il sera donc inséré dans la base de données lorsque SaveChanges est appelé.
  • Le billet retiré de la collection locale (Guide du débutant EF) est désormais indiqué comme éliminé dans le contexte. Elle sera donc supprimée de la base de données lorsque SaveChanges est appelé.
  • Le billet supplémentaire (ASP.NET Guide des débutants) chargé dans le contexte avec la deuxième requête est automatiquement ajouté à la collection locale.

Une dernière chose à noter sur Local est qu'étant donné que c'est une ObservableCollection, la performance n'est pas optimale pour un grand nombre d’entités. Par conséquent, si vous traitez des milliers d’entités dans votre contexte, il est peut-être déconseillé d’utiliser Local.

Utilisation de local pour la liaison de données WPF

La propriété locale sur DbSet peut être utilisée directement pour la liaison de données dans une application WPF, car il s’agit d’une instance de ObservableCollection. Comme décrit dans les sections précédentes, cela signifie qu’il reste automatiquement synchronisé avec le contenu du contexte et que le contenu du contexte reste automatiquement synchronisé avec celui-ci. Notez que vous devez préremplir la collection locale avec des données pour qu’il y ait quelque chose à lier, car Local n’entraîne jamais une requête de base de données.

Il ne s’agit pas d’un emplacement approprié pour un exemple de liaison de données WPF complet, mais les éléments clés sont les suivants :

  • Configurer une source de liaison
  • Liez-le à la propriété locale de votre jeu
  • Remplissez le Local à l’aide d’une requête dans la base de données.

Liaison WPF aux propriétés de navigation

Si vous effectuez une liaison de données maître/détail, vous pouvez lier l’affichage des détails à une propriété de navigation de l’une de vos entités. Un moyen simple de faire fonctionner cela consiste à utiliser une ObservableCollection pour la propriété de navigation. Par exemple:

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; }
    }
}

Utilisation de Local pour nettoyer des entités dans SaveChanges

Dans la plupart des cas, les entités supprimées d’une propriété de navigation ne sont pas marquées automatiquement comme supprimées dans le contexte. Par exemple, si vous supprimez un objet Post de la collection Blog.Posts, ce billet ne sera pas automatiquement supprimé lorsque SaveChanges est appelé. Si vous avez besoin de les supprimer, vous devrez peut-être rechercher ces entités orphelines et les marquer comme supprimées avant d’appeler SaveChanges ou dans le cadre d'une méthode SaveChanges redéfinie. Par exemple:

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

    return base.SaveChanges();
}

Le code ci-dessus utilise la collection locale pour rechercher tous les billets et marquer toutes les publications qui n’ont pas de référence de blog comme supprimé. L’appel ToList est requis, car sinon la collection sera modifiée par l’appel Remove pendant qu’elle est énumérée. Dans la plupart des autres situations, vous pouvez interroger directement sur la propriété locale sans utiliser ToList en premier.

Utilisation de Local et de ToBindingList pour la liaison de données dans Windows Forms

Windows Forms ne prend pas en charge la liaison de données avec fidélité totale à l’aide de *ObservableCollection* directement. Toutefois, vous pouvez toujours utiliser la propriété DbSet Local pour la liaison de données pour obtenir tous les avantages décrits dans les sections précédentes. Pour ce faire, la méthode d’extension ToBindingList crée une implémentation IBindingList soutenue par Local ObservableCollection.

Il ne s’agit pas d’un emplacement approprié pour un exemple de liaison de données Windows Forms complet, mais les éléments clés sont les suivants :

  • Configurer une source de liaison d’objet
  • Liez-le à la propriété Local de votre ensemble à l’aide de Local.ToBindingList()
  • Remplir Local par une requête dans la base de données

Obtention d’informations détaillées sur les entités suivies

De nombreux exemples de cette série utilisent la méthode Entry pour renvoyer une instance DbEntityEntry pour une entité. Cet objet d’entrée agit ensuite comme point de départ pour collecter des informations sur l’entité, telles que son état actuel, ainsi que pour effectuer des opérations sur l’entité, telles que le chargement explicite d’une entité associée.

Les méthodes Entries retournent des objets DbEntityEntry pour de nombreuses entités ou toutes les entités suivies par le contexte. Cela vous permet de collecter des informations ou d’effectuer des opérations sur de nombreuses entités plutôt qu’une seule entrée. Par exemple:

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);
    }
}

Vous remarquerez que nous introduisons une classe Author et Reader dans l’exemple : ces deux classes implémentent l’interface 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; }
}

Supposons que nous disposons des données suivantes dans la base de données :

Blog avec BlogId = 1 et Name = 'ADO.NET Blog'
Blog avec BlogId = 2 et Name = 'The Visual Studio Blog'
Blog avec BlogId = 3 et Name = '.NET Framework Blog'
Auteur avec AuthorId = 1 et Name = 'Joe Bloggs'
Lecteur avec ReaderId = 1 et Nom = 'John Doe'

La sortie de l’exécution du code serait :

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

Ces exemples illustrent plusieurs points :

  • Les méthodes Entrées retournent des entrées pour les entités dans tous les états, y compris supprimés. Comparez ceci à Local, qui exclut les entités supprimées.
  • Les entrées pour tous les types d’entités sont retournées lorsque la méthode Entrées non générique est utilisée. Lorsque la méthode d’entrées génériques est utilisée, les entrées sont retournées uniquement pour les entités qui sont des instances du type générique. Cela a été utilisé ci-dessus pour obtenir des entrées pour tous les blogs. Il a également été utilisé pour obtenir des entrées pour toutes les entités qui implémentent IPerson. Cela montre que le type générique n’a pas besoin d’être un type d’entité réel.
  • LINQ to Objects peut être utilisé pour filtrer les résultats retournés. Cela a été utilisé ci-dessus pour rechercher des entités de n’importe quel type, à condition qu'elles soient modifiées.

Notez que les instances DbEntityEntry contiennent toujours une entité non null. Les entrées de relation et les entrées stub ne sont pas représentées en tant qu’instances DbEntityEntry. Il n’est donc pas nécessaire de filtrer ces entrées.