在上一个POCO贴子里,我提到了跟踪POCO变动的两种可行性:基于快照的变动跟踪(Snapshot based Change Tracking) 和 使用代理的基于通知的变动跟踪(Notification based Change Tracking with Proxies)。 在这个贴子里,我将对这两个选项做进一步的讨论,讨论它们的优缺点,以及使用它们的含意(implications)。我们还将在《实体框架设计博客》上贴出针对至今为止受到的关于Entity Framework 4.0中POCO支持的一些反馈的想法。
基于快照的变动跟踪(不用代理的纯POCO类)
就象我在这个系列的第二部分中提到的,基于快照(Snapshot)的变动跟踪,就是纯粹的POCO实体的做法,不使用代理来处理变动跟踪。这是个简明的变动跟踪方案,依赖于实体框架所维护的之前和之后值的完整快照,在SaveChanges中对这些值进行比较,决定到底哪些值与初始值有所不同。在这个模型中,除非你用了懒式装载,你的实体的运行时类型跟你定义的POCO实体类型是一模一样的。
这个做法没什么问题,如果你在乎你的实体类型的运行时纯粹性(不使用代理),完全可以依赖这个方案,走纯粹的POCO实体之路(不依赖于代理类型实现额外的功能)。
基于快照的变动跟踪唯一潜在的问题是,有几件事情你需要注意,因为在你的对象变动时没有向实体框架做直接变动通知,实体框架的对象状态管理器将与你的对象图不再同步。
让我们来看一个展示这个问题的例子:
1 Customer customer = (from c in context.Customers 2 where c.CustomerID == "ALFKI" 3 select c).Single(); 4 5 ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(customer); 6 7 Console.WriteLine("Customer object state: {0}", ose.State); // Unchanged 8 9 customer.Country = "UK"; 10 11 Console.WriteLine("Customer object state: {0}", ose.State); // Still Unchanged
在这个例子中,Customer是个纯POCO类型,跟基于EntityObject或IPOCO的实体不一样,对该实体做变动并不自动与状态管理器保持同步,因为在你的纯POCO实体与实体框架间没有自动的通知机制。所以,在查询状态管理器时,它会认为你的客户对象的状态是Unchanged(未被改动),尽管我们显式地对该实体的一个属性做了变动。
如果你调用SaveChanges而不选择acceptChangesDuringSave(在保存后接受变动之选项)的话,你会看到在保存后,其状态变成了Modified(改动过了)。这是因为在保存时,基于快照的变动跟踪机制开始起作用,检测到了变动。当然,默认的SaveChanges调用会将状态变回到 Unchanged ,因为默认的Save行为是在保存后接受变动。
在后面一点,我们将进一步讨论在你需要让你的对象,对象图以及状态管理器间保持同步时该怎么做,但让我们先来看一下可为你所用的POCO的另一类变动跟踪机制。
使用代理的基于通知的变动跟踪
如果你在乎在你对实体值,关系和对象图做变动时的非常高效的和即时性的变动跟踪的话,这是个另样的方案,基于代理的变动跟踪。如果你把某个特定实体类型上的所有映射属性都声明为virtual的话,你就可以利用基于代理的变动跟踪了。
使用代理来跟踪变动的实体总是与实体框架的对象状态管理器保持同步,因为代理会在实体的值和关系变动时通知实体框架。总的来说,这使得变动跟踪更为有效,因为对象状态管理器可以略去比较属性的原始值和当前值这一步,如果它知道属性没有变动的话。
实际上,你从代理上得到的变动跟踪行为跟从基于EntityObject的非POCO实体或IPOCO实体上得到的变动跟踪行为是完全一样的。
让我们用同一个例子来看一下:
1 Customer customer = (from c in context.Customers 2 where c.CustomerID == "ALFKI" 3 select c).Single(); 4 5 ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(customer); 6 7 Console.WriteLine("Customer object state: {0}", ose.State); // Unchanged 8 9 customer.Country = "UK"; 10 11 Console.WriteLine("Customer object state: {0}", ose.State); // Modified
这个例子是不言自明的,在你对实体做变动时,对象状态管理器被通知到你的变动了。在调用SaveChanges时,不会导致另外的花销(overhead)。
但是,基于代理的变动跟踪也意味着你的实体的运行时类型跟你定义的类型不完全一样,而是你的类型的子类。这在许多场景下(譬如序列化)也许不太合适,你需要选择在你的应用和领域的需求和约束下最合适的方法。
不使用代理,与状态管理器保持同步
两种方法都有其优缺点,但不是基于代理的纯POCO方案,其之简明和优雅也意味着它会成为你们中许多人的默认选择,也就毫不奇怪了。所以让我们来看一下在这种模式下,如何使状态管理器与你的对象图保持同步。
ObjectContext.DetectChanges()
在你使用基于快照的纯POCO实体时,有一个至关重要的方法:ObjectContext.DetectChanges()。
这个API在你无论何时改变对象图时都应该显式调用,它会告知状态管理器它需要与的你对象图做同步。
ObjectContext.SaveChanges在默认情形下会隐式调用DetectChanges,所以,如果你所做的就是对你的对象们做一系列的变动,然后立刻调用Save的话,你不必显式调用DetectChanges。但是,要记住的是,取决于你的对象图的大小,DetectChanges也许会花销很大(而且,取决于你在做什么,DetectChanges也许是多余的),所以,有可能你可以略去SaveChanges中隐式调用的DetectChanges,这一点,在我们讨论Entity Framework 4.0中新引进的SaveChanges的重载方法时会做更多的讨论。
让我们来看一下DetectChanges如何影响我们前面看过的例子:
1 Customer customer = (from c in context.Customers 2 where c.CustomerID == "ALFKI" 3 select c).Single(); 4 5 ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(customer); 6 7 Console.WriteLine("Customer object state: {0}", ose.State); // Unchanged 8 9 customer.Country = "UK"; 10 Console.WriteLine("Customer object state: {0}", ose.State); // Still Unchanged 11 12 context.DetectChanges(); 13 Console.WriteLine("Customer object state: {0}", ose.State); // Modified
显式调用DetectChanges 会导致状态管理器与你的对象的状态保持一致。
因为DetectChanges的潜在花销,依赖于状态管理器与对象图保持一致的ObjectContext上的其他一些APIs是不显式调用它的。因此,无论什么时候你在context上做依赖于状态的操作时,你需要调用DetectChanges。
Object Servces(对象服务)中的下列API的行为取决于ObjectStateManager的当前状态,因此,如果它们操作的部分对象图的情况与对象图的实际状态不同步的话,会受影响:
ObjectContext API:
- AddObject
- Attach
- AttachTo
- DeleteObject
- Detach
- GetObjectByKey
- TryGetObjectByKey
- ApplyCurrentValues
- ApplyPropertyChanges
- ApplyOriginalValues
- Refresh
- ExecuteQuery
ObjectStateManager API:
- ChangeObjectState
- ChangeRelationshipState
- GetObjectStateEntry
- TryGetObjectStateEntry
- GetObjectStateEntries
ObjectStateEntry API:
- Any
EntityCollection/EntityReference API:
- Any
SaveChanges的新重载方法
在Entity Framework 3.5中, 在调用ObjectContext.SaveChanges()时,有两种可能:
- SaveChanges() – 无参数选项,允许你保存变动,同时隐式接受变动。
- SaveChanges(bool acceptChangesDuringSave) – 这个选项允许你跳出默认的“accept changes during save”(保存时同时接受变动)行为,你需要在保存后显式地使用AcceptAllChanges来接受变动。
在加了DetectChanges行为后,再加另外的重载方法很明显会搞乱API。为了解决这个问题,引进了SaveOptions这个列举标记:
1 [Flags] 2 public enum SaveOptions 3 { 4 None = 0, 5 DetectChangesBeforeSave = 1, 6 AcceptChangesAfterSave = 2, 7 }
SaveChanges现在包括了象这样一个重载:
1 public virtual int SaveChanges(SaveOptions options);
SaveOptions将允许我们(Entity Framework开发团队)以可控制得多的方式来扩展API,假如我们需要在将来添加另外的选项的话。
但这个重载的有趣性还有另外一个原因:注意,这个SaveChanges重载是virtual。在Entity Framework 4.0中,你可以用你自己的定制行为,作为你选择编写的ObjectContext的继承类的一部分,来覆盖SaveChanges行为。无论你是否使用Entity Framework中的POCO支持,这将允许你以一种Entity Framework 3.5中不可能的方式来做定制。
至此,我想要重点阐述的Entity Framework 4.0中有关POCO的事情几乎都说完了,请接着期待下一篇在Entity Framework中使用模式的贴子。
Faisal Mohamood Entity Framework开发团队的Program Manager