本地数据Local Data

直接对 DbSet 运行 LINQ 查询将始终向数据库发送查询,但你可以使用 DbSet 属性访问当前在内存中的数据。 使用 DbContext 和 DbContext 方法,还可以访问额外的信息 EF 正在跟踪实体。 本主题所介绍的方法同样适用于查询使用 Code First 和 EF 设计器创建的模型。

使用本地查看本地数据Using Local to look at local data

DbSet 的 Local 属性提供对当前正在由上下文跟踪并且未标记为已删除的集的实体的简单访问。 访问本地属性绝不会使查询发送到数据库。 这意味着它通常在查询已执行后使用。 负载扩展方法可用于执行查询,以便上下文可以跟踪结果。 例如:

  1. using (var context = new BloggingContext())
  2. {
  3. // Load all blogs from the database into the context
  4. context.Blogs.Load();
  5. // Add a new blog to the context
  6. context.Blogs.Add(new Blog { Name = "My New Blog" });
  7. // Mark one of the existing blogs as Deleted
  8. context.Blogs.Remove(context.Blogs.Find(1));
  9. // Loop over the blogs in the context.
  10. Console.WriteLine("In Local: ");
  11. foreach (var blog in context.Blogs.Local)
  12. {
  13. Console.WriteLine(
  14. "Found {0}: {1} with state {2}",
  15. blog.BlogId,
  16. blog.Name,
  17. context.Entry(blog).State);
  18. }
  19. // Perform a query against the database.
  20. Console.WriteLine("\nIn DbSet query: ");
  21. foreach (var blog in context.Blogs)
  22. {
  23. Console.WriteLine(
  24. "Found {0}: {1} with state {2}",
  25. blog.BlogId,
  26. blog.Name,
  27. context.Entry(blog).State);
  28. }
  29. }

如果数据库中有两个博客-“ADO.NET Blog” 的 BlogId 为1,”Visual Studio 博客” 的 BlogId 为2,则可能会收到以下输出:

  1. In Local:
  2. Found 0: My New Blog with state Added
  3. Found 2: The Visual Studio Blog with state Unchanged
  4. In DbSet query:
  5. Found 1: ADO.NET Blog with state Deleted
  6. Found 2: The Visual Studio Blog with state Unchanged

这说明了三个要点:

  • 新博客的 “我的新博客” 包含在本地集合中,即使尚未将其保存到数据库中。 此博客的主键为零,因为数据库尚未生成实体的实际密钥。
  • 即使上下文仍在跟踪 “ADO.NET Blog”,也不会将其包含在本地集合中。 这是因为我们从 DbSet 中删除了它,从而将其标记为已删除。
  • 当使用 DbSet 来执行查询时,将在结果中包含标记为删除的博客(ADO.NET 博客),并且未保存到数据库中的新博客(我的新博客)不会包含在结果中。 这是因为,DbSet 正在对数据库执行查询,并且返回的结果始终反映了数据库中的内容。

使用本地来添加和删除上下文中的实体Using Local to add and remove entities from the context

