Compartilhar via


Dados Locais

Executar uma consulta LINQ diretamente em um DbSet sempre enviará uma consulta para o banco de dados, mas você pode acessar os dados que estão atualmente na memória usando a propriedade DbSet.Local. Você também pode acessar as informações extras que o EF está acompanhando sobre suas entidades usando os métodos DbContext.Entry e DbContext.ChangeTracker.Entries. As técnicas mostradas neste tópico se aplicam igualmente a modelos criados com o Code First e com o EF Designer.

Como usar Local para examinar os dados locais

A propriedade Local do DbSet fornece acesso simples às entidades do conjunto que estão sendo controladas pelo contexto e não foram marcadas como Excluídas. Acessar a propriedade Local nunca faz com que uma consulta seja enviada ao banco de dados. Isso significa que ela geralmente é usada depois que uma consulta já foi executada. O método de extensão Load pode ser usado para executar uma consulta para que o contexto acompanhe os resultados. Por exemplo:

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 tivéssemos dois blogs no banco de dados, "Blog do ADO.NET " com uma BlogId de 1 e "O Blog do Visual Studio" com uma BlogId de 2, poderíamos esperar a seguinte saída:

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

Isso ilustra três pontos:

  • O novo blog "Meu Novo Blog" está incluído na coleção Local, embora ainda não tenha sido salvo no banco de dados. Esse blog tem uma chave primária de zero porque o banco de dados ainda não gerou uma chave real para a entidade.
  • O "Blog do ADO.NET" não está incluído na coleção local, embora ainda esteja sendo acompanhado pelo contexto. Isso ocorre porque o removemos do DbSet marcando-o como excluído.
  • Quando dbSet é usado para executar uma consulta, o blog marcado para exclusão (Blog ADO.NET) é incluído nos resultados e o novo blog (Meu Novo Blog) que ainda não foi salvo no banco de dados não está incluído nos resultados. Isso ocorre porque o DbSet está executando uma consulta no banco de dados e os resultados retornados sempre refletem o que está no banco de dados.

Usando Local para adicionar e remover entidades do contexto

A propriedade Local no DbSet retorna um ObservableCollection com eventos conectados de modo que ele permaneça em sincronia com o conteúdo do contexto. Isso significa que as entidades podem ser adicionadas ou removidas da coleção Local ou do DbSet. Isso também significa que consultas que trazem novas entidades para o contexto resultarão na atualização da coleção Local com essas entidades. Por exemplo:

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

Supondo que tivemos algumas postagens marcadas com "entity-framework" e "asp.net" , a saída pode ser algo assim:

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

Isso ilustra três pontos:

  • A nova postagem "Quais as novidades do EF" que foi adicionada à coleção Local torna-se controlada pelo contexto no estado Adicionado. Portanto, ele será inserido no banco de dados quando SaveChanges for chamado.
  • A postagem que foi removida da coleção Local (Guia para Iniciantes no EF) agora está marcada como excluída no contexto. Portanto, ele será excluído do banco de dados quando SaveChanges for chamado.
  • A postagem adicional (Guia para Iniciantes no EF) carregada no contexto com a segunda consulta é adicionada automaticamente à coleção Local.

Uma última coisa a observar sobre Local é que, por ser um desempenho ObservableCollection, não é o ideal para um grande número de entidades. Portanto, se você estiver lidando com milhares de entidades em seu contexto, talvez não seja aconselhável usar Local.

Como usar Local para associação de dados do WPF

A propriedade Local no DbSet pode ser usada diretamente para associação de dados em um aplicativo WPF porque é uma instância de ObservableCollection. Conforme descrito nas seções anteriores, isso significa que ele permanecerá automaticamente em sincronia com o conteúdo do contexto e o conteúdo do contexto permanecerá automaticamente sincronizado com ele. Observe que você precisa preencher previamente a coleção local com dados para que haja algo a ser associado, pois Local nunca causa uma consulta de banco de dados.

Esse não é um local apropriado para um exemplo completo de associação de dados do WPF, mas os principais elementos são:

  • Configurar uma origem de associação
  • Associá-lo à propriedade Local do seu conjunto
  • Preencha Local usando uma consulta para o banco de dados.

