之前整理了一下XPO在Session管理和缓存方面的一些资料(XPO:Session管理与缓存--机制篇),但原文的例程还是有些含糊的地方,这两天抽空做了一下测试。若有不当或者不对的地方敬请不吝赐教。
XPO初始化的代码就不重复贴了,这里只贴上主要的代码。
测试中构建了2个简单的类,XpoUser和XpoOrder,一对多的关系。

using DevExpress.Xpo;
namespace Model
{
public class XpoUser : XPObject
{
public XpoUser()
: base()
{
// This constructor is used when an object is loaded from a persistent storage.
// Do not place any code here.
}
public XpoUser(Session session)
: base(session)
{
// This constructor is used when an object is loaded from a persistent storage.
// Do not place any code here.
}
public override void AfterConstruction()
{
base.AfterConstruction();
// Place here your initialization code.
}
private string _Name;
public string Name
{
get
{
return _Name;
}
set
{
SetPropertyValue("Name", ref _Name, value);
}
}
[Association("XpoUser-Orders")]
public XPCollection<XpoOrder> Orders
{
get
{
return GetCollection<XpoOrder>("Orders");
}
}
}
}

using DevExpress.Xpo;
namespace Model
{
public class XpoOrder : XPObject
{
public XpoOrder()
: base()
{
// This constructor is used when an object is loaded from a persistent storage.
// Do not place any code here.
}
public XpoOrder(Session session)
: base(session)
{
// This constructor is used when an object is loaded from a persistent storage.
// Do not place any code here.
}
public override void AfterConstruction()
{
base.AfterConstruction();
// Place here your initialization code.
}
private string _OrderID;
public string OrderID
{
get
{
return _OrderID;
}
set
{
SetPropertyValue("OrderID", ref _OrderID, value);
}
}
private XpoUser _User;
[Association("XpoUser-Orders")]
public XpoUser User
{
get
{
return _User;
}
set
{
SetPropertyValue("User", ref _User, value);
}
}
}
}
做了两个测试,测试一的代码:

StringBuilder sb = new StringBuilder();
Session s1 = new Session();
Session s2 = new Session();
XpoUser user1 = new XpoUser(s1) { Name = "UserName" };
user1.Save();
XpoUser user2 = s2.FindObject<XpoUser>(new BinaryOperator("Name", "UserName"));
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(Environment.NewLine + "Change user's name in Session 1");
user1.Name = "New UserName";
user1.Save();
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(Environment.NewLine + "Create / Read something else(XpoRole) in Session 2");
XpoRole role = new XpoRole(s2) { Name = "RoleName" };
role.Save();
role = s2.FindObject<XpoRole>(new BinaryOperator("Name", "RoleName"));
sb.AppendLine(string.Format("Role name : {0}", role.Name));
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(Environment.NewLine + "Create / Read another XpoUser in Session 2");
XpoUser anotherUser = new XpoUser(s2) { Name = "Another User" };
anotherUser.Save();
anotherUser = s2.FindObject<XpoUser>(new BinaryOperator("Name", "Another User"));
sb.AppendLine(string.Format("Another user's name : {0}", anotherUser.Name));
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(Environment.NewLine + "Try to Save the user in Session 2, which is modified in Session 1");
try
{
user2.Save();
}
catch (Exception ex)
{
sb.AppendLine(ex.Message);
}
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(Environment.NewLine + "Session.Reload(User) in Session 2");
s2.Reload(user2);
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
txtResult.Text = sb.ToString();
输出的结果:
[Session 1] User's Name : UserName
[Session 2] User's Name : UserName
Change user's name in Session 1
[Session 1] User's Name : New UserName
[Session 2] User's Name : UserName
Create / Read something else(XpoRole) in Session 2
Role name : RoleName
[Session 1] User's Name : New UserName
[Session 2] User's Name : UserName
Create / Read another XpoUser in Session 2
Another user's name : Another User
[Session 1] User's Name : New UserName
[Session 2] User's Name : UserName
Try to Save the user in Session 2, which is modified in Session 1
Cannot persist the object. It was modified or deleted (purged) by another application.
[Session 1] User's Name : New UserName
[Session 2] User's Name : UserName
Session.Reload(User) in Session 2
[Session 1] User's Name : New UserName
[Session 2] User's Name : New UserName
可以看到:
2个不同Session里Load同一个object,确实是同一个object被传递给了2个Session,而并非该Object的任何副本。
在一个Session中对该Object做了修改后,其他Session除非Reload该对象,否则将永远无法获得其他Session对其做的改动。
同时,试图保存这个已经被其他Session所更改过的对象时会抛错。
测试2:

