A Data Access Layer to persist business objects using attributes and reflection - Part I
By xicoloko
Persistance to business objects through attributes and reflection
无常译
目录:
第一部分
第二部分
第三部分
前言
从使用.NET(Beta 1 Release版本之后)开始,我开发过一些的使用MS Access或SQL Server的数据应用程序。ADO.NET的确是比ADO或OLEDB简单得多。我只需要一个打开的连接,就可以通过一个DataSet或DataReader从数据库中取数据。你可以根据需要选择合适的方法来检索数据库中的数据。
我知道强类型DataSet的强大功能,并且可以节省时间,但是我更喜欢用我的方法来进行数据库编程。我喜欢用一个类,调用这个类的方法就可以对其进行更新。这样一来,如果我需要往数据库中添加一行数据,只需要创建一个对象的实例并设置它的属性,再调用update方法。就这么简单!!
但这种编程方法让我输入很多代码,比如business object 类、更新数据和读数据的代码。开始的时候我没使用存储过程来更新数据,所以我必需为每个business object写一段SQL语句来更新数据表中的数据。只要我一改变数据或是business design,就不得不重复做这些工单调乏味的作。
我的解决方案
在我的方案中,首先要创建一个简单的类(DALQueryBuilder,代码在本系列的最后一篇文章中),它可以让我在更新对象的时候不用输入任何代码。我所要做的仅仅是给每列赋值,这个类就可以生成相应的SQL语句。在我完成之后我会高兴上一个礼拜...
当我开始用SQL Server代替Access的时候我就高兴不起来了。我不应该使用纯粹的SQL语句来更新我的对象,我只好使用存储过程。痛苦的日子又来了...我不得不创建上打的SQL参数来更新我的对象。令人厌烦的工作又开始了...
我注意到我可以写一个简单的类来生成这些参数,就像一个SQL语句生成器。虽然这个方法将只需要写很少的代码,但任何时候当我的方案改变时,都要回顾一下更新的代码。
我想到一个创建一些类来声明持久化到数据库中的方法。同样的使用另一些特性来声明属性如何影射到表中的列。以后改变方案的时候我只需需要修改business object类。
为了方便阅读,我将这篇文章分作3个部分。第一部分将解释怎样用特性来描述一个business class,第二部分讲怎么取得这些描述信息,最后一部分我将要给你展示完成的方案。
本方案仅在SQL Server7.0下测试通过。如果谁发现在Access下不能工作请告诉我。
第一部分:特性
使用特性是给程序集、类、属性、方法和字段添加声明信息的一种方法。有一些特性已经包含在.NET Framework中,但你也可以创建自己的特性。
我使用特性来声明把一个类存储到数据库中的方法。一个类中的属性可以被持久化也可以使用存储过程把数据存储到数据库中。我在类的属性中使用特性来声明和数据表中列的对应关系。列可以是简单的数据字段、unique键或外键。为了更好的理解特性,我推荐阅读.NET帮助文档或是James T. Johnson写的这篇文章(http://www.codeproject.com/csharp/dotnetattributes.asp)。
如何创建自定义特性?
这非常简单。你可以从System.Attribute类派生一个新类。按照命令的约定,自定义特性类最好使用Attribute后缀。在你创建自定义特性时,可以使用一个特性来宣告怎样使用,使用在类上?还是属性上?是否允许重复使用等等。
现在来看一段代码。这是一个用来描述Business Object类的特性。
using System.Data;
namespace DAL
{
[AttributeUsage(AttributeTargets.Property)]
public class BaseFieldAttribute : Attribute
{
string columnName;
public BaseFieldAttribute( string columnName)
{
this .columnName = columnName;
}
public string ColumnName
{
get { return columnName; }
set { columnName = value; }
}
}
[AttributeUsage(AttributeTargets.Property)]
public class DataFieldAttribute : BaseFieldAttribute
{
DbType dbType = DbType.String;
int size = 0 ;
public DataFieldAttribute( string columnName) : base (columnName)
{
}
public DbType Type
{
get { return dbType; }
set { dbType = value; }
}
public int Size
{
get { return size; }
set { size = value; }
}
};
[AttributeUsage(AttributeTargets.Property)]
public class KeyFieldAttribute : BaseFieldAttribute
{
public KeyFieldAttribute( string columnName) : base (columnName)
{
}
};
[AttributeUsage(AttributeTargets.Property)]
public class ForeignKeyFieldAttribute : BaseFieldAttribute
{
public ForeignKeyFieldAttribute( string columnName) : base (columnName)
{
}
};
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class DataTableAttribute : Attribute
{
string tableName;
string updateStoredProcedure = "" ;
public DataTableAttribute( string tableName)
{
this .tableName = tableName;
}
public string TableName
{
get { return tableName; }
set { tableName = value; }
}
public string UpdateStoredProcedure
{
get { return updateStoredProcedure; }
set { updateStoredProcedure = value; }
}
}
}
像你所看到的一样,在每个类都有和个AttributeUsage特性,它表示此特性可以在什么地方使用。
如何使用特性来声明类?
假设你有一个程序,需要存储顾客和联系人的信息。在OO设计中,我们先要设计一个Person类,联系为就是Person加上地址和联系的信息,顾客是联系为加上购买的统计。同样,一个顾客类也是依赖于Persons类。我知道这样很乏味,但我还是在要文章的其余部分用到它。
using System.Data;
using DAL;
namespace TestApp
{
public class Person
{
string name = "" ;
int age = 0 ;
int id = 0 ;
[KeyField( " id " )]
public int Id
{
get { return id; }
set { id = value; }
}
[DataField( " name " , Size = 50 )]
public string Name
{
get { return name; }
set { name = value; }
}
[DataField( " age " )]
public int Age
{
get { return age; }
set { age = value; }
}
public override string ToString()
{
return string .Format( " <PERSON>{0}, {1} years old " , Name, Age);
}
}
[DataTable( " contact " , UpdateStoredProcedure = " sp_UpdateContact " )]
public class Contact : Person
{
string phone = "" ;
string email = "" ;
string address = "" ;
string address2 = "" ;
string city = "" ;
string postalCode = "" ;
string state = "" ;
string country = "" ;
[DataField( " phone " , Size = 20 )]
public string Phone
{
get { return phone; }
set { phone = value; }
}
[DataField( " email " , Size = 80 )]
public string Email
{
get { return email; }
set { email = value; }
}
[DataField( " address " , Size = 80 )]
public string Address
{
get { return address; }
set { address = value; }
}
[DataField( " address2 " , Size = 80 )]
public string Address2
{
get { return address2; }
set { address2 = value; }
}
[DataField( " city " , Size = 50 )]
public string City
{
get { return city; }
set { city = value; }
}
[DataField( " postalCode " , Size = 20 )]
public string PostalCode
{
get { return postalCode; }
set { postalCode = value; }
}
[DataField( " state " , Size = 4 )]
public string State
{
get { return state; }
set { state = value; }
}
[DataField( " country " , Size = 50 )]
public string Country
{
get { return country; }
set { country = value; }
}
public override string ToString()
{
return string .Format( " <Contact>{0} - {1} from {2} " , Id, Name, Country);
}
}
public enum CustomerRelationship { Family, Friend, Other };
[DataTable( " customerDependent " , UpdateStoredProcedure = " sp_UpdateCustomerDependent " )]
public class CustomerDependent : Person
{
int customerId = 0 ;
CustomerRelationship relationship = CustomerRelationship.Family;
protected CustomerDependent()
{
}
public CustomerDependent( int customerId)
{
this .customerId = customerId;
}
[ForeignKeyFieldAttribute( " customerId " )]
public int CustomerId
{
get { return customerId; }
set { customerId = value; }
}
[DataFieldAttribute( " relationship " )]
public CustomerRelationship Relationship
{
get { return relationship; }
set { relationship = value; }
}
}
public enum CustomerStatus { Active, Inactive };
[DataTable( " customer " , UpdateStoredProcedure = " sp_UpdateCustomer " )]
public class BaseCustomer : Contact
{
CustomerStatus status = CustomerStatus.Active;
Decimal totalPurchased = 0M;
int numberOfPurchases = 0 ;
DateTime dateRegistered = DateTime.Now;
[DataField( " status " )]
public CustomerStatus Status
{
get { return status; }
set { status = value; }
}
[DataField( " totalPurchased " )]
public Decimal TotalPurchased
{
get { return totalPurchased; }
set { totalPurchased = value; }
}
[DataField( " numberOfPurchases " )]
public int NumberOfPurchases
{
get { return numberOfPurchases; }
set { numberOfPurchases = value; }
}
[DataField( " dateRegistered " )]
public DateTime DateRegistered
{
get { return dateRegistered; }
set { dateRegistered = value; }
}
public override string ToString()
{
return string .Format( " <Customer>{0} - {1} from {2}, registered in {3}. " +
" #{4} purchases spending a total of $ {5} " ,
Id,
Name,
Country,
DateRegistered,
NumberOfPurchases,
TotalPurchased);
}
}
public class Customer : BaseCustomer
{
ArrayList dependents = null ;
public ArrayList Dependents
{
get
{
if (dependents == null )
{
DAL dal = new DAL();
dependents = dal.GetCustomerDependents( this );
}
return dependents;
}
}
public CustomerDependent NewDependent()
{
return new CustomerDependent(Id);
}
public Decimal PurchaseMedia
{
get { return TotalPurchased / NumberOfPurchases; }
}
}
}
Person类是我们所有类的基类,这是唯一一个没有DataTable特性的类,因为它不需要被持久化。只有Contacts、CustomerDependents 和Customers保存在数据库中。但每个派生类都拥有Person类中已经定义的一些列。在这个例子中,ID属性是一int、自动增长类型的主键。属性名为50个字符以内的字符串等竺。这里仅定义了属性CustomerDependent::CustomerId为外键。
在SQL Server中创建这些类,仅需要在查询分析器在执行下面的SQL脚本。
and OBJECTPROPERTY (id, N ' IsUserTable ' ) = 1 )
drop table [ dbo ] . [ contact ]
GO
if exists ( select * from sysobjects where id = object_id (N ' [dbo].[customer] ' )
and OBJECTPROPERTY (id, N ' IsUserTable ' ) = 1 )
drop table [ dbo ] . [ customer ]
GO
if exists ( select * from sysobjects where id = object_id (N ' [dbo].[customerDependent] ' )
and OBJECTPROPERTY (id, N ' IsUserTable ' ) = 1 )
drop table [ dbo ] . [ customerDependent ]
GO
CREATE TABLE [ dbo ] . [ contact ] (
[ id ] [ int ] IDENTITY ( 1 , 1 ) NOT NULL ,
[ name ] [ varchar ] ( 50 ) NOT NULL ,
[ age ] [ int ] NOT NULL ,
[ address ] [ varchar ] ( 80 ) NOT NULL ,
[ postalCode ] [ varchar ] ( 20 ) NOT NULL ,
[ phone ] [ varchar ] ( 20 ) NOT NULL ,
[ email ] [ varchar ] ( 80 ) NOT NULL ,
[ address2 ] [ varchar ] ( 80 ) NOT NULL ,
[ city ] [ varchar ] ( 50 ) NOT NULL ,
[ state ] [ varchar ] ( 4 ) NOT NULL ,
[ country ] [ varchar ] ( 50 ) NOT NULL
) ON [ PRIMARY ]
GO
CREATE TABLE [ dbo ] . [ customer ] (
[ id ] [ int ] IDENTITY ( 1 , 1 ) NOT NULL ,
[ name ] [ varchar ] ( 50 ) NOT NULL ,
[ age ] [ int ] NOT NULL ,
[ address ] [ varchar ] ( 80 ) NOT NULL ,
[ postalCode ] [ varchar ] ( 20 ) NOT NULL ,
[ phone ] [ varchar ] ( 50 ) NOT NULL ,
[ email ] [ varchar ] ( 80 ) NOT NULL ,
[ address2 ] [ varchar ] ( 80 ) NOT NULL ,
[ city ] [ varchar ] ( 50 ) NOT NULL ,
[ state ] [ varchar ] ( 4 ) NOT NULL ,
[ country ] [ varchar ] ( 50 ) NOT NULL ,
[ totalPurchased ] [ money ] NOT NULL ,
[ numberOfPurchases ] [ int ] NOT NULL ,
[ dateRegistered ] [ datetime ] NOT NULL ,
[ status ] [ smallint ] NOT NULL
) ON [ PRIMARY ]
GO
CREATE TABLE [ dbo ] . [ customerDependent ] (
[ id ] [ int ] IDENTITY ( 1 , 1 ) NOT NULL ,
[ name ] [ varchar ] ( 50 ) NOT NULL ,
[ customerId ] [ int ] NOT NULL ,
[ relationship ] [ int ] NOT NULL ,
[ age ] [ int ] NOT NULL
) ON [ PRIMARY ]
GO
下一部分要讨论的内容
在下一篇文章中,我将要给你绍介反射件怎样帮助我们聚合更新数据库时需要的信息的。我将要
创建一个简单的应用程序,通过程序集中的类定义来生成创建表的SQL脚本。