翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇

8-6  获取原始对象

问题

  你正在使用POCO,想从数据库获取原始对象。

解决方案

  假设你有如图8-7所示的模型。你正在离线环境下工作,你想应用在获取客户端修改之前,使用Where从句和FirstDefault()方法从数据库中获取原始对象。

图8-7.包含一个单独实体Item的模型

  按代码清单8-9的方式,在获取实体之后,使用新值更新实体并将其保存到数据库中。

代码清单8-9. 获取最新添加的实体并使用Entry()方法替换它的值

class Program{static void Main(string[] args){RunExample();}static void RunExample(){int itemId = 0;using (var context = new EFRecipesEntities()){var item = new Item{Name = "Xcel Camping Tent",UnitPrice = 99.95M};context.Items.Add(item);context.SaveChanges();//为下一步保存itemIditemId = item.ItemId;Console.WriteLine("Item: {0}, UnitPrice: {1}",item.Name, item.UnitPrice.ToString("C"));}using (var context = new EFRecipesEntities()){//假设这是一个更新,我们获取的item使用一个新的价格var item = new Item{ItemId = itemId,Name = "Xcel Camping Tent",UnitPrice = 129.95M};var originalItem = context.Items.Where(x => x.ItemId ==  itemId).FirstOrDefault<Item>();context.Entry(originalItem).CurrentValues.SetValues(item);context.SaveChanges();}using (var context = new EFRecipesEntities()){var item = context.Items.Single();Console.WriteLine("Item: {0}, UnitPrice: {1}", item.Name,item.UnitPrice.ToString("C"));}Console.WriteLine("Enter input as exit to exit.:");string line = Console.ReadLine();if (line == "exit"){return;};}}public partial class Item{public int ItemId { get; set; }public string Name { get; set; }public decimal UnitPrice { get; set; }}public partial class EFRecipesEntities : DbContext{public EFRecipesEntities(): base("name=EFRecipesEntities"){}protected override void OnModelCreating(DbModelBuilder modelBuilder){throw new UnintentionalCodeFirstException();}public DbSet<Item> Items { get; set; }}

代码清单的输出如下:

Item: Xcel Camping Tent, UnitPrice: $99.95
Item: Xcel Camping Tent, UnitPrice: $129.95

原理

  在代码清单8-9中,我们插入一个item到模型中并保存到数据库中。然后我们假装接收到了一个更新的item,也许是来至一个Silverlight客户端。

  接下来,我们要更新这个item到数据库中。为了实现这个操作,我们从数据库中获取这个对象到上下文中。在这个过程中,我们使用了where从句和FirstOrDefault方法。然后,我们使用方法Entry(),它能访问整个实体,并且,能在实体上应用很多方法。我们应用了CureentValues.SetVaules方法,用从客户端接受到的新值来替换原始值。最后,我们调用SaveChages保存实体。

8-7  手工同步对象图和变化跟踪器

问题

  你想手工同步你的POCO类和更化跟踪器。

  更化跟踪器会访问实体框架正在存储的,且正在被跟踪的实体的信息。这些信息不只是你存储在实体属性中的值,还包含实体的当前状态、从数据库获取的原始值、哪些属性被修改了以及别的数据。变化跟踪器提供额外操作的访问,这些操作能在实体上执行,例如,重新从数据库中加载值 ,以确保你有最新数据。

  实体框架有两种不的方法来跟踪你的对象:基于快照的变化跟踪和变化跟踪代理。

基于快照的变化跟踪

  POCO类不包含任何的,当属性值发生更改时,通知实体框架的逻辑。因为,当一个属性的值发生变化时,没有任何可以通知的方式。当实体框架第一次遇到一个对象时,它会为每一个属性的值在内存中生成一个快照。当一个对象从查询中返回,或者我们添加一个对象到DbSet中时,快照就被生成了。当实体框架需要知道发生了哪些变化时,它会浏览每一个对象并用他们的当前值和快照比较。这个处理过程是被一个在变化跟踪器中名为DetectChanges的方法触发的。

变化跟踪代理

  变化跟踪的另一项技术是变化跟踪代理,它能使实体框架具有接收变更通知的能力。 变化跟踪代理是使用实现延迟加载时动态创建代理的机制来实现的,这个动态创建的代理,不只提供延迟加载的功能,当对象发生改变时,它还能通知上下文对象。为了使用变化跟踪代理,你创建的类必须满足,实体框架能在运行时创建一个派生至你的POCO类的动态类型,在这个类型中,它重载了每个属性。这个动态类型叫做动态代理,在重载属性中包含当属性值发生改变时,通知实体框架的逻辑。

