缓存的意义已经无需多言了。这里整理了一篇DevExpress的关于XPO的Session管理和缓存的文章:Session Management and Caching。
About Sessions
从6.1版开始,XPO新增了一个单独的Data Layer层,居于Session和IData Store之间,它的作用是接管之前版本中由Session管理的所有持久类的元数据。这样创建Session的资源开销被大大的减少,常常需要频繁创建新的Session实例的项目成为可能。
The Session as an Identity Map
Session包含了一个object cache,用以实现Identity Map。Identity Map在Martin Fowler的 "Patterns of Enterprise Application Architecture" 一书中有很好的解释(Summary)。
简单说来,它的任务是确保当相同的对象分别位于两个独立的查询的结果集里时,返回的应该是完全相同的同一个对象--不是任何的副本。即如下代码是应该可以通过的。
Person person1 = session.FindObject<Person>(new BinaryOperator("Name", "Billy Bott"));
Person person2 = session.FindObject<Person>(new BinaryOperator("Name", "Billy Bott"));
Assert.AreSame(person1, person2);
Person person2 = session.FindObject<Person>(new BinaryOperator("Name", "Billy Bott"));
Assert.AreSame(person1, person2);
注意:Session的object cache不是一个可选设置,不能被关闭。
Object cache的内部原理非常简单,每次有至数据库的查询时,XPO会检查相关对象的版本(使用Optimistic Locking字段)。若数据库中有更新版本的记录,则相关对象的数据会被更新至缓存并返回。否则即直接返回缓存中的数据而跳过重新创建相关对象等操作。
How current is the object cache?
“如何确保缓存中的数据足够新?”问题很简单,但是“足够新”在不同的项目中的含义是不同的。XPO提供如下的办法来处理这个问题:
- 创建新的Session来创建新的object cache。Session的生命周期越短,则其中的object cache中的内容也越新。并且这是通常推荐使用的唯一不会破坏缓存一致性(object cache consistency)的方法。
- 使用Optimistic Locking。XPO会自动处理。如下述代码所演示的:
// Create a test object
using (Session session = new Session( )) {
new Person(session, "Billy Bott").Save( );
}
Session session1 = new Session( );
Session session2 = new Session( );
// Create a collection in session1 and check the content
XPCollection<Person> peopleSession1 = new XPCollection<Person>(session1);
Assert.AreEqual(1, peopleSession1.Count);
Assert.AreEqual("Billy Bott", peopleSession1[0].Name);
// Fetch the test object in session2 and make a change
Person billySession2 = session2.FindObject<Person>(new BinaryOperator("Name", "Billy Bott"));
billySession2.Name = "Billy's new name";
billySession2.Save( );
// Create a new collection in session1 - it will have the change from the other session
XPCollection<Person> newPeopleSession1 = new XPCollection<Person>(session1);
Assert.AreEqual(1, newPeopleSession1.Count);
Assert.AreEqual("Billy's new name", newPeopleSession1[0].Name);
// This last test shows the workings of the Identity Map - the object in the "old"
// collection has the same change, because it's actually the same object.
Assert.AreEqual("Billy's new name", peopleSession1[0].Name);
Assert.AreSame(peopleSession1[0], newPeopleSession1[0]);
using (Session session = new Session( )) {
new Person(session, "Billy Bott").Save( );
}
Session session1 = new Session( );
Session session2 = new Session( );
// Create a collection in session1 and check the content
XPCollection<Person> peopleSession1 = new XPCollection<Person>(session1);
Assert.AreEqual(1, peopleSession1.Count);
Assert.AreEqual("Billy Bott", peopleSession1[0].Name);
// Fetch the test object in session2 and make a change
Person billySession2 = session2.FindObject<Person>(new BinaryOperator("Name", "Billy Bott"));
billySession2.Name = "Billy's new name";
billySession2.Save( );
// Create a new collection in session1 - it will have the change from the other session
XPCollection<Person> newPeopleSession1 = new XPCollection<Person>(session1);
Assert.AreEqual(1, newPeopleSession1.Count);
Assert.AreEqual("Billy's new name", newPeopleSession1[0].Name);
// This last test shows the workings of the Identity Map - the object in the "old"
// collection has the same change, because it's actually the same object.
Assert.AreEqual("Billy's new name", peopleSession1[0].Name);
Assert.AreSame(peopleSession1[0], newPeopleSession1[0]);