5.抽象的数据访问层(Abstract DAL)
1) 先说一下几个接口(interface)
Eastasp.Enterprise.Objects.IObjectStoreProvider 此接口实现对象的Create, Update 和Delete操作。
Eastasp.Enterprise.Objects.IObjectListProvider 此接口实现对象的Retrieve操作,在Snake.Net中,每个对象都需要一个唯一标识,根据这个唯一标识不但可以获取单个的对象实例,也可以通过IObjectListProvider接口获取一组对象集合的实例。
Eastasp.Enterprise.Objects.IObjectQueryProvider 此接口实现对象的查询功能。
Eastasp.Enterprise.Transactions.ISupportedTransaction此接口实现对象的事物操作。
Snake.Net提供了一个高度封装的数据访问层类DataBindObjectDataAccess。DataBindObjectDataAccess实现了上述的所有接口,它是ORM中业务实体对象访问数据库的桥梁。虽然,在一般情况下,我们并不需要接触这个类,但是对这个类还是应该简单了解一下,这将有助于对Snake.Net更深层次的了解。
DataBindObjectDataAccess对象是提供了实现了业务实体的的所有数据库访问操作包括创建,更新,删除,获取和查询的能力。
2)CRUD方法(即Create, Retrieve, Update, Delete方法)
曾几何时,程序员每天都将要面对一些简单又繁杂的工作,不停的编写Insert , Update , Delete和Select之类的SQL语句。现在ORM把我们从这种简单重复劳动中解放出来。通过ORM我们可以不再理会那些陈年的SQL语句,只需把精力关注于业务实体的建模中。现在让我们欣赏一下让我们看一下Snake.Net是如何实现CRUD操作的, 再以Customer实体为例,请看以下代码:
//declare
Customer customer;
IObjectProviderFactory factory;
//Create
customer = new Customer("AROWT");
customer.CompanyName = "Eastasp.com";
customer.ContactName = "Bruce";
customer.ContactTitle = "owner";
customer.Address = "Avda. de la Constituci¨®n 2222";
customer.City = "Tsawassen";
customer.Region = "BC";
customer.PostalCode = "S-958 22";
customer.Country = "UK
";
customer.Phone = "0621-08460";
customer.Fax = "0621-08924";
customer.Save();
//get provider factory
factory = DataBindObject.ProviderFactory;
try{
//Retrieve
customer = (Customer)factory.GetListProvider(typeof(Customer)).Get(new StringToken("AROWT"));
}
catch(ObjectNotFoundException ex){
Console.Write("Cann't found object.");
throw ex;
}
//Update
customer.Phone = "0321-02420";
customer.Save();
//Delete
customer.Delete();
从上面的代码中我们可以发现进行Create和Update操作时都只需调用Object.Save(),Snake.Net将根据Object的State的状态值,自动判断是创建还是更新操作并执行。而对于Delete操作也只需要简单的调用一下Object.Delete ()方法。一切看起来太不可思议了,但事实的确如此简单。
进行Retrieve操作时,我们应当先获取Customer实体的IObjectListProvider接口,然后根据Customer的唯一标识Unique,取得Customer的实例。我们把上面的代码拆分开来仔细观察下面的代码:
//declare
Unique unique;
Customer customer;
IObjectListProvider provider;
IObjectProviderFactory factory;
//get list provider
factory = DataBindObject.ProviderFactory;
provider = factory.GetListProvider(typeof(Customer));
//create unique
unique = new StringToken("AROWT");
//get object
customer = (Customer)provider.Get(unique);
简单点说,要获取一个对象的实例,首先应当根据这个业务实体的类型,获取对应的ListProvider;然后,生成对象的唯一标识(在Snake.Net中所有的唯一标识都应当是实现IUnique接口的对象,Snake.Net内置定义了GuidToken、StringToken、Int64Token和Int32Token分别对应Guid, string, long, int类型);最后,根据唯一标识调用ListProvider的Get方法获取对象实体。
3)事务处理
目前Snake.Net对事物处理,采用了以一种较为简单的方法。
对于一个业务实体类而言,对其Save和Delete方法的调用,会自动实现对事务的支持。确切的说,如果一个业务实体的Save或Delete操作可能需要对多张表进行多步操作,那么这些操作将被自动包含在一个事务内,除非你通过配置文件,禁止使用事务。
Snake.Net可以通过使用TransactionContext 对象处理一个事务块,包含在该事务块内所有Object对象的Save和Delete操作都将被视为在一个事务内执行。请看下面的代码:
//declare
Customer customer;
Employee employee;
//use transactions
using(TransactionContext context = TransactionContext.Create()){
//create customer
customer = new Customer("AROWT");
customer.CompanyName = "Eastasp.com";
customer.ContactName = "Bruce";
customer.ContactTitle = "owner";
customer.Address = "Avda. de la Constituci¨®n 2222";
customer.City = "Tsawassen";
customer.Region = "BC";
customer.PostalCode = "S-958 22";
customer.Country = "UK
";
customer.Phone = "0621-08460";
customer.Fax = "0621-08924";
customer.Save();
//create employee
employee = new Employee();
employee.LastName = "a";
employee.FirstName = "b";
employee.Title = "Mr.";
employee.BirthDate = new DateTime(1988, 2, 2);
employee.Save();
//must set consistent
context.Consistent = true;
}
上述代码会将customer 和employee对象各自的Save方法视为一个事务行为,如果其中任何一个对象保存失败,对会引发事物回滚。特别需要注意的是必须设置context.Consistent = true;这行代码,否则事务将不会被执行。当然TransactionContext对象也可以使用另一种手动方式执行(调用TransactionContext的Commit和Abort方法)。
4)查询
查询是ORM的高级应用也是ORM操作中最难处理的部分。因为查询总是多样化的,如果每个特定查询调用都编制一个对象方法,则维护量太大且扩展性很差,而要编写一个通用的查询接口似乎难度颇大。一些ORM框架会提供一种符合面向对象语言本身语法规则的Query Language支持,但是这种语言本身很累似SQL语法,与标准的SQL语句差别不大,而且这种做法几乎丧失了封装的意义。
查询的核心问题是Query本身是复杂的、多变的和不可完全预计的,Snake.Net的做法是将其化为一个类。Eastasp.Framework.Data.Query.DataQuery对象就是Snake.Net所提供的查询类,从而实现结构化查询的各种功能。同时通过Eastasp.Framework.Data.DbQueryParser对象可以自动将DataQuery转换成对应的SQL语句。这种的好处是,一方面我们在进行查询操作中就无需关心SQL语句,只需对DataQuery对象进行操作即可,从而实现对查询的封装。另一方面,针对不同公司的数据库产品,我们可以编写特定的DbQueryParser类,以实现对其的支持。
下面让我们来看一下Snake.Net是如何实现查询功能的:
//declare
IList customers;
DataConditionBuilder builder;
DataSortBuilder builder2;
IObjectProviderFactory factory;
//query
builder = DataBindObject.GetConditionBuilder(typeof(Customer));
builder2 = DataBindObject.GetSortBuilder(typeof(Customer));
builder.AddCondition("companyname", DataExpressionOperator.BeginsWith, "A");
builder.AddCondition(DataLogical.Or, "companyname", DataExpressionOperator.BeginsWith, "B");
builder.AddCondition(DataLogical.Or, "companyname", DataExpressionOperator.BeginsWith, "C");
builder.AddCondition(DataLogical.Or, "companyname", DataExpressionOperator.BeginsWith, "K");
builder2.AddSort("companyname", DataSortType.Descending);
customers = factory.GetQueryProvider(typeof(Customer)).Query(builder.ToConditions(), builder2.ToSorts());
for(int i = 0; i < customers.Count; i++){
customer = (Customer)customers[i];
Console.WriteLine("name:{0}", customer.CompanyName);
}
从上面的代码中我们可以看出,Snake.Net执行查询操作时非常简便的。先创建一个条件或排序构造器,然后添加查询的条件或排序的字段;再获取该对象的查询供应者,同时利用条件或排序构造器生成的一组条件或排序字段,进行查询操作;最后按顺序返回符合条件的业务实体的集合。
Snake.Net针对业务实体的查询功能应当是相当先进和完善的。不但支持各种复杂条件的查询和排序,并且还全面支持分页操作。同时还可以通过使用缓存和池,实现对查询的优化处理,大大提高查询的效率。下面我们就来叙述一下缓存和池。
缓存和池是Snake.Net中优化查询的两种主要方式。两者都是通过将业务实体置于内存中,从而提高查询的效率。不同的是在缓存模式下,业务实体对象被加载入内存中,并通过设置过期时限来释放对象。池模式与缓存模式主要区别处在于,池模式有一个存储对象最大数量值,当加载的对象数量超过这个值时,就会抛出PoolOverflowException异常。可见缓存可以将一些内容不经常更新的实体加载到内存中,以提高其访问的速度,但当缓存的对象数量过大时将造成可用内存的紧缺,可以用于少量的不经常变更内容的业务实体的查询;而池模式在一定程度上能够灵活调节其占用内存的大小,从而可以避免在占用的内存过多,反而影响系统的整体性能的情况出现。
Snake.Net提供两种方式使用缓存和池来优化查询。
第一种是手动方式:
//use cache mode
customers = factory.GetQueryProvider(ObjectProviderType.Cache, typeof(Customer)).Query(builder.ToConditions(), builder2.ToSorts());
//use pool mode
customers = factory.GetQueryProvider(ObjectProviderType.Pool, typeof(Customer)).Query(builder.ToConditions(), builder2.ToSorts());
第一种通过配置文件:
<!— use cache setting -->
<section name="eastasp.enterprise.samples.customer">
<key name="query.provider.type">cache</key>
</section>
<!— use pool setting -->
<section name="eastasp.enterprise.samples.customer">
<key name="query.provider.type">pool</key>
</section>
然受使用下列代码,自动调用Cache或Pool模式。
customers = factory.GetQueryProvider(typeof(Customer)).Query(builder.ToConditions(), builder2.ToSorts());
6.自定义的数据访问层(Custom DAL)
在上一节中我们叙述了DataBindObjectDataAccess对象的主要功能,虽然在大多数情况下,DataBindObjectDataAccess对象所实现的数据访问层能够处理所有的操作,但是这种实现也是需要付出一定性能的代价。一方面这种代价带来的是系统开发上的便捷,另一方面这种代价也造成了性能上的下降。因而,Snake.Net希望能够在这种利弊达到一个比较好的平衡,那就是通过开发自定义的数据访问层(DAL)来提升整个系统访问数据库的效率。
所谓自定义的访问层就是在Snake.Net现有框架的基础上,为某一个或几个特殊的实体编写特定的数据访问层,以提高数据库操作的性能。应当说DataBindObjectDataAccess自动生成的SQL语句已经起到了一定的优化作用,但总会产生一定程度上的冗余,以Delete操作为例,Snake.Net在对业务实体进行删除操作时,是根据表的主关键字生成SQL语句的,如果一个业务实体存在子表,将对应子表的每条记录生成一条对应的SQL语句进行删除操作,实际上完全可以根据子表的外关键字或者通过一个和主表关联的执行Delete 操作的SQL进行操作。在自定义的数据访问层中,你完全可以针对某个特定对象、特定数据库编写更优化的SQL语句或存储过程实现对数据库访问性能上的提升。