开发者论坛

 找回密码
 注册 (请使用非IE浏览器)
查看: 5220|回复: 0

在ASP.NET项目中使用XPO的最佳准则

[复制链接]

0

精华

14

贡献

28

赞扬

帖子
30
软币
273
在线时间
29 小时
注册时间
2013-7-30
发表于 2013-9-22 18:25:04 | 显示全部楼层 |阅读模式
学习XPO有一段时间了,也用它陆续做过几个项目。遇到过问题走过弯路,这里把DevExpress知识库的几篇文章的内容做了摘录和整理,并且加上了一些自己的注释,方便自己日后备忘也方便其他的朋友。

1. Always define a constructorwith a Session parameter in your persistent objects.
This will help you preparefor point 4 (see below) and will also allow you to avoid the exceptionexplained in theSessionCtorAbsentException - what does itmean? article.
[C#]
public class OrderDetail : XPObject {
    public OrderDetail(Session session) : base(session) {
    }
...
}
.


为每个持久化对象定义一个包含Session参数的构造函数。
实际上使用Dev的模板创建的XPO对象已经默认包含了这个构造函数。而使用中应该明确的使用该构造函数来初始化对象,即显式的传递进一个Session对象来初始化持久类。

2. Use the SetPropertyValuemethod in persistent property setters.
Here is the recommended code fora persistent property:
[C#]
string fProductName;
public string ProductName {
    get { return fProductName; }
    set { SetPropertyValue("ProductName", ref fProductName, value); }
}

Q. Why must I define a property ratherthan just a field (public string ProductName;)? Why should I use a SetPropertyValue rather than simply setting avalue to a class field?
A. XPO expects an Object Changednotification, when a persistent property's value is changed. For instance, thisnotification is used to include the modified object into the UnitOfWork'sObjects To Save collection. The SetPropertyValue method sends thisnotification by calling the object's OnChanged method, while a simplefield assignment does not.
Q. Why should I use the SetPropertyValueoverloaded method with a property name as the first parameter?
A. If a property name isn't specified,XPO still tries to determine a property name, which is being set, by analyzingthe call stack. When the property name is explicitly specified, theSetPropertyValue method is executed much faster!
Q. Why not to use a GetPropertyValue ina property's getter?
A. Code like return fProductName; is faster than a call to theGetPropertyValue method. When it comes to reading persistent properties, theaccess time is critical, because it's a frequent operation.
Please refer to the XPO Simplified property syntax article to learn more.

在持久类的属性的Set方法里使用SetPropertyValue来替代一个普通的赋值。它的作用是通知UnitOfWork记录下发生了变动的对象,以便在CommitChanges时保存该对象。若不这么做,则UnitOfWork无法跟踪发生变动的对象,亦无法在最后调用CommitChanges时如预期般保存下这些对象了。Get方法则不需要,它只会无谓的增加访问属性时的时间开销。

3. Explicitly set theXpoDefault.DataLayer property in the entry point of your application.
[C#]
[STAThread]
static void Main()
{
    string conn = AccessConnectionProvider.GetConnectionString(@"ApplicationData.mdb");
    XpoDefault.DataLayer = XpoDefault.GetDataLayer(conn,AutoCreateOption.DatabaseAndSchema);
...
}
Without this code, each new Session or UnitOfWork will createa new data layer for its exclusive use. The data layer creates a new connectionto your database. This is not advisable for at least for two reasons:
1.Establishing a connection to a database is a long lasting operation. It maycause performance issues.
2.Your application may exceed of the maximum DB connection limit.
Whenthe XpoDefault.DataLayer is set, all Session and UnitOfWork objects, which werecreated with their default constructors (without a DataLayer parameter), sharea given XpoDefault.DataLayer object.
Thiswill also help you get prepare for Best Practices #4 (see below).
Pleasenote that setting an XpoDefault.DataLayer doesn't prevent you from creating anew connection to your database when you need one (e.g. for emulating amulti-user application for testing purposes): You can create a newSimpleDataLayer instance, pass it to the Session's constructor as a parameterand use this Session as your needs dictate.

在程序的入口点显式的为XpoDefault.DataLayer赋值。
若不这么做,每个Session(UnitOfWork)对象都将自动创建一个新的专用的DataLayer,而这又将创建一个新的数据库连接。频繁的创建数据库连接将会对程序性能带来不利影响,并且可能会超出数据库允许的最大连接数。在为XpoDefault指定了DataLayer以后,则后续的Session(UnitOfWork)都将共享的使用这个DataLayer。(这样做以后我们依然可以在需要的时候创建新的DataLayer,并不会受到任何限制。)

Create a ThreadSafeDataLayerinstance in the entry point of your application and assign it to the staticXpoDefault.DataLayer property.
It's similar to Windows Forms development whereit's recommended to initialize XpoDefault.DataLayer in the Mainprocedure. The differences include:
A) ThreadSafeDataLayer is to be used in ASP.NET, not in aSimpleDataLayer.
B) The database schema must be up-to-date and containthe XPObjectType table. An XPO connection provider must be createdwith the SchemaAlreadyExists parameter.
C) ThreadSafeDataLayer requires the XPO dictionary (storespersistent objects' metadata) to be initialized at the moment whenThreadSafeDataLayer is created.
D) The Web application's entry point is the Application_Startprocedure in Global.asax.
To sum it up, here is a template for the Application_Startprocedure in your application:
[C#]
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. Use a new Session / UnitOfWorkinstance to fully control loading, modifying and saving data.
XPO Session caches objects. Bycreating a new Session/UnitOfWork instance for data processing, you acquirebetter control over reloading data and saving changes. We advise that youutilize a separate UnitOfWork instance in all visual modules (Forms and UserControls)of your application.
Please review the attached sampleproject. It demonstrates how to create a grid form for navigation and anon-modal form for editing XPO data.

建议在每个Form,用户控件后对持久类进行操作时都使用一个新的Session/UnitOfWork类。因为Session会对经它操手的对象进行一定的缓存管理(有关XPO的缓存机制日后会有另文详述)。采用新的独立的Session对象则可以方便我们自如的控制读写。例如由我们指定的废除缓存强制重新从数据库中读取最新的数据。这样可避免对其他数据产生影响。

5. Avoid the use of a defaultsession.
The XPO default Session isaccessible via the XpoDefault.Session or Session.Default staticproperty. The default session is used internally when objects or XPCollectioninstances are created without a Session parameter. This may result inaSessionMixingException and/or make you write cumbersome code forreloading data (see How the XPO cache works). To avoid these problems,please don't use the default session.
1. Set XpoDefault.Session to null(Nothing) in the entry point of your application:
[C#]
XpoDefault.Session = null;
2. Remove default constructorsfrom your persistent classes.

避免使用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. Use a UnitOfWork rather thanSession.
When a Session isused, and its transaction isn't explicitly started, a persistent object isimmediately persisted in the data store when its Save method is called. If the BeginTransaction / CommitTransaction methodsare explicitly called, then the Session behaves exactly as if it was aUnitOfWork. The Session class is maintained for backward compatibility with XPO1.x.
Unlike Session,UnitOfWork doesn't persist changes until its CommitChanges method is called.Thus, the UnitOfWork gives you more control over what and when to save.

尽可能的使用UnitOfWork代替Session。
在使用Session时,默认情况下它并不会显式的启动一个事务。每当调用Save方法时所有的改动都将被实时的保存进数据库。
UnitOfWork是继承自Session的,即它本身就可以被当做Session使用。然而它可以比Session更好的控制“何时”保存“什么”内容。因为凡是使用该UnitOfWork的对象,在CommitChanges被调用前,所有的改动都由UnitOfWork在内存中管理,不会实时入库。在对多个对象进行较为复杂的修改时,UnitOfWork将比Session节省出很多资源。
当然,若我们使用Session时显式的调用BeginTransaction /CommitTransaction方法,则和UnitOfWork的效果一样了。

7. Create a separate applicationfor database maintenance and schema updates.
For security reasons, you maywish to deny access to system tables and disable modifications to the databaseschema for the database account used in your end-user application. Please usethe AutoCreateOption.SchemaAlreadyExists option when creating a DataLayer inyour XPO application. In this case, you can grant fewer privileges to thedatabase user account used in your application.
To create a database schema,please write a separate application, which calls the UpdateSchema andCreateObjectTypeRecords methods:
[C#]
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可以在项目启动时对数据库进行检查并做出需要的改动。然而这需要较高的数据库帐户权限。在对安全性要求较为严格的项目中,我们可以创建一个独立的项目,专门用以更新数据库结构所用,为这个项目指定一个具有较高权限的账户。而主项目则可以分配给一个权限较低的账户了。

XPOBonus: Persistent objects etiquette for enterprise applications (Part I)
Here, Etiquette means a simple set of rules, which are notenforced, but if followed, will simplify your life significantly:
• Never useSession.DefaultSession for actual persistent objects.
•Never use an XPBaseObject if not really required. Use XPCustomObject orXPObject as base classes.
• Neverproduce side effects on a persistent property assignment. At the very least,don't change or check directly or indirectly other persistent propertiesbetween OnLoading and OnLoad (while IsLoading == true).
• If youneed to enforce your business rules in the properties setters or getters,always do it under:
[C#]
    if(!IsLoading) { ... YourBusinessRule... }
or use the following technique:
[C#]
[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();
    }
}
• It's a badidea to throw exceptions inside your persistent properties. At the very least,don't do it between OnLoading and OnLoaded (IsLoading == true).
• Don't useSave whenever possible. Use UnitOfWork whenever possible.
• Share asingle DataLayer between all your sessions within same AppDomain wheneverpossible. Assigning XpoDefault.DataLayer in the Main() function of your programis good style.
• Never doany complex work on persistent object .ctor(Session) and persistent propertiesassignments (at least between OnLoading and OnLoaded (until IsLoading == true))-- if you want Xpo to be effective, persistent objects must be created asquickly as possible from their Data Store images.
• If youralgorithm requires ten persistent objects -- load them all at once at thebeginning of the method
• Never loadmore objects then actually needed.
• Use Gridsand Lookups in ServerMode when appropriate
• Always useIDataStore for remoting scenarios.
• Don'texpose your business objects instances remotely, in scenarios where this ispossible. Use eXpress Persistent Objects, when available, if you absolutelyneed to transfer objects over the wire, but think twice: do you really needit?
• Never useXmlWebServices until all your clients have access to assemblies provided byyou. Expose IDataStore via WebService, and allow your clients to work withpersistent objects.
• If youneed to disallow your customers from changing something through remotableIDataStore -- create an IDataStore wrapper, which will throw anInvalidOperationException on each call to ModifyData.
• Use Domain Model whenever possible
• TheOnSaving method approaches are valuable in two ways:
  1. Custom keys generating
  2. Last-chance checking, if you adapt defensiveprogramming, like I do, (this rule does not violate the previous rule! Defensechecks must not be violated in the normal workflow -- if such a check fires,it's means something is bad with your program)
• Don't mix the process of objects persisting and business logic(including business rules validation) -- this is a codesmell of theTransaction Script pattern
•     Don'twork with the same persistent object instance from different threads. Create aseparate session for each thread, and work with different instances of the samepersistent object.
• Don'tcreate a class structure which will result in loading half of the database onaccessing a single object/property. For instance if you have a class Genderwith two instances, Gender("Male") and Gender("Female")it's a bad idea to create a collection of all persons of a specific sex in theGender class. If you need to do it for building criteria -- make the collectionproperty private or protected, and return null from it (undocumented andunsupported but a working and useful feature).
• If your class structure is highly coupled and it's actuallypossible to raise a complete database accessing the single object/property,break this net using delayed loading.
• Don't useDelayed loading if not really needed.

原文这部分内容比较杂和细,有一些在上文里已经说过了,其他的挑我认为比较常见的说了。
尽量不要在持久化对象内部包含业务逻辑,而只应该包含数据存取本身需要的逻辑。这些代码应该尽量被放在属性的Get和Set方法里。并且尽量避免在对象被读取/保存的时候进行复杂的操作或运算。
如果程序里需要对多个对象进行操作,则最好一次性读入这些对象,完成后再一次性写入(善用UnitOfWork)。然而不应该读取任何不需要的内容。
设计对象时应该尽量避免在访问单个对象/属性时可能会导致加载大量数据的结构。例如有一个Gender类,只保存男女,在访问其属性时则可能导致数据库读取整库一半的Person对象。如果确实无法避免这类结构,可以使用XPO的延迟加载特性(为属性加上DelayedLoading标签)。这样仅当关联数据确实被访问时XPO才会再去数据库中进行查询。但该特性依然不应该被滥用,因为它会导致额外的数据库访问开销。

评分

参与人数 2赞扬 +2 收起 理由
maple + 1 感谢分享
elysian + 1 Thanks

查看全部评分

回复

使用道具 举报

Archiver|手机版|小黑屋|开发者网 ( 苏ICP备08004430号-2 )
版权所有:南京韵文教育信息咨询有限公司

GMT+8, 2024-4-28 00:15

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表