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 qui se trouvent actuellement en mémoire à l’aide de la propriété DbSet.Local. Vous pouvez également accéder aux informations supplémentaires que suit EF concernant de vos entités à l’aide des 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é Local de DbSet fournit un accès simple aux entités du jeu qui sont actuellement suivies par le contexte et n’ont pas été marquées comme supprimées. L’accès à la propriété Local n’entraîne jamais l’envoi d’une requête à la base de données. Cela signifie qu’on l’utilise généralement 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 nous 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 'My New Blog' est inclus dans la collection Local, 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 Blog' n’est pas inclus dans la collection Local, 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, le blog marqué pour suppression (ADO.NET Blog) est inclus dans les résultats et le nouveau blog (My New Blog), qui n’a pas encore été enregistré dans la base de données, n’y est pas inclus. Cela est dû au fait que DbSet effectue une requête sur la base de données et que les résultats renvoyés reflètent toujours ce qui se trouve dans la base de données.

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

La propriété Local sur DbSet renvoie une ObservableCollection avec des événements connectés, de sorte qu’elle reste synchronisée avec le contenu du contexte. Cela signifie que des entités peuvent être ajoutées à la collection Local ou à DbSet, ou en être supprimées. Cela signifie également que les requêtes qui apportent de nouvelles entités dans le contexte entraînent la mise à jour de la collection Local 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);
    }
}

Si nous avions quelques publications étiquetés avec 'entity-framework' et 'asp.net', la sortie pourrait 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 :

  • La nouvelle publication 'What’s New in EF' qui a été ajoutée à la collection Local est alors suivie par le contexte dans l’état Added. Elle est donc insérée dans la base de données lorsque SaveChanges est appelé.
  • La publication supprimée de la collection Local (EF Beginners Guide) est désormais marqué comme supprimé dans le contexte. Elle est donc supprimée de la base de données lorsque SaveChanges est appelé.
  • La publication supplémentaire (ASP.NET Beginners Guide) chargée dans le contexte avec la deuxième requête est automatiquement ajoutée à la collection Local.

Une dernière chose à noter concernant Local est que, comme il s’agit d’une ObservableCollection, la performance peut être insuffisante pour un grand nombre d’entités. Par conséquent, si vous traitez des milliers d’entités dans votre contexte, il n’est pas nécessairement conseillé d’utiliser Local.

Utilisation de Local pour la liaison de données WPF

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

Ceci n’est pas l’endroit 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
  • La lier à la propriété Local de votre jeu
  • Remplir Local à l’aide d’une requête sur 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 vouloir lier l’affichage des détails à une propriété de navigation de l’une de vos entités. Un moyen simple de rendre cela possible 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 automatiquement marquées comme supprimées dans le contexte. Par exemple, si vous supprimez un objet Post de la collection Blog.Posts, cette publication n’est pas automatiquement supprimée lorsque SaveChanges est appelé. Si vous avez besoin qu’elle soit supprimée, vous devrez peut-être rechercher ces entités non résolues et les marquer comme supprimées avant d’appeler SaveChanges ou dans le cadre d’un SaveChanges remplacé. 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 Local pour rechercher toutes les publications et marquer celles qui n’ont pas de référence de blog comme supprimées. L’appel ToList est requis, car autrement la collection sera modifiée par l’appel Remove lorsqu’elle sera énumérée. Dans la plupart des autres situations, vous pouvez interroger directement sur la propriété Local sans d’abord utiliser ToList.

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

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

Ceci n’est pas l’endroit 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
  • Le lier à la propriété Local de votre jeu à l’aide de Local.ToBindingList()
  • Remplir Local à l’aide d’une requête sur 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 Entry 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 renvoient 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 que sur 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 une classe 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 ayons les 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'
Author avec AuthorId = 1 et Name = 'Joe Bloggs'
Reader avec ReaderId = 1 et Name = '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 Entries renvoient des entrées pour les entités quel que soit leur état, y compris les entités supprimées. Comparez cela à Local, qui exclut les entités supprimées.
  • Les entrées pour tous les types d’entités sont renvoyées lorsque la méthode Entries non générique est utilisée. Lorsque la méthode Entries générique est utilisée, les entrées sont renvoyées uniquement pour les entités qui sont des instances du type générique. C’est ce qui a été utilisé ci-dessus pour obtenir des entrées pour tous les blogs. C’est ce qui 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 renvoyés. C’est ce qui a été utilisé ci-dessus pour rechercher des entités de n’importe quel type tant qu’elles sont modifiées.

Notez que les instances DbEntityEntry contiennent toujours une entité non définie sur la valeur 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 les filtrer.