  基于快照的变化跟踪,依赖实体框架在变化发生时能执行检测。DbContext API的默认行为是,通过DbContext上的事件来自动执行检测。DetectChanges不仅仅是更新上下文的状态管理信息,这些状态管理信息能让更改持久化到数据库中,当你有一个引用类型导航属性,集合类型导航属性和外键的组合时,它还能执行关系修正。清晰认识变化什么时候被检测,这些变化要做什么,以及如何控制它们是非常重要的。

  实体框架需要知道变更最明显的时间是在执行SaveChanges期间。还有很多地方也需要知道变更的情况。比如,询问更化跟踪器实体对象的当前状态时,它需要扫描并检测任何发生的变更。扫描和检没并不只发生在我们讨论的问题中,当你执行DbContext中的很多API都能引起DetectChanges方法的运行。在绝大多数情况下,DetectChanges方法足够快,不会引起性能问题。但是,如果在内存中有大量的对象或者在DbContext中接二连三地执行操作,自变更检测行为就会成为性能关注点。幸运的是,有选项可以把这个自动变更检测关闭掉,并在需要时手工调用它。但稍有不慎,可能会导致意想不到的后果。实体框架精心地为你提供了关闭自动更变检测的功能。如果在性能差的地方使用它时,你将负担起调用DetectChages方法的责任。一旦这个部分的代码执行完毕,就得通过DbContext.Configuration.AutoDetectChangesEnable标识把它启用。

  

解决方案

  假设你有如图8-8所示的模型。它描述了演示者和为各种会议准备的演讲。

图8-8. 演讲者和他准备的演讲之间多对多关系模型

  

  首先需要注意的是,在我们的模型中,Speaker和 Talk之间是多对多关联。我们通过独立关联来实现(在数据库中使用SpeakerTalk链接表)这个模型,让它支持一个演讲者对应多场演讲,一场演讲对应多位演讲者。

  我们想手工同步对象图和变化跟踪器。通过调用DetectChanges()方法来实现。同时,将演示同步是如何进行的。

  按代码清单8-10的方法,手工同步你的POCO实体对象图和变化跟踪器。

代码清单8-10. 当需要手工同步变化跟踪器时,显式使用DetectChages()方法

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             RunExample();
 6         }
 7
 8         static void RunExample()
 9         {
10             using (var context = new EFRecipesEntities())
11             {
12                 context.Configuration.AutoDetectChangesEnabled = false;
13                 var speaker1 = new Speaker { Name = "Karen Stanfield" };
14                 var talk1 = new Talk { Title = "Simulated Annealing in C#" };
15                 speaker1.Talks = new List<Talk> { talk1 };
16
17                 //关联尚未完成
18                 Console.WriteLine("talk1.Speaker is null: {0}",
19                                     talk1.Speakers == null);
20
21                 context.Speakers.Add(speaker1);
22
23                 //现在关联已经修正
24                 Console.WriteLine("talk1.Speaker is null: {0}",
25                                     talk1.Speakers == null);
26                 Console.WriteLine("Number of added entries tracked: {0}",
27                                     context.ChangeTracker.Entries().Where(e => e.State == System.Data.Entity.EntityState.Added).Count());
28                 context.SaveChanges();
29                 //修改talk的标题
30                 talk1.Title = "AI with C# in 3 Easy Steps";
31                 Console.WriteLine("talk1's state is: {0}",
32                                     context.Entry(talk1).State);
33                 context.ChangeTracker.DetectChanges();
34                 Console.WriteLine("talk1's state is: {0}",
35                                     context.Entry(talk1).State);
36                 context.SaveChanges();
37             }
38
39             using (var context = new EFRecipesEntities())
40             {
41                 foreach (var speaker in context.Speakers.Include("Talks"))
42                 {
43                     Console.WriteLine("Speaker: {0}", speaker.Name);
44                     foreach (var talk in speaker.Talks)
45                     {
46                         Console.WriteLine("\tTalk Title: {0}", talk.Title);
47                     }
48                 }
49             }
50         }
51     }
52     public partial class Speaker
53     {
54         public int SpeakerId { get; set; }
55         public string Name { get; set; }
56         public ICollection<Talk> Talks { get; set; }
57     }
58     public partial class Talk
59     {
60         public int TalkId { get; set; }
61         public string Title { get; set; }
62         public System.DateTime CreateDate { get; set; }
63         public System.DateTime RevisedDate { get; set; }
64         public ICollection<Speaker> Speakers { get; set; }
65     }
66  public partial class EFRecipesEntities : DbContext
67     {
68         public EFRecipesEntities()
69             : base("name=EFRecipesEntities")
70         {
71         }
72
73         protected override void OnModelCreating(DbModelBuilder modelBuilder)
74         {
75             throw new UnintentionalCodeFirstException();
76         }
77
78         public DbSet<Speaker> Speakers { get; set; }
79         public DbSet<Talk> Talks { get; set; }
80
81         public override int SaveChanges()
82         {
83             var changeSet = this.ChangeTracker.Entries().Where(e => e.Entity is Talk);
84             if (changeSet != null)
85             {
86                 foreach (var entry in changeSet.Where(c => c.State == System.Data.Entity.EntityState.Added).Select(a => a.Entity as Talk))
87                 {
88                     entry.CreateDate = DateTime.UtcNow;
89                     entry.RevisedDate = DateTime.UtcNow;
90                 }
91                 foreach (var entry in changeSet.Where(c => c.State == System.Data.Entity.EntityState.Modified).Select(a => a.Entity as Talk))
92                 {
93                     entry.RevisedDate = DateTime.UtcNow;
94                 }
95             }
96             return base.SaveChanges();
97         }
98     }

代码清单8-10的输出如下:

talk1.Speaker is null: True
talk1.Speaker is null: False
Number of added entries tracked: 2
talk1's state is: Unchanged
talk1's state is: Modified
Speaker: Karen StanfieldTalk Title: AI with C# in 3 Easy Steps

原理