StringBuilder sb = new StringBuilder();
Session s1 = new Session();
Session s2 = new Session();
XpoUser user1 = new XpoUser(s1) { Name = "UserName" };
XpoOrder order = new XpoOrder(s1) { OrderID = "#1", User = user1 };
user1.Orders.Add(order);
user1.Save();
XpoUser user2 = s2.FindObject<XpoUser>(new BinaryOperator("Name", "UserName"));
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 1] User's Order : {0}", GetOrders(user1)));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(string.Format("[Session 2] User's Order : {0}", GetOrders(user2)));
sb.AppendLine(Environment.NewLine + "Create a new order , change user's name and change Order1's ID to #3 in Session 1");
order = s1.FindObject<XpoOrder>(new BinaryOperator("OrderID", "#1"));
order.OrderID = "#3";
order.Save();
order = new XpoOrder(s1) { OrderID = "#2", User = user1 };
order.Save();
user1.Name = "New UserName";
user1.Orders.Add(order);
user1.Save();
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 1] User's Order : {0}", GetOrders(user1)));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(string.Format("[Session 2] User's Order : {0}", GetOrders(user2)));
sb.AppendLine(Environment.NewLine + "Session.Reload(User) in Session 2");
s2.Reload(user2);
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 1] User's Order : {0}", GetOrders(user1)));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(string.Format("[Session 2] User's Order : {0}", GetOrders(user2)));
sb.AppendLine(Environment.NewLine + "Session.Reload(User, forceAggregatesReload) in Session 2");
s2.Reload(user2, true);
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 1] User's Order : {0}", GetOrders(user1)));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(string.Format("[Session 2] User's Order : {0}", GetOrders(user2)));
sb.AppendLine(Environment.NewLine + "Load Order again in Session 2");
order = s2.FindObject<XpoOrder>(new BinaryOperator("OrderID", "#3"));
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 1] User's Order : {0}", GetOrders(user1)));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(string.Format("[Session 2] User's Order : {0}", GetOrders(user2)));
sb.AppendLine(Environment.NewLine + "Session.Reload(Order, forceAggregatesReload) in Session 2");
s2.Reload(order, true);
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 1] User's Order : {0}", GetOrders(user1)));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(string.Format("[Session 2] User's Order : {0}", GetOrders(user2)));
sb.AppendLine(Environment.NewLine + "Load Order2 in Session 2");
order = s2.FindObject<XpoOrder>(new BinaryOperator("OrderID", "#2"));
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 1] User's Order : {0}", GetOrders(user1)));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(string.Format("[Session 2] User's Order : {0}", GetOrders(user2)));
sb.AppendLine(Environment.NewLine + "Session.Reload(Order2, forceAggregatesReload) in Session 2");
s2.Reload(order, true);
sb.AppendLine(string.Format("[Session 1] User's Name : {0}", user1.Name));
sb.AppendLine(string.Format("[Session 1] User's Order : {0}", GetOrders(user1)));
sb.AppendLine(string.Format("[Session 2] User's Name : {0}", user2.Name));
sb.AppendLine(string.Format("[Session 2] User's Order : {0}", GetOrders(user2)));
txtResult.Text = sb.ToString();
上面代码其实测试了几种不同的情况,分别注释掉后部几个代码块单独跑可以看得更清楚,这里只贴全部跑的结果:
[Session 1] User's Name : UserName
[Session 1] User's Order : #1
[Session 2] User's Name : UserName
[Session 2] User's Order : #1
Create a new order , change user's name and change Order1's ID to #3 in Session 1
[Session 1] User's Name : New UserName
[Session 1] User's Order : #3 #2
[Session 2] User's Name : UserName
[Session 2] User's Order : #1
Session.Reload(User) in Session 2
[Session 1] User's Name : New UserName
[Session 1] User's Order : #3 #2
[Session 2] User's Name : New UserName
[Session 2] User's Order : #1
Session.Reload(User, forceAggregatesReload) in Session 2
[Session 1] User's Name : New UserName
[Session 1] User's Order : #3 #2
[Session 2] User's Name : New UserName
[Session 2] User's Order : #1
Load Order again in Session 2
[Session 1] User's Name : New UserName
[Session 1] User's Order : #3 #2
[Session 2] User's Name : New UserName
[Session 2] User's Order : #3
Session.Reload(Order, forceAggregatesReload) in Session 2
[Session 1] User's Name : New UserName
[Session 1] User's Order : #3 #2
[Session 2] User's Name : New UserName
[Session 2] User's Order : #3
Load Order2 in Session 2
[Session 1] User's Name : New UserName
[Session 1] User's Order : #3 #2
[Session 2] User's Name : New UserName
[Session 2] User's Order : #3 #2
Session.Reload(Order2, forceAggregatesReload) in Session 2
[Session 1] User's Name : New UserName
[Session 1] User's Order : #3 #2
[Session 2] User's Name : New UserName
[Session 2] User's Order : #3 #2
在这个例子里,刷新User没有获得Order的变更,刷新Order也没有获得User的变更,更没有获得同一个User下其他Order的变更。
对于有一对多关系的结构来说,无论是刷新哪个对象,用不用forceAggregatesReload,都只会刷新到自己,不会对关联对象做出更新。
是bug还是对forceAggregatesReload的理解有误?
另一个问题是,在Session下并没有找到DropCache()方法,只有一个DropIdentityMap()方法,而调用这个方法以后,再Reload其他对象会抛错(对象已被Dispose)。在DevExpress的官网上大概搜了一下,也没找到什么说法。
从上面的测试结果来看,我很同意DevExpress建议的,对每一组互相关联的少量数据都使用一个单独的Session,“大方”的使用。在保持数据之间的逻辑关系不会被破坏的情况下,颗粒度越小越好。 单一Session里包含的数据越少,则这些数据的时间跨度就越小,而且一般说来也越快会被更新。如果想彻底抛弃一个Session的缓存重新加载的话,最好就直接new一个Session来做,而不要去Drop什么的了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述