DbSet 上的本地属性返回一个ObservableCollection ,其中包含与该上下文的内容保持同步的事件。 这意味着可以在本地集合或 DbSet 中添加或移除实体。 这也意味着,将新实体引入上下文的查询将导致用这些实体更新本地集合。 例如:

  1. using (var context = new BloggingContext())
  2. {
  3. // Load some posts from the database into the context
  4. context.Posts.Where(p => p.Tags.Contains("entity-framework")).Load();
  5. // Get the local collection and make some changes to it
  6. var localPosts = context.Posts.Local;
  7. localPosts.Add(new Post { Name = "What's New in EF" });
  8. localPosts.Remove(context.Posts.Find(1));
  9. // Loop over the posts in the context.
  10. Console.WriteLine("In Local after entity-framework query: ");
  11. foreach (var post in context.Posts.Local)
  12. {
  13. Console.WriteLine(
  14. "Found {0}: {1} with state {2}",
  15. post.Id,
  16. post.Title,
  17. context.Entry(post).State);
  18. }
  19. var post1 = context.Posts.Find(1);
  20. Console.WriteLine(
  21. "State of post 1: {0} is {1}",
  22. post1.Name,
  23. context.Entry(post1).State);
  24. // Query some more posts from the database
  25. context.Posts.Where(p => p.Tags.Contains("asp.net").Load();
  26. // Loop over the posts in the context again.
  27. Console.WriteLine("\nIn Local after asp.net query: ");
  28. foreach (var post in context.Posts.Local)
  29. {
  30. Console.WriteLine(
  31. "Found {0}: {1} with state {2}",
  32. post.Id,
  33. post.Title,
  34. context.Entry(post).State);
  35. }
  36. }

假设我们有几个标记有 “entity-框架” 和 “asp.net” 的文章,输出可能如下所示:

  1. In Local after entity-framework query:
  2. Found 3: EF Designer Basics with state Unchanged
  3. Found 5: EF Code First Basics with state Unchanged
  4. Found 0: What's New in EF with state Added
  5. State of post 1: EF Beginners Guide is Deleted
  6. In Local after asp.net query:
  7. Found 3: EF Designer Basics with state Unchanged
  8. Found 5: EF Code First Basics with state Unchanged
  9. Found 0: What's New in EF with state Added
  10. Found 4: ASP.NET Beginners Guide with state Unchanged

这说明了三个要点:

  • 添加到本地集合中的新 post “EF 新增功能” 将由上下文在已添加状态中进行跟踪。 因此,当调用 SaveChanges 时,它将被插入到数据库中。
  • 从本地集合中删除的 post (EF 初学者指南)现在已在上下文中标记为 “已删除”。 因此,当调用 SaveChanges 时,将从数据库中删除该方法。
  • 将第二个查询加载到上下文中的附加 post (ASP.NET 初学者指南)自动添加到本地集合中。

关于本地需要注意的最后一点是,因为这是一个 ObservableCollection 的性能,不适用于大量实体。 因此,如果你在上下文中处理数千个实体,则可能不建议使用本地。

使用本地进行 WPF 数据绑定Using Local for WPF data binding

DbSet 上的本地属性可以直接用于 WPF 应用程序中的数据绑定,因为它是 ObservableCollection 的实例。 如前面部分所述,这意味着它将与上下文内容自动保持同步,并且上下文的内容将自动与它保持同步。 请注意,您需要预先填充本地集合,其中包含的数据可以绑定到任何内容,因为本地从不会导致数据库查询。

这不是完整的 WPF 数据绑定示例的合适位置,但关键元素是:

  • 设置绑定源
  • 将其绑定到集的本地属性
  • 使用数据库的查询填充本地。

WPF 绑定到导航属性WPF binding to navigation properties

如果要执行主/详细数据绑定,则可能需要将详细信息视图绑定到某个实体的导航属性。 若要执行此操作,一种简单的方法是将 ObservableCollection 用于导航属性。 例如:

  1. public class Blog
  2. {
  3. private readonly ObservableCollection<Post> _posts =
  4. new ObservableCollection<Post>();
  5. public int BlogId { get; set; }
  6. public string Name { get; set; }
  7. public virtual ObservableCollection<Post> Posts
  8. {
  9. get { return _posts; }
  10. }
  11. }

使用 Local 在 SaveChanges 中清理实体Using Local to clean up entities in SaveChanges

在大多数情况下,从导航属性中删除的实体在上下文中不会自动标记为 “已删除”。 例如,如果从博客发布了一个 Post 对象,则在调用 SaveChanges 后,将不会自动删除此 post。 如果需要删除此实体,则可能需要在调用 SaveChanges 之前,或将其标记为已删除,然后再将其标记为已删除。 例如:

  1. public override int SaveChanges()
  2. {
  3. foreach (var post in this.Posts.Local.ToList())
  4. {
  5. if (post.Blog == null)
  6. {
  7. this.Posts.Remove(post);
  8. }
  9. }
  10. return base.SaveChanges();
  11. }

上面的代码使用本地集合查找所有帖子,并将没有博客引用的任何文章标记为已删除。 System.linq.enumerable.tolist 调用是必需的,因为在枚举时,删除调用会对集合进行修改。 在大多数其他情况下,您可以直接对本地属性进行查询,而不先使用 System.linq.enumerable.tolist。

为 Windows 窗体数据绑定使用本地和对 tobindinglist 获得Using Local and ToBindingList for Windows Forms data binding

Windows 窗体不支持直接使用 ObservableCollection 的完全保真数据绑定。 不过,你仍然可以使用 DbSet 本地属性进行数据绑定,以获得前面几节中所述的全部权益。 这是通过对 tobindinglist 获得扩展方法实现的,该方法创建由本地 ObservableCollection 支持的IBindingList实现。

此位置不适合完全 Windows 窗体的数据绑定示例,但关键元素是:

  • 设置对象绑定源
  • 使用对 tobindinglist 获得()将其绑定到集的本地属性
  • 使用数据库的查询填充本地

获取有关被跟踪实体的详细信息Getting detailed information about tracked entities

此系列中的许多示例使用 Entry 方法返回实体的 DbEntityEntry 实例。 然后,此 entry 对象充当用于收集有关实体的信息(如当前状态)的起始点,以及用于对实体执行操作(如显式加载相关实体)的起始点。

条目方法为上下文跟踪的多个或所有实体返回 DbEntityEntry 对象。 这允许您收集信息或对多个实体执行操作,而不是只收集单个条目。 例如:

  1. using (var context = new BloggingContext())
  2. {
  3. // Load some entities into the context
  4. context.Blogs.Load();
  5. context.Authors.Load();
  6. context.Readers.Load();
  7. // Make some changes
  8. context.Blogs.Find(1).Title = "The New ADO.NET Blog";
  9. context.Blogs.Remove(context.Blogs.Find(2));
  10. context.Authors.Add(new Author { Name = "Jane Doe" });
  11. context.Readers.Find(1).Username = "johndoe1987";
  12. // Look at the state of all entities in the context
  13. Console.WriteLine("All tracked entities: ");
  14. foreach (var entry in context.ChangeTracker.Entries())
  15. {
  16. Console.WriteLine(
  17. "Found entity of type {0} with state {1}",
  18. ObjectContext.GetObjectType(entry.Entity.GetType()).Name,
  19. entry.State);
  20. }
  21. // Find modified entities of any type
  22. Console.WriteLine("\nAll modified entities: ");
  23. foreach (var entry in context.ChangeTracker.Entries()
  24. .Where(e => e.State == EntityState.Modified))
  25. {
  26. Console.WriteLine(
  27. "Found entity of type {0} with state {1}",
  28. ObjectContext.GetObjectType(entry.Entity.GetType()).Name,
  29. entry.State);
  30. }
  31. // Get some information about just the tracked blogs
  32. Console.WriteLine("\nTracked blogs: ");
  33. foreach (var entry in context.ChangeTracker.Entries<Blog>())
  34. {
  35. Console.WriteLine(
  36. "Found Blog {0}: {1} with original Name {2}",
  37. entry.Entity.BlogId,
  38. entry.Entity.Name,
  39. entry.Property(p => p.Name).OriginalValue);
  40. }
  41. // Find all people (author or reader)
  42. Console.WriteLine("\nPeople: ");
  43. foreach (var entry in context.ChangeTracker.Entries<IPerson>())
  44. {
  45. Console.WriteLine("Found Person {0}", entry.Entity.Name);
  46. }
  47. }

你会注意到,我们在示例中引入了一个作者和读者类,这两个类都实现 IPerson 接口。

  1. public class Author : IPerson
  2. {
  3. public int AuthorId { get; set; }
  4. public string Name { get; set; }
  5. public string Biography { get; set; }
  6. }
  7. public class Reader : IPerson
  8. {
  9. public int ReaderId { get; set; }
  10. public string Name { get; set; }
  11. public string Username { get; set; }
  12. }
  13. public interface IPerson
  14. {
  15. string Name { get; }
  16. }

假设数据库中包含以下数据:

BlogId = 1 和 Name = ‘ ADO.NET Blog ‘ 的博客
BlogId = 2 的博客和名称 = ‘ Visual Studio 博客 ‘
BlogId = 3 且名称为 “.NET Framework 博客” 的博客
作者为 AuthorId = 1,名称 = “Joe Bloggs”
ReaderId = 1 且名称 = “John Doe” 的读取器

运行代码的输出为:

  1. All tracked entities:
  2. Found entity of type Blog with state Modified
  3. Found entity of type Blog with state Deleted
  4. Found entity of type Blog with state Unchanged
  5. Found entity of type Author with state Unchanged
  6. Found entity of type Author with state Added
  7. Found entity of type Reader with state Modified
  8. All modified entities:
  9. Found entity of type Blog with state Modified
  10. Found entity of type Reader with state Modified
  11. Tracked blogs:
  12. Found Blog 1: The New ADO.NET Blog with original Name ADO.NET Blog
  13. Found Blog 2: The Visual Studio Blog with original Name The Visual Studio Blog
  14. Found Blog 3: .NET Framework Blog with original Name .NET Framework Blog
  15. People:
  16. Found Person John Doe
  17. Found Person Joe Bloggs
  18. Found Person Jane Doe

这些示例阐释了几个要点:

  • 条目方法返回所有状态中实体的条目,包括 “已删除”。 将此与本地(不包括已删除的实体)进行比较。
  • 当使用非泛型条目方法时,将返回所有实体类型的条目。 当使用泛型条目方法时,仅为作为泛型类型的实例的实体返回条目。 上述内容用于获取所有博客的条目。 它还用于获取实现 IPerson 的所有实体的条目。 这表明泛型类型不一定是实际的实体类型。
  • LINQ to Objects 可用于筛选返回的结果。 在上述情况下,只要修改了任何类型的实体,就可以找到它。

请注意,DbEntityEntry 实例始终包含非 null 的实体。 关系项和存根项不表示为 DbEntityEntry 实例,因此无需对其进行筛选。