Associação do WPF às propriedades de navegação

Se você estiver fazendo a associação de dados mestre/detalhe, talvez queira associar o modo de exibição de detalhes a uma propriedade de navegação de uma de suas entidades. Uma maneira fácil de fazer isso funcionar é usar um ObservableCollection para a propriedade de navegação. Por exemplo:

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

Usando Local para limpar entidades no SaveChanges

Na maioria dos casos, as entidades removidas de uma propriedade de navegação não serão marcadas automaticamente como excluídas no contexto. Por exemplo, se você remover um objeto Post da coleção Blog.Posts, essa postagem não será excluída automaticamente quando SaveChanges for chamado. Se você precisar que ele seja excluído, talvez seja necessário encontrar essas entidades pendentes e marcá-las como excluídas antes de chamar SaveChanges ou como parte de um SaveChanges substituído. Por exemplo:

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

    return base.SaveChanges();
}

O código acima usa a coleção Local para localizar todas as postagens e marca qualquer uma que não tenha uma referência de blog como excluída. A chamada ToList é necessária porque, caso contrário, a coleção será modificada pela chamada Remover enquanto está sendo enumerada. Na maioria das outras situações, você pode consultar diretamente a propriedade Local sem usar ToList primeiro.

Como usar a associação de dados Local e ToBindingList para Windows Forms

O Windows Forms não dá suporte à associação de dados de fidelidade total usando ObservableCollection diretamente. No entanto, você ainda pode usar a propriedade DbSet Local para associação de dados para obter todos os benefícios descritos nas seções anteriores. Isso é feito por meio do método de extensão ToBindingList que cria uma implementação IBindingList apoiada pelo Local ObservableCollection.

Este não é um local apropriado para um exemplo completo de associação de dados do Windows Forms, mas os principais elementos são:

  • Configurar uma origem de associação de objeto
  • Associe-o à propriedade Local do seu conjunto usando Local.ToBindingList()
  • Preencha Local usando uma consulta para o banco de dados

Como obter informações detalhadas sobre entidades controladas

Muitos dos exemplos nessa série usam o método Entry para retornar uma instância DbEntityEntry para uma entidade. Esse objeto de entrada atua como ponto de partida para coletar informações sobre a entidade, como seu estado atual, bem como para executar operações na entidade, como carregar explicitamente uma entidade relacionada.

Os métodos Entries retornam objetos DbEntityEntry para muitas ou todas as entidades que estão sendo controladas pelo contexto. Isso permite que você colete informações ou execute operações em muitas entidades, em vez de apenas uma única entrada. Por exemplo:

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

Você observará que estamos introduzindo uma classe Author e Reader no exemplo : ambas as classes implementam a 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; }
}

Vamos supor que temos os seguintes dados no banco de dados:

Blog com BlogId = 1 e Nome = "blog ADO.NET"
Blog com BlogId = 2 e Nome = "O Blog do Visual Studio"
Blog com BlogId = 3 e Nome = "Blog do.NET Framework"
Autor com AuthorId = 1 e Name = "Joe Bloggs"
Leitor com ReaderId = 1 e Nome = "John Doe"

A saída da execução do código seria:

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

Esses exemplos ilustram vários pontos:

  • Os métodos Entries retornam entradas para entidades em todos os estados, incluindo Deleted. Compare isso com Local, que exclui entidades excluídas.
  • As entradas para todos os tipos de entidade são retornadas quando o método não genérico Entries é usado. Quando o método Entries genérico é usado, as entradas são retornadas apenas para entidades que são instâncias do tipo genérico. Isso foi usado acima para obter entradas para todos os blogs. Ele também foi usado para obter entradas para todas as entidades que implementam IPerson. Isso demonstra que o tipo genérico não precisa ser um tipo de entidade real.
  • LINQ to Objects pode ser usado para filtrar os resultados retornados. Isso foi usado acima para localizar entidades de qualquer tipo, desde que sejam modificadas.

Observe que as instâncias DbEntityEntry sempre contêm uma entidade não nula. Entradas de relação e entradas de stub não são representadas como instâncias DbEntityEntry, portanto, não há necessidade de filtrar para elas.