  代码清单8-10中的代码有点复杂,让我们一步一步的来讲解。首先,关闭自动跟踪,创建speaker和talk实例,然后把talk添加到speaker的导航属性集合Talks中。此时,talk已经是speaker的导航属性集合Talks的一部分,但是speaker还不是talk的导航属性集合Speakers的一部分。关联中的另一边还没有被修正。

  接下来,我们使用Add方法,将speaker1添加到上下文中。从输出的第二行可以看出,现在,talk的导航属性集合Speakers已经正确。实体框架已经修正了关联中的另一边。在这里实体框架做了两件事,第一件事是它通知对象状态管理器,有三个对象被创建,虽然最终输出结果不是三个,这是因为它把多对多关联看作是一个独立关系,而不是一个单独的实体。因此,输出结果为2。这些对象分别是speaker和talk。没有多对多关联对应的对象。这是因为变化跟踪器没有返回独立关系的状态。第二件事是实体框架修正了talk中的导航属性Speakers。

  当我们调用SaveChages()方法时,实体框架会使用重载版本的Savechanges。在这个方法中,我们更新属性 CreateDate和RevisedDate。在调用SaveChanges()方法之前,实体框架会调用DetectChanges()来查找发生的变更。在代码清单8-10中,我们重写了SaveChages()方法。

  DetectChanges()方法依赖一个快照来比较每一个实体的每一个属性的原始值和当前值。这个过程能判断出对象图中那些变化发生了。对于一个大的对象图,这个比较过程可能会比较耗时。

  

实体框架交流QQ群:  458326058,欢迎有兴趣的朋友加入一起交流

谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/

转载于:https://www.cnblogs.com/VolcanoCloud/p/4549225.html

《Entity Framework 6 Recipes》中文翻译系列 (45) ------ 第八章 POCO之获取原始对象与手工同步对象图和变化跟踪器...相关推荐

  1. 《Entity Framework 6 Recipes》中文翻译系列 (32) ------ 第六章 继承与建模高级应用之TPH与TPT (1)...

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-6  映射派生类中的NULL条件 问题 你的表中,有一列允许为null.你想使用 ...

  2. Entity Framework 6 Recipes 2nd Edition(9-1)译-用Web Api更新单独分离的实体

    第九章 在N层结构的应用程序中使用EF 不是所有的应用都能完全地写入到一个单个的过程中(就是驻留在一个单一的物理层中),实际上,在当今不断发展的网络世界,大量的应用程序的结构包含经典的表现层,应用程, ...

  3. Entity Framework 6 Recipes 2nd Edition(13-2)译 - 用实体键获取一个单独的实体

