代码改变世界

A Data Access Layer to persist business objects using attributes and reflection - Part III [无常译]

2006-04-12 16:50  无常  阅读(917)  评论(3编辑  收藏  举报

下载源代码

目录:
第一部分
第二部分
第三部分

前言

这是本系列最后一篇文章。在第一篇中我们知道了自给使用特性来给类添加声明信息。第二篇中我们已经知道如何使用System.Reflection namespace来取得声明信息。现在我们将要创建一个DAL库,可以根据任何对象自身的声明信息来实现持久化。

设计DAL库

在创建这个DAL库时,我打算将它设计成既支持SQL Serverdata provider ,也支持OldDbdata provider 。也可以扩展支持其它的data provider。我把这个库分成下面几个部分:

Utility classes
DALQueryBuilder类

在每一篇文章中已经提到过,这个类帮助构建更新对象到数据库中的SQL语句。

DALParameter类
一个普通的参数类,在存储过程中用到。

DALException类
这是一个System.Exception的派生类。在操作数据库操作出错时就会掏出此异常。我提供了有关出错的更多的信息。

Attribute 类

派生于System.Attribute 类,在第一篇中已经介绍过。

DAL itself

DALEngine 类

数据操作的抽象基类,但数据库操作更加容易。它拥有虚拟和抽象方法,不同的data provider会有不同的实现。大多的函数从数据库中返回DataSets、DataReaders,它提供方法来操作使用DAL特性标记过的对象。

DALSqlEngine 类

DALEngine的使用SQL Server的实现。 

详细分析DALEngine 类

这是DALEngine类的声明:

public abstract class DALEngine : IDisposable
{
    
//
    
// private data members
    
//
    IDbConnection conn       = null;
    
string connectionString  = "";
    ArrayList parameters 
= new ArrayList();
    
bool canClose = true;

    
// constructor
    public DALEngine(string connectionString);
    
public bool CanClose;
    
public string ConnectionString;


    
protected IDbConnection Connection;
    
protected ArrayList Parameters;

    
public void Close();
    
public void Dispose();


    
//
    
// methods that must be override with a specific data provider 
    
// implementation please see the implementation of DALSqlEngine 
    
// or DALOleDbEngine
    
// 
    protected abstract IDbConnection GetConnection();
    
protected abstract IDbCommand CreateCommand(string spName);
    
public abstract void ExecSP_DataSet(string spName, DataSet dataSet, 
                                       
string tableName);
    
public abstract void ExecQuery_DataSet(string query, DataSet dataSet, 
                                           
string tableName);


    
//
    
// related to stored procedure parameters
    
//
    public DALParameter GetParameter(string name);
    
void UpdateOutputParameters(IDbCommand cmd);
    
public void AddParameter(DALParameter param);
    
public void ClearParameters();


    
//
    
// for those that use stored procedures
    
//
    public IDataReader ExecSP_DataReader(string spName);
    
public IDataReader ExecSP_DataReader(string spName, 
                                         CommandBehavior behavior);
    
public object ExecSP_Scalar(string spName);
    
public int ExecSP_NonQuery(string spName);


    
//
    
// methods for those that use plain SQL statements
    
//
    public IDataReader ExecQuery_DataReader(string query, 
                                            CommandBehavior behavior);
    
public IDataReader ExecQuery_DataReader(string query);
    
public object ExecQuery_Scalar(string query);
    
public int ExecQuery_NonQuery(string query);




    
//
    
// Business objects methods
    
//
    public static object CreateFromReader(IDataReader reader, Type objType);
    
public object RetrieveObject(object keyValue, Type objType);
    
public int RetrieveChildObjects(object foreignKeyValue, ArrayList objects,
                                    Type childType);
    
void UpdateObjectSql(object o, DataTableAttribute dataTable);
    
void UpdateObjectStoredProcedure(object o, DataTableAttribute dataTable);
    
public void UpdateObject(object o);
    
public void UpdateObjects(IEnumerable enumObjects);
}

像你所想象中的一样,我们只需要关注最后的6个函数。其它的是很容易理解的,因为他们很容易实现。

CreateFromReader函数返回一个objType类型的对象的实例。当我们从数据库中取出数据时,就可以使用此函数来返回一个business object实例。它是一个静态函数,所以不需要建立数据库连接,只需要一个DataReader就可以读数据了。

RetrieveObject函数返回一个objType类型的对象的实例。这个实例的属性与表主键列与keyValue参数相等的唯一一行对应。这个函数在底层创建了有WHERE子句的SELECT语句。RetrieveChildObjects创建外键与foreignKeyValue 参数相等的 childType 类型的对象实例。创建的这些实体都被加到objects 参数中。

UpdateObject 和UpdateObjects 函数对数据库进行一个更新操作。更新可以使用储存过程(在此例中的类的DataTable 特性用UpdateStoreProcedure 属性来设置)或是普通SQL语句。二个私有方法UpdateObjectSql 和UpdateObjectStoredProcedure 负责执行这些更新操作。

这样做的好处

所有使用此DAL的都会使数据库编程更加容易。我强烈建议使用这个DAL,因为已经实现了重复冗长的任务,即使你没打算像我一样进行数据库编程。这次你不是必需用到,但以后至少会用到一次。

但是如果你开发数据库程序的时候用business objects ,这个库可以帮你节省很多时间和精力。这只有一个好处,就是在修改数据库时需要重新检查代码,这可以使用你自己的一个工具来完成,但利用在这篇文章的第二部分创建的工具可以完成相反的功能,即先修改business objects然后再根据business objects生成表。

为了让你更容易理解获取和更新对象,我在DAL库的代码中包含有一个样本的应用程序。这里有一些此应用程序的代码片断,你可以从中了解它的功能。

这个DAL库确实存在很多BUG,这也是我在这里发表这篇文章的原因之一。也许你会有更好的方法和代码来实现这个库的目的。有什么建议请告诉我,像我一样乐于改进这相库。 

因为我要使用SQL Server,可以我从DALSqlEngine派生了一个DAL类来实现这个样板应用程序。这是我的DAL类的代码:

public class DAL : DALSqlEngine
{
    
const string CONN_STRING = "server=localhost;uid=sa;pwd=;database=pubs";

    
public DAL() : base(CONN_STRING)
    {

    }

    
public ArrayList GetCustomerDependents(Customer customer)
    {
        ArrayList result 
= new ArrayList();

        RetrieveChildObjects(customer.Id, result, 
typeof(CustomerDependent));

        
return result;
    }

    
public void UpdateCustomerDependents(Customer customer)
    {
        UpdateObjects(customer.Dependents);
    }
}

是不是很简单?现在是这个应用程序的代码,仅仅插入/更新了contact/customers和customer从属的对象。

public static void Main()
{

    DAL dal 
= new DAL();

    
try
    {

        Contact contact 
= new Contact();
        contact.Name 
= "Joao Cardoso";
        contact.Age  
= 23;
        contact.Address 
= "Av. Rio Branco, 202/121";
        contact.Address2 
= "Centro";
        contact.PostalCode 
= "09029-901";
        contact.City 
= "Sao Paulo";
        contact.State 
=  "SP";
        contact.Country 
= "Brazil";

        dal.UpdateObject(contact);
        Console.WriteLine(contact);


        Contact joaoCardoso 
= (Contact)dal.RetrieveObject(1typeof(Contact));
        joaoCardoso.Age
++;
        Console.WriteLine(joaoCardoso);
        Console.WriteLine(
"");


        Customer customer 
= new Customer();
        customer.Name 
= "Paul Noyter";
        customer.Age  
= 34;
        customer.Address 
= "All St, 2202/2121";
        customer.Address2 
= "Downville";
        customer.PostalCode 
= "90931";
        customer.City 
= "Los Angeles";
        customer.State 
=  "CA";
        customer.Country 
= "United States";
        customer.TotalPurchased 
+= 1900.87M;
        customer.NumberOfPurchases
++;

        dal.UpdateObject(customer);


        Customer paul 
= (Customer)dal.RetrieveObject(1typeof(Customer));
        Console.WriteLine(paul);

        paul.TotalPurchased 
+= 100M;
        paul.NumberOfPurchases
++;
        dal.UpdateObject(paul);

        
if (paul.Dependents.Count == 0)
        {
            CustomerDependent dependent 
= paul.NewDependent();
            dependent.Name 
= "Marie Noyter";
            dependent.Age 
= 31;
            paul.Dependents.Add(dependent);


            dependent 
= paul.NewDependent();
            dependent.Name 
= "Mark Noyter";
            dependent.Age 
= 10;
            paul.Dependents.Add(dependent);


            dependent 
= paul.NewDependent();
            dependent.Name 
= "Claudia Snorg";
            dependent.Age 
= 32;
            dependent.Relationship 
= CustomerRelationship.Friend;
            paul.Dependents.Add(dependent);

            dal.UpdateCustomerDependents(paul);
        }
        
else
        {
            Console.WriteLine(
"Dependents of {0}", paul.Name);

            
foreach(CustomerDependent dependent in paul.Dependents)
            {
                Console.WriteLine(
"<Dependent>{0} - {1} [{2}]", dependent.Id, 
                                  dependent.Name, dependent.Relationship);
                dependent.Relationship 
= CustomerRelationship.Family;
            }

            dal.UpdateCustomerDependents(paul);
        }

    }
    
finally
    {
        dal.Dispose();
    }
}

局限性

当然这个库也有一定的局限性。部分可以通过扩展DAL或attributes来解决。如果你不使用自动增长列,那么就必须使用存储过程来更新数据。这是因为DAL不能断定这个对象是更新还是插入到数据库。你可以扩展DAL来检测已经的特性或接口来提供需要的信息;一般不支持复合主键的表;性能要比直接使用SqlCommand要差些,但是你会有更多的时间来使用business classes替换数据库编程;我没有实现使用存储过程从数据库中检索对象,但这很容易实现。

结论

在.NET世界里,有很多途径来进行数据库编程。Visual Studio .NET允许你使用强类型的DataSet,可以节省你很多生成代码的时间。使用DataSets也一点不错,但是我更喜欢用business objects。当我开始开发这样的一样类的时间,我就可以知道写其它的类需要多长时间。这个库介绍到这里,你应该知道了怎么完善它来开发使用business objects的数据库应用程序。

---------------------------------------------------by wuchang
 终于完成了这篇文章的翻译。
很多外文的文章,阅读没什么问题,意思都能看懂,但想用文字翻译流畅出来,却要费一番工夫了。
翻译得不好的地方欢迎指出。