《Entity Framework 6 Recipes》中文翻译系列 (45) ------ 第八章 POCO之获取原始对象与手工同步对象图和变化跟踪器
翻译的初衷以及为什么选择《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 = ;
using (var context = new EFRecipesEntities())
{
var item = new Item
{
Name = "Xcel Camping Tent",
UnitPrice = 99.95M
};
context.Items.Add(item);
context.SaveChanges(); //为下一步保存itemId
itemId = 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()方法
class Program
{
static void Main(string[] args)
{
RunExample();
} static void RunExample()
{
using (var context = new EFRecipesEntities())
{
context.Configuration.AutoDetectChangesEnabled = false;
var speaker1 = new Speaker { Name = "Karen Stanfield" };
var talk1 = new Talk { Title = "Simulated Annealing in C#" };
speaker1.Talks = new List<Talk> { talk1 }; //关联尚未完成
Console.WriteLine("talk1.Speaker is null: {0}",
talk1.Speakers == null); context.Speakers.Add(speaker1); //现在关联已经修正
Console.WriteLine("talk1.Speaker is null: {0}",
talk1.Speakers == null);
Console.WriteLine("Number of added entries tracked: {0}",
context.ChangeTracker.Entries().Where(e => e.State == System.Data.Entity.EntityState.Added).Count());
context.SaveChanges();
//修改talk的标题
talk1.Title = "AI with C# in 3 Easy Steps";
Console.WriteLine("talk1's state is: {0}",
context.Entry(talk1).State);
context.ChangeTracker.DetectChanges();
Console.WriteLine("talk1's state is: {0}",
context.Entry(talk1).State);
context.SaveChanges();
} using (var context = new EFRecipesEntities())
{
foreach (var speaker in context.Speakers.Include("Talks"))
{
Console.WriteLine("Speaker: {0}", speaker.Name);
foreach (var talk in speaker.Talks)
{
Console.WriteLine("\tTalk Title: {0}", talk.Title);
}
}
}
}
}
public partial class Speaker
{
public int SpeakerId { get; set; }
public string Name { get; set; }
public ICollection<Talk> Talks { get; set; }
}
public partial class Talk
{
public int TalkId { get; set; }
public string Title { get; set; }
public System.DateTime CreateDate { get; set; }
public System.DateTime RevisedDate { get; set; }
public ICollection<Speaker> Speakers { get; set; }
}
public partial class EFRecipesEntities : DbContext
{
public EFRecipesEntities()
: base("name=EFRecipesEntities")
{
} protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
} public DbSet<Speaker> Speakers { get; set; }
public DbSet<Talk> Talks { get; set; } public override int SaveChanges()
{
var changeSet = this.ChangeTracker.Entries().Where(e => e.Entity is Talk);
if (changeSet != null)
{
foreach (var entry in changeSet.Where(c => c.State == System.Data.Entity.EntityState.Added).Select(a => a.Entity as Talk))
{
entry.CreateDate = DateTime.UtcNow;
entry.RevisedDate = DateTime.UtcNow;
}
foreach (var entry in changeSet.Where(c => c.State == System.Data.Entity.EntityState.Modified).Select(a => a.Entity as Talk))
{
entry.RevisedDate = DateTime.UtcNow;
}
}
return base.SaveChanges();
}
}
代码清单8-10的输出如下:
talk1.Speaker is null: True
talk1.Speaker is null: False
Number of added entries tracked:
talk1's state is: Unchanged
talk1's state is: Modified
Speaker: Karen Stanfield
Talk Title: AI with C# in 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/
《Entity Framework 6 Recipes》中文翻译系列 (45) ------ 第八章 POCO之获取原始对象与手工同步对象图和变化跟踪器的更多相关文章
- 《Entity Framework 6 Recipes》中文翻译系列 (42) ------ 第八章 POCO之使用POCO
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第八章 POCO 对象不应该知道如何保存它们,加载它们或者过滤它们.这是软件开发中熟 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (43) ------ 第八章 POCO之使用POCO加载实体
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-2 使用POCO加载关联实体 问题 你想使用POCO预先加载关联实体. 解决方 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (44) ------ 第八章 POCO之POCO中使用值对象和对象变更通知
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-4 POCO中使用值对象(Complex Type--也叫复合类型)属性 问题 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (46) ------ 第八章 POCO之领域对象测试和仓储测试
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-8 测试领域对象 问题 你想为领域对象创建单元测试. 这主要用于,测试特定的数 ...
- 《Entity Framework 6 Recipes》翻译系列 (1) -----第一章 开始使用实体框架之历史和框架简述
微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF ...
- 《Entity Framework 6 Recipes》翻译系列(2) -----第一章 开始使用实体框架之使用介绍
Visual Studio 我们在Windows平台上开发应用程序使用的工具主要是Visual Studio.这个集成开发环境已经演化了很多年,从一个简单的C++编辑器和编译器到一个高度集成.支持软件 ...
- 《Entity Framework 6 Recipes》翻译系列 (4) -----第二章 实体数据建模基础之从已存在的数据库创建模型
不知道对EF感兴趣的并不多,还是我翻译有问题(如果是,恳请你指正),通过前几篇的反馈,阅读这个系列的人不多.不要这事到最后成了吃不讨好的事就麻烦了,废话就到这里,直奔主题. 2-2 从已存在的数据库创 ...
- 《Entity Framework 6 Recipes》翻译系列 (3) -----第二章 实体数据建模基础之创建一个简单的模型
第二章 实体数据建模基础 很有可能,你才开始探索实体框架,你可能会问“我们怎么开始?”,如果你真是这样的话,那么本章就是一个很好的开始.如果不是,你已经建模,并在实体分裂和继承方面感觉良好,那么你可以 ...
- 《Entity Framework 6 Recipes》翻译系列 (5) -----第二章 实体数据建模基础之有载荷和无载荷的多对多关系建模
2-3 无载荷(with NO Payload)的多对多关系建模 问题 在数据库中,存在通过一张链接表来关联两张表的情况.链接表仅包含连接两张表形成多对多关系的外键,你需要把这两张多对多关系的表导入到 ...
随机推荐
- swift 命令
http://blog.chinaunix.net/uid-15063109-id-5144658.html http://www.cnblogs.com/fczjuever/p/3224022.ht ...
- arm工作模式笔记
linux用户态程序即应用程序,在user模式 linux内核运行在svc模式 arm七个模式: usr用户模式 fiq快速中断模式 irq普通中断模式 supervior svc模式 abort ...
- 《UML大战需求分析》阅读随笔(四)
状态机图(State Machine Diagram),状态机图是通过描述某事物状态的改变来展现流程的.一般适用于流程围绕某个事物展开,例如请假的流程就围绕请假条的展开.语法,开始于结束符号,实心圆表 ...
- java调用sqlldr oracle 安装的bin目录
package com.jyc.sqlldr; import java.io.BufferedReader;import java.io.InputStream;import java.io.Inpu ...
- 递推+高精度 UVA 10497 Sweet Child Makes Trouble(可爱的孩子惹麻烦)
题目链接 题意: n个物品全部乱序排列(都不在原来的位置)的方案数. 思路: dp[i]表示i个物品都乱序排序的方案数,所以状态转移方程.考虑i-1个物品乱序,放入第i个物品一定要和i-1个的其中一个 ...
- 疑难问题解决备忘录(3)——ubuntu12.04配置vsftp本地用户登录
vsftpd.conf相关项设置 local_enabled = YES write_enable=YES pam_service_name=ftp pam_service_name按默认的vsftp ...
- Java throws Exception、try、catch
throws Exception是方法后面接的 意思是向上级抛出异常 try{}里面的异常会被外面的catch捕捉到 抛出异常是throw new Exception("异常"); ...
- Junit测试框架 Tips
关于Junit测试框架使用的几点总结: 1.Junit中的测试注解: @Test →每个测试方法前都需要添加该注解,这样才能使你的测试方法交给Junit去执行. @Before →在每个测试方法执行前 ...
- asp.net中用cookie记住密码上次不用登陆
------最佳解决方案--------------------写入CookieResponse.Cookies["UserName"].Value = "用户名&quo ...
- jQuery中prop() , attr() ,css() 的区别
1. HTML属性是指页面标记中放在引号中的值,而DOM属性则是指通过JavaScript能够存取的值. (1)在jQuery中,prop()是操作DOM属性,attr()是操作HTML属性. HT ...