基于Grove实践ORM的感悟
名词解释
- 数据访问层:基于逻辑分层(Layer)的应用程序中直接与数据库交互的应用程序代码。
- 业务实体:应用程序中业务数据的载体,一般有DataSet、xml文本、自定义类等表现形式。(详细信息)
- ORM:是Object Relation Mapping的所写。通俗的讲就是要建立业务实体与关系数据库的映射关系。(详细信息)
Grove Develop Kit
Grove Develop Kit是一套由国人开发的免费的数据持久层及相关工具,它包括Grove Develop Component和Grove Tool Kit 两部分:
Grove Develop Component是一套基于.NET的可重用开发组件,为开发人员提供一个数据持久层并提供多种ORM方式,另外它也支持传统的ADO.Net式的数据库访问方式。
Grove Tool Kit是基于微软VS的外接程序。通过使用Grove Tool Kit,开发人员能够在VS环境中直接从数据库表生成相应的业务实体类,极大的提高开发效率。
应用实例
说明
本实例旨在说明Grove的应用要点,未提供操作细节。如果需要全部代码,请到这里下载。
创建环境
1) 在Sql Server数据库中建立一个名为”Test”的数据库并建立如下两张表及对应关系(本实例所使用的业务数据类型非常简单,事实上Grove支持各种数据类型):
表名 |
字段 |
Customer |
CustomerID char(36) PK |
Addresse |
AddressID char(36) PK |
2) 在VS中建立一个TestGrove的解决方案,它保护工程DAL和DataEntity,前者是数据访问层,后者是业务实体层。
3) 通过菜单”工具-Grove Tool Kit”打开Grove的操作界面。通过点击该界面工具栏的”Set connection string”按钮来配置Grove的数据库操作环境。配置完成后连接数据库可看到Address和Customer这两张表。(工具栏第二个按钮用于设置数据库连接字符串,第一个按钮用于连接数据库,第三个按钮用于创建业务实体类)
建立业务实体
本实例使用自描述自定义类作为业务实体类。所谓自描述,就是在该类中用Attribute的形式包含了与数据库的映射关系。个人认为此种方式容易理解和维护。
4) 在Grove界面选择Address表,点击代码生成按钮将得到以下代码:
[DataTable("Address")]
public class Address
{
String _AddressID;
String _CustomerID;
String _Location;
[KeyField("AddressID")]
public String AddressID
{
get{return this._AddressID;}
set{this._AddressID=value;}
}
[ForeignKeyField("CustomerID")]
public String CustomerID
{
get{return this._CustomerID;}
set{this._CustomerID=value;}
}
[DataField("Location")]
public String Location
{
get{return this._Location;}
set{this._Location=value;}
}
}
注意:Grove对关键字字段进行映射时会将该字段看成自增类型。本实例未使用自增ID做关键字,所以需要手动调整。调整后AddressID属性的代码如下:
[KeyField("AddressID",KeyType = UniqueIDType.OtherDefinition)]
public String AddressID
{
get{return this._AddressID;}
set{this._AddressID=value;}
}
用同样的方法得到Customer业务实体类,代码如下:
[DataTable("Customer")]
public class Customer
{
String _CustomerID;
String _Name;
[KeyField("CustomerID",KeyType = UniqueIDType.OtherDefinition)]
public String CustomerID
{
get{return this._CustomerID;}
set{this._CustomerID=value;}
}
[DataField("Name")]
public String Name
{
get{return this._Name;}
set{this._Name=value;}
}
}
5) 生成业务实体强类型集合类:Grove暂时不支持直接生成业务实体集合类,好在这部分工作很简单(写完一个后用copy+replace就能搞定),代码如下(为使代码简单只实现了集合最基本的方法):
public class AddressCollection : CollectionBase
{
public Address this[Int32 index]
{
set
{
this.List[index] = value;
}
get
{
return (Address)this.List[index];
}
}
public Int32 Add(Address value)
{
return this.List.Add(value);
}
}
public class CustomerCollection : CollectionBase
{
public Customer this[Int32 index]
{
set
{
this.List[index] = value;
}
get
{
return (Customer)this.List[index];
}
}
public void Add(Customer value)
{
this.List.Add(value);
}
}
6) 修改Customer类,为其添加一个Addresses的属性,访问该属性可获取Customer对象对应全部Address。添加如下代码:
private AddressCollection _addresses;
public AddressCollection Addresses
{
get
{
if(this._addresses == null)
this._addresses = new AddressCollection();
return this._addresses;
}
}
建立数据访问层
7) ORM功能:Grove 提供ORM功能最重要的两个接口是IObjectOperator和IObjectQuery,前者为业务实体提供Insert、Update和Delete的功能,后者主要负责业务实体的查询。Grove没有提供直接实现IObjectOperator接口的public的类型,但我们可以通过ObjectOperatorFactory这个工厂类来得到一个实现了IObjectOperator接口的实例。代码如下:
public class CustomerDB
{
//插入一个Customer对象
public static void Insert(Customer customer)
{
IObjectOperator objectOperator =
ObjectOperatorFactory.GetObjectOperator();
objectOperator.BeginTranscation();//开始事务
try
{
//插入Customer对象与数据库有映射关系的属性值
objectOperator.InsertObject(customer);
//插入Customer对象对应的Address对象
objectOperator.InsertObjects(customer.Addresses);
objectOperator.Commit();//提交事务
}
catch
{
objectOperator.Rollback();//回滚事务
throw;
}
finally
{
objectOperator.Dispose();
}
}
//更新一个Customer对象
public static void Update(Customer customer)
{
IObjectOperator objectOperator =
ObjectOperatorFactory.GetObjectOperator();
objectOperator.BeginTranscation();
try
{
//更新Customer
objectOperator.UpdateObject(customer);
//删除Customer对象对应的Address
objectOperator.RemoveChildObjects(
customer.CustomerID, typeof(Address));
//插入Customer对象对应的Address对象
objectOperator.InsertObjects(customer.Addresses);
objectOperator.Commit();
}
catch
{
objectOperator.Rollback();
throw;
}
finally
{
objectOperator.Dispose();
}
}
//删除一个Customer对象
public static void Remove(Customer customer)
{
IObjectOperator objectOperator =
ObjectOperatorFactory.GetObjectOperator();
objectOperator.BeginTranscation();
try
{
//删除Customer对象
objectOperator.RemoveObject(customer);
//删除Customer对象对应的Address
objectOperator.RemoveChildObjects(
customer.CustomerID,typeof(Address));
objectOperator.Commit();
}
catch
{
objectOperator.Rollback();
throw;
}
finally
{
objectOperator.Dispose();
}
}
//通过查询条件获取Customer集合
public static CustomerCollection ReadCustomers(String filter)
{
IObjectOperator objectOperator =
ObjectOperatorFactory.GetObjectOperator();
IObjectQuery objectQuery =
objectOperator.NewQuery(typeof(Customer));
objectQuery.Filter = filter;//设置查询条件
CustomerCollection customers =new CustomerCollection();
ArrayList alCustomers;
try
{
alCustomers = objectQuery.Execute(typeof(Customer));
foreach(Object oCustomer in alCustomers)
{
Customer customer = (Customer)oCustomer;
//读取Customer对应的Address集合
ArrayList alAddresses = new ArrayList();
objectOperator.RetrieveChildObjects(
customer.CustomerID,alAddresses,typeof(Address));
foreach(Object oAddress in alAddresses)
{
customer.Addresses.Add((Address)oAddress);
}
customers.Add(customer);
}
}
finally
{
objectOperator.Dispose();
}
return customers;
}
}
以上代码随便比较长,但结构比较清晰,思路基本都是创建ObjectOperator -> 开启事务 -> 操作数据库 -> 释放ObjectOperator,调用过程都使用了try来捕获异常。另外值得一提的是Grove返回数据实体集合的时候是以ArrayList形式保存的,需要进行一下类型转换,然后添加到自定义的业务实体集合种。
8) 使用SQL或存储过程:有时候上面这个数据访问类并不能完全满足我们的要求,比如我们需要知道当前数据库种Customer的数量,当然这可以通过Ado.net来实现,但用Grove来实现的话更简便,代码如下(添加到CustomerDB类种):
//读取所有Customer的数量
public static Int32 GetCustomerCount()
{
IDbOperator dbOperator = DbOperatorFactory.GetDbOperator(
Grove.AppSettingManager.getAppSetting("DBConnString"));
try
{
return Convert.ToInt32(
dbOperator.ExecScalar("Select count(*) from customer"));
}
finally
{
dbOperator.Dispose();
}
}
意见和建议
- Grove所需的配置信息只能通过应用程序配置文件进行设置,不能用属性直接设置,不利于进行单元测试。
- 以ArrayList或DataSet形式返回数据实体集合,最好再能以CollectionBase的形式返回数据实体集合,这有利于操作强类型的数据实体集合。
- 支持自动生成强类型的数据实体集合。
- 能够在VS英文环境下生成代码文件。