    问题 不管你用DBFirst,ModelFirst或是CodeFirst的方式,你想用实体键获取一个单独的实体.在本例中,我们用CodeFirst的方式. 解决方案 假设你有一个模型表示一个Paint ...

  4. Entity Framework 6 Recipes 2nd Edition(13-4)译 - 有效地创建一个搜索查询

    问题 你想用LINQ写一个搜索查询,能被转换成更有效率的SQL.另外,你想用EF的CodeFirst方式实现. 解决方案 假设你有如下Figure 13-6所示的模型 Figure 13-6. A s ...

  5. Entity Framework 6 Recipes 2nd Edition(9-7)译-在WCF服务中序列化代理

    9-7. 在WCF服务中序列化代理 问题 从一个查询里返回一个动态代理对象,想要把它序列为一个POCO(Plain-Old CLR Objects)对象. 实现基于POCO实体对象, 在运行时,EF会 ...

  6. Entity Framework 6 Recipes 2nd Edition(10-6)译 - TPT继承模型中使用存储过程

    10-6. TPT继承模型中使用存储过程 问题 想在一个TPT继承模型中使用存储过程 解决方案 假设已有如Figure 10-6所示模型. 在模型里, Magazine(杂志) and DVD继承于基 ...

  7. Entity Framework 6 Recipes 2nd Edition(9-2)译-用WCF更新单独分离的实体

    9-2. 用WCF更新单独分离的实体 问题 你想通过WCF为一个数据存储发布查询,插入,删除和修改,并且使这些操作尽可能地简单 此外,你想通过Code First方式实现EF6的数据访问管理 解决方案 ...

  8. Entity Framework 6 Recipes 2nd Edition(12-1)译 - 当SaveChanges( ) 被调用时执行你的代码...

    第12章定制EF 在本章的小节里,定制实体对象和EF处理的一些功能.这些小节将涵盖很多"幕后"的事情,能让你的代码更加统一解决一些事情,比如用一个业务规则中心统一地为实体执行验证. ...

  9. Entity Framework 6 Recipes 2nd Edition(10-5)译 - 在存储模型中使用自定义函数

    10-5. 在存储模型中使用自定义函数 问题 想在模型中使用自定义函数,而不是存储过程. 解决方案 假设我们数据库里有成员(members)和他们已经发送的信息(messages) 关系数据表,如Fi ...

最新文章

  1. 洛谷 3519 bzoj 2213 Difference
  2. 转 graphviz工具及其原理
  3. 中山市交通集团热备容灾项目成功案例
  4. stm32官方例程在哪找_正点原子Linux第十一章模仿STM32驱动开发格式实验
  5. (十三)洞悉linux下的Netfilteramp;iptables:为防火墙增添功能模块【实战】
  6. 单自由度系统的振动的幅频特性曲线及相频特性曲线及matlab分析,实验四 线性系统的频域分析...
  7. 2021牛客暑期多校训练营1
  8. 26 SD配置-主数据-信用管理-指定信用控制区域及信用部分
  9. 华为云生态2020年政策FAQ(一)
  10. 二分查找与 bisect 模块
  11. Linux音频驱动-AOSC之Codec
  12. 使用Intellij Idea生成可执行文件jar,转为exe文件步骤
  13. java生成电子发票_Java电子发票管理系统
  14. 刀友访谈:哪有什么天生的设计大师,有的是长久的喜爱坚持
  15. linux.zip文件怎么解压,linux怎么解压zip文件
  16. linux批量修改文件编码格式(包含子目录)
  17. 声音存储空间计算公式_音频文件存储容量计算
  18. 为什么计算机专硕比学硕难考,专硕一定比不上学硕?“我们专硕”,就是鄙视链最底端的一群人”...
  19. layer.open(常用)
  20. 让我摘下星星送给你_去最美的地方,我摘下星星给你。

热门文章

  1. 刹车时到底要不要踩离合器?
  2. 过年最大的烦恼是什么?
  3. 流量少怎么办?先考虑下面四项
  4. 很多人在销售过程中,喜欢考虑用一些话术
  5. 中小卖家需要避开的三个坑
  6. SQL 中为什么经常要加NOLOCK?
  7. 4.4GDI基本图形
  8. 实时操作系统和优先级反转
  9. jdbc数据源连接oracle,请教JDBC怎么连接ORACLE数据库
  10. sql t-sql_增强的PolyBase SQL 2019-使用t-SQL的外部表