在XAF和dev项目中使用XPO的最佳准则
1、为每个持久化对象定义一个包含Session参数的构造函数。
public class OrderDetail : XPObject {
public OrderDetail(Session session) : base(session) {
}
...
}
实际上使用Dev的模板创建的XPO对象已经默认包含了这个构造函数。而使用中应该明确的使用该构造函数来初始化对象,即显式的传递进一个Session对象来初始化持久类。
2、在持久类的属性的Set方法里使用SetPropertyValue来替代一个普通的赋值。
static void Main()
{
string conn = AccessConnectionProvider.GetConnectionString(@"ApplicationData.mdb");
XpoDefault.DataLayer = XpoDefault.GetDataLayer(conn,AutoCreateOption.DatabaseAndSchema);
...
}
它的作用是通知UnitOfWork记录下发生了变动的对象,以便在CommitChanges时保存该对象。若不这么做,则UnitOfWork无法跟踪发生变动的对象,亦无法在最后调用CommitChanges时如预期般保存下这些对象了。Get方法则不需要,它只会无谓的增加访问属性时的时间开销。
3、在程序的入口点显式的为XpoDefault.DataLayer赋值。
static void Main()
{
string conn = AccessConnectionProvider.GetConnectionString(@"ApplicationData.mdb");
XpoDefault.DataLayer = XpoDefault.GetDataLayer(conn,AutoCreateOption.DatabaseAndSchema);
...
}
若不这么做,每个Session(UnitOfWork)对象都将自动创建一个新的专用的DataLayer,而这又将创建一个新的数据库连接。频繁的创建数据库连接将会对程序性能带来不利影响,并且可能会超出数据库允许的最大连接数。在为XpoDefault指定了DataLayer以后,则后续的Session(UnitOfWork)都将共享的使用这个DataLayer。(这样做以后我们依然可以在需要的时候创建新的DataLayer,并不会受到任何限制。)
protected void Application_Start(object sender, EventArgs e) {
string conn = DevExpress.Xpo.DB.MSSqlConnectionProvider.GetConnectionString("(local)","XpoWebTest");
DevExpress.Xpo.Metadata.XPDictionary dict = newDevExpress.Xpo.Metadata.ReflectionDictionary();
DevExpress.Xpo.DB.IDataStore store =DevExpress.Xpo.XpoDefault.GetConnectionProvider(conn,DevExpress.Xpo.DB.AutoCreateOption.SchemaAlreadyExists);
dict.GetDataStoreSchema(typeof(PersistentObjects.Customer).Assembly);
DevExpress.Xpo.XpoDefault.DataLayer = new DevExpress.Xpo.ThreadSafeDataLayer(dict,store);
DevExpress.Xpo.XpoDefault.Session = null;
}
对于ASP.NET项目,我们应该采用ThreadSafeDataLayer而非SimpleDataLayer(因为ASP.NET项目是多线程的)。
ThreadSafeDataLayer需要一个XPO字典类来保存所有的持久化对象的元数据。上述代码中的dict.GetDataStoreSchema(typeof(PersistentObjects.Customer).Assembly);就是这个作用。注意如果项目中的XPO对象不是集中在一个类库而是分散在多个类库的话,我们需要在这里从每一个类库中都任意抓取一个对象作为参数传递给GetDataStoreSchema方法。
举例来说,我们在CustomerObjects项目下有Customer, Address两个XPO类,又有一个OrderObjects项目下有Order, OrderItem两个XPO类,则GetDataStoreSchema方法应该写成:
GetDataStoreSchema(typeof(CustomerObjects.Customer).Assembly, typeof(OrderObjects.Order).Assembly);
4、建议在每个Form,用户控件中对持久类进行操作时都使用一个新的Session/UnitOfWork类。因为Session会对经它操手的对象进行一定的缓存管理(有关XPO的缓存机制日后会有另文详述)。采用新的独立的Session对象则可以方便我们自如的控制读写。例如由我们指定的废除缓存强制重新从数据库中读取最新的数据。这样可避免对其他数据产生影响。
5、避免使用XpoDefault.Session。
在创建XPO对象或者XPCollection等对象时若没有显式的为他们指定一个Session,则他们会自动调用默认的Session,即XpoDefault.Session,而这会导致一系列的问题。为避免发生这样的情况,建议在程序的入口点就将XpoDefault.Session设为null。并且删除所有XPO对象的默认构造函数(没有传递Session参数的),避免在写代码的时候无意中调用到。
但是在实践中,这样会产生一个问题,即如果我们在Form中使用了XpoDataSource和其他UI控件做绑定(这很常见并且很方便),XpoDataSource会因无法找到适用的Session而无法工作(在没有显式的为其指定Session对象的情况下,它默认使用XpoDefault.Session)。为解决这个问题,我们可以在Form的Page_Init方法中为它指定Session,如:
Session session;
protected void Page_Init(object sender, EventArgs e) {
session = new Session(XpoDefault.DataLayer);
XpoDataSource1.Session = session;
}
这里注意的是上述代码必须被放在Page_Init内,不能放在Page_Load内。 具体请参见ASP.NET页生命周期概述。
6、尽可能的使用UnitOfWork代替Session。
在使用Session时,默认情况下它并不会显式的启动一个事务。每当调用Save方法时所有的改动都将被实时的保存进数据库。
UnitOfWork是继承自Session的,即它本身就可以被当做Session使用。然而它可以比Session更好的控制“何时”保存“什么”内容。因为凡是使用该UnitOfWork的对象,在CommitChanges被调用前,所有的改动都由UnitOfWork在内存中管理,不会实时入库。在对多个对象进行较为复杂的修改时,UnitOfWork将比Session节省出很多资源。
当然,若我们使用Session时显式的调用BeginTransaction / CommitTransaction方法,则和UnitOfWork的效果一样了。
7、创建一个独立的项目进行数据库的维护和结构更新。
string conn = ...;
IDataLayer dl = XpoDefault.GetDataLayer(conn,DevExpress.Xpo.DB.AutoCreateOption.DatabaseAndSchema);
using(Session session = new Session(dl)) {
System.Reflection.Assembly[] assemblies = new System.Reflection.Assembly[] {
typeof(AnyPersistentObjectFromAssemblyA).Assembly,
typeof(AnyPersistentObjectFromAssemblyB).Assembly
};
session.UpdateSchema(assemblies);
session.CreateObjectTypeRecords(assemblies);
}
XPO可以在项目启动时对数据库进行检查并做出需要的改动。然而这需要较高的数据库帐户权限。在对安全性要求较为严格的项目中,我们可以创建一个独立的项目,专门用以更新数据库结构所用,为这个项目指定一个具有较高权限的账户。而主项目则可以分配给一个权限较低的账户了。
8、尽量不要在持久化对象内部包含业务逻辑,而只应该包含数据存取本身需要的逻辑。这些代码应该尽量被放在属性的Get和Set方法里。并且尽量避免在对象被读取/保存的时候进行复杂的操作或运算。
如果程序里需要对多个对象进行操作,则最好一次性读入这些对象,完成后再一次性写入(善用UnitOfWork)。然而不应该读取任何不需要的内容。
设计对象时应该尽量避免在访问单个对象/属性时可能会导致加载大量数据的结构。例如有一个Gender类,只保存男女,在访问其属性时则可能导致数据库读取整库一半的Person对象。如果确实无法避免这类结构,可以使用XPO的延迟加载特性(为属性加上Delayed Loading标签)。这样仅当关联数据确实被访问时XPO才会再去数据库中进行查询。但该特性依然不应该被滥用,因为它会导致额外的数据库访问开销。
[Persistent("Name")]
private string PersistentName {
get { return name; }
set { SetPropertyValue("PersistentName", ref name, value); }
}
[PersistentAlias("PersistentName")]
public virtual string Name {
get { return PersistentName; }
set {
DoMyBusinessTricksBeforePropertyAssignement();
PersistentName = value;
DoMyBusinessTricksAfterPropertyAssignement();
}
}
官网原文参考链接:https://supportcenter.devexpress.com/ticket/details/a2944/xpo-best-practices
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异