Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Executar uma consulta LINQ diretamente contra um DbSet enviará sempre uma consulta para a base de dados, mas pode aceder aos dados que estão atualmente em memória usando a propriedade DbSet.Local. Também pode aceder à informação extra que EF está a rastrear sobre as suas entidades usando os métodos DbContext.Entry e DbContext.ChangeTracker.Entries. As técnicas apresentadas neste tópico aplicam-se igualmente a modelos criados com o Code First e o EF Designer.
Usar Local para analisar dados locais
A propriedade Local do DbSet fornece acesso simples às entidades do conjunto que estão atualmente a ser rastreadas pelo contexto e que não foram marcadas como Eliminadas. Aceder à propriedade Local nunca faz com que uma consulta seja enviada para a base de dados. Isto significa que normalmente é usado depois de uma consulta já ter sido realizada. O método de extensão Load pode ser usado para executar uma consulta de modo a 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 na base de dados – 'ADO.NET Blog' com um BlogId de 1 e 'The Visual Studio Blog' com um BlogId de 2 – poderíamos esperar o seguinte resultado:
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
Isto ilustra três pontos:
- O novo blogue 'O Meu Novo Blog' está incluído na coleção Local, embora ainda não tenha sido guardado na base de dados. Este blogue tem uma chave primária zero porque a base de dados ainda não gerou uma chave real para a entidade.
- O 'ADO.NET Blog' não está incluído na coleção local, apesar de ainda estar a ser acompanhado pelo contexto. Isto porque o removemos do DbSet, marcando-o assim como eliminado.
- Quando o DbSet é usado para realizar uma consulta, o blog marcado para eliminação (ADO.NET Blog) é incluído nos resultados e o novo blog (My New Blog) que ainda não foi guardado na base de dados não está incluído nos resultados. Isto porque o DbSet está a realizar uma consulta na base de dados e os resultados devolvidos refletem sempre o que está na base de dados.
Usar Local para adicionar e remover entidades do contexto
A propriedade Local no DbSet devolve uma ObservableCollection com eventos ligados de modo a que se mantenha sincronizada com o conteúdo do contexto. Isto significa que as entidades podem ser adicionadas ou removidas tanto da coleção Local como do DbSet. Isto 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);
}
}
Assumindo que tivemos alguns posts marcados com 'entity-framework' e 'asp.net', o resultado 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
Isto ilustra três pontos:
- A nova publicação 'O que há de novo no EF' que foi adicionada à coleção Local passa a ser acompanhada pelo contexto no estado Adicionado. Por isso, será inserido na base de dados quando o SaveChanges é chamado.
- A publicação que foi removida da coleção Local (EF Beginners Guide) está agora marcada como eliminada no contexto. Assim, será eliminado da base de dados quando o SaveChanges for chamado.
- O post adicional (ASP.NET Guia para Iniciantes) carregado no contexto com a segunda consulta é automaticamente adicionado à coleção Local.
Uma última coisa a notar sobre o Local é que, por ser uma ObservableCollection, o desempenho não é bom para um grande número de entidades. Portanto, se estiver a lidar com milhares de entidades no seu contexto, pode não ser aconselhável usar o Local.
Utilização de recursos locais para associação de dados WPF
A propriedade Local no DbSet pode ser usada diretamente para ligação de dados numa aplicação WPF porque é uma instância do ObservableCollection. Como descrito nas secções anteriores, isto significa que o conteúdo permanecerá automaticamente sincronizado com o conteúdo do contexto e o conteúdo do contexto permanecerá automaticamente sincronizado com ele. Note que precisa de pré-preencher a coleção Local com dados para que haja algo a que se ligar, já que Local nunca causa uma consulta à base de dados.
Este não é um local apropriado para uma amostra completa de ligação de dados WPF, mas os elementos-chave são:
- Configurar uma fonte de ligação
- Vincule-o à propriedade Local do seu conjunto de dados
- Popule a variável Local utilizando uma consulta na base de dados.
Vinculação WPF às propriedades de navegação
Se estiver a fazer associação de dados mestre/detalhe, pode querer associar a visualização detalhada a uma propriedade de navegação de uma das suas entidades. Uma forma simples de fazer isto funcionar é usar uma 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; }
}
}
Usar Local para limpar entidades ao executar SaveChanges
Na maioria dos casos, entidades removidas de uma propriedade de navegação não serão automaticamente marcadas como eliminadas no contexto. Por exemplo, se remover um objeto Post da coleção Blog.Posts, essa publicação não será automaticamente apagada quando o SaveChanges for chamado. Se precisar que algo seja eliminado, pode ser necessário encontrar essas entidades órfãs e marcá-las como eliminadas antes de chamar o 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 encontrar todas as publicações e marca como eliminadas quaisquer que não tenham referência de blog. A chamada ToList é necessária porque, caso contrário, a coleção será modificada pela chamada Remove enquanto está a ser enumerada. Na maioria das outras situações, pode consultar diretamente a propriedade Local sem primeiro usar o ToList.
Utilização de Local e ToBindingList para ligação de dados no Windows Forms
O Windows Forms não suporta ligação de dados de fidelidade total usando diretamente o ObservableCollection. No entanto, ainda pode usar a propriedade DbSet Local para ligação de dados e obter todos os benefícios descritos nas secções anteriores. Isto é conseguido através do método de extensão ToBindingList, que cria uma implementação IBindingList apoiada pelo Local ObservableCollection.
Este não é um local apropriado para uma amostra completa de ligação de dados do Windows Forms, mas os elementos-chave são:
- Configurar uma fonte de ligação a objetos
- Vincule-o à propriedade Local do seu conjunto usando Local.ToBindingList()
- Preenche Local usando uma consulta à base de dados
Obter informações detalhadas sobre entidades rastreadas
Muitos dos exemplos desta série usam o método Entry para devolver uma instância DbEntityEntry para uma entidade. Este objeto de entrada atua então como ponto de partida para recolher informações sobre a entidade, como o seu estado atual, bem como para realizar operações sobre a entidade, como carregar explicitamente uma entidade relacionada.
Os métodos Entries devolvem objetos DbEntityEntry para muitas ou todas as entidades que estão a ser rastreadas pelo contexto. Isto permite-lhe recolher informação ou realizar operações em várias entidades em vez de apenas uma 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);
}
}
Vai reparar que estamos a introduzir uma classe Author e uma Reader no exemplo – ambas 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 assumir que temos os seguintes dados na base de dados:
Blog com BlogId = 1 e Nome = 'ADO.NET Blog'
Blog com BlogId = 2 e Nome = 'The Visual Studio Blog'
Blog com BlogId = 3 e Nome = '.NET Framework Blog'
Autor com AuthorId = 1 e Nome = '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
Estes exemplos ilustram vários pontos:
- Os métodos Entradas devolvem entradas para entidades em todos os estados, incluindo Eliminado. Compare isto com Local, que exclui entidades Eliminadas.
- As entradas de todos os tipos de entidades são retornadas quando se utiliza o método Entradas não genérico. Quando o método de entradas genéricas é utilizado, as entradas só são devolvidas para entidades que são instâncias do tipo genérico. Isto foi usado acima para obter entradas para todos os blogs. Também foi usado para obter entradas para todas as entidades que implementam IPerson. Isto demonstra que o tipo genérico não tem de ser um tipo de entidade real.
- LINQ para Objetos pode ser usado para filtrar os resultados devolvidos. Isto foi usado acima para encontrar entidades de qualquer tipo, desde que tenham sido modificadas.
Note que as instâncias DbEntityEntry contêm sempre uma Entidade não nula. As entradas de relação e entradas de stub não são representadas como instâncias DbEntityEntry, pelo que não é necessário filtrar por estas.