O/R Mapping 的故事

 
当年,我跟朋友一起开发了一套小型的收费系统,其实是个很简单的数据库应用。这个系统是给某个服务提供商使用的,这个提供商有几种类型的客户,针对不同类型的客户有不同的计费标准和收费流程,而且不同类型客户的基本信息的数据结构也差别很大。那么很容易想到,把针对不同类型客户的收费业务,分别放在不同的用例里面,然后这些用例也分别对应了不同的数据表、业务逻辑以及用户界面实现。当然,为了做报表和统计分析的方便,所有的数据表都放在同一个数据库中;为了界面风格的统一,实现了不同用例的用户界面,也都集成在同一个MDI框架窗口里面,并且为不同的用例分别提供一个单独的菜单,还有一个收费首页,用户登录以后可以快速导航进入不同的用例即业务模块中。为了方便并行开发,我用.NET的动态程序集加载技术做了一个简单的插件体系,这样不同用例的业务逻辑和用户界面都可以在一些单独的模块中进行开发,最后插入到MDI的框架窗口中。当然这些单独的模块也会使用一些公共的组件,比如在所谓的数据持久层,我做了一个很简单甚至有些简陋的O/R Mapping机制。然而对于这个小型应用来讲,用例划分很清楚,数据表之间的关系也很简单,尽管简陋一点,也绰绰有余了,而且需求变更时还能方便地增删字段。开发的过程是快乐的,大家都享受着轻松和便捷。
 
其实很早就听说过O/R Mapping,可惜自己太不幸,一直没有机会可以专心地搞一下数据库方面的技术,也没有心情好好去研究Hibernate或者NHibernate,所以对O/R Mapping的理解也只是停留于表面。然后还听说了在医疗领域应用很好的Cache’,这个从骨子里就是面向对象的数据库,于是甚至有点怀疑关系数据库是否已经走到尽头,O/R Mapping是否也已经变成落日黄花。直到后来在业余时间有机会出来搞搞震的时候,才重新回归数据库这个枯燥而又赚钱的古老国度。记得N年前,还在学校的时候,当时正好是WEB1.0的鼎盛时代,我也风风火火地投入ASP+Access/SQLServer的开发之中,疯狂地编写着关于ADO和Recordset的代码,直到麻木。经历过这样悲欢离合的人,也许不用过多解释,单单从字面上看O/R Mapping这个术语,就已经能理解其中的含义了。因此,下面的这个及其老土的O/R Mapping,尽管让我们在很短时间内轻松搞定这个收费系统,但在思想上并没有太多的新意了。
 
首先,所有一切的基础还是数据库访问。就算现在有了ADO.Net,有了DataSet,我个人还是比较喜欢用ADO时代留下来的编程习惯,简单而又快捷。
 
public   class  DataAccess
{
    
private static OleDbConnection _dbConnection;
    [MethodImpl(MethodImplOptions.Synchronized)]
    
public static void CloseDBConnection()
    
{
        
if( _dbConnection != null ) _dbConnection.Close();
    }

        
    [MethodImpl(MethodImplOptions.Synchronized)]
    
public static OleDbDataReader DoQuery( string sql )
    
{
        
if( sql==null || sql.Length<1 ) return null;
        
        OleDbConnection conn 
= new OleDbConnection(ConnectionString);
        conn.Open();

        OleDbCommand cmd 
= new OleDbCommand(sql,conn);
        OleDbDataReader dbData 
= cmd.ExecuteReader();

        _dbConnection 
= conn;
        
return dbData;
    }

}

这里定义了一个静态的DoQuery方法,传入SQL语句,返回DataReader。这恐怕是整个系统里唯一显式地调用OLEDB的地方了,因为后面我们实现映射到数据表的类的时候,只需要把类成员以及这些成员的值编码成SQL语句,然后把SQL语句传给这个DoQuery方法即可。这里顺便用一下MethodImplAttribute为的是保证线程安全。 

下面我们定义一下用来映射的类 DObject,里面包含一些方法,用来生成相应的SQL语句。如果觉得这些方法不够,还可以自己添加一些。其实刚开始的时候我也只是定义了 GetSelectSql() 、GetInsertSql()、GetDeleteSql()、GetUpdateSql()这几个方法,其他的方法都是后来发现需要的时候才添加进去的。
 
public   abstract   class  DObject
{
    
internal abstract string GetCreateTableSql( string tableName );
    
internal abstract string GetSelectAllSql( string tableName );
    
internal abstract string GetSelectSql( string tableName );
    
internal abstract string GetInsertSql( string tableName );
    
internal abstract string GetDeleteSql( string tableName );
    
internal abstract string GetUpdateSql( string tableName );
    
internal abstract string GetMaxAutoIDSql( string tableName );
}

注意(其实大家也已经猜到),一个DObject的实例正好映射数据表中的一条记录,所以DObject派生类里面定义的字段、属性、标签,就会映射到这个数据表里面的字段、及其索引、主键、外键等复杂的关系。他们具体又是怎么映射的呢?也许看过“O/X Mapping的故事”的朋友很容易会想到,不就是反射么。看,metadata多伟大。下面给个GetInsertSql ()的实现作为例子吧,全给出来就太罗嗦了。

internal   string  GetInsertSql(  string  tableName )
{
    PropertyInfo[] plist 
= this.GetType().GetProperties();
    
if( plist == null ) return "";

    StringBuilder sb 
= new StringBuilder();
    sb.Append( 
"INSERT INTO " );
    sb.Append( tableName );
    sb.Append( 
" (");

    
foreach( PropertyInfo p in plist )
    
{
        sb.Append(p.Name
+ "," );
    }


    
string str = sb.ToString().TrimEnd(',');
    str 
= str + ") VALUES (";

    sb 
= new StringBuilder();
    sb.Append( str );

    
foreach( PropertyInfo p in plist )
    
{
        sb.Append( GetProperty( p ) 
+ "," );
    }


    str 
= sb.ToString().TrimEnd(',');
    str 
= str + ")";

    
return str;
}

大家可能会问,为什么非要把表的名字作为参数从外面传入。当然是为了解偶啦,因为真正控制O/R Mapping的工作是在另外一个类DObjectManager里面完成的。使用DObjectManager的时候需要派生一下,派生的时候需要在向DObjectManager的构造函数传入两个参数。一个是你的派生类需要映射到的表的名字tableName,另一个是某条数据记录被查询出来后映射到的对象(即DObject的某个派生类)的类型objectType。其实我一直用的都是.NET Framework 1.1,真希望什么时候能有机会用用2.0,这样实现起范型来就不会这么难看了。

public   abstract   class  DObjectManager
{
    
protected Type _objectType;
    
protected string _tableName;
    
public DObjectManager( string tableName, Type objectType )
    
{
        _tableName 
= tableName;
        _objectType 
= objectType;
    }


    
public abstract bool Create();
    
public abstract bool Drop();

    
public abstract int GetMaxID();
    
public abstract bool Insert( DObject obj );
    
public abstract bool Delete( DObject obj );
    
public abstract bool Update( DObject obj );
    
public abstract DObjectCollection SelectAll();
    
public abstract DObjectCollection Select( DObject criteria, DOrder order );
}

虽然大家看到上面的代码里面有一大堆的abstract,但这里只是简便起见。我真正实现的时候才懒得写这么多的abstract,能在直接实现的方法我都会直接实现,当然也是为了简便起见,不想把继承树搞得很复杂。下面继续以Insert操作为例给一段代码吧,这样整个机制就可以串起来了。

public   bool  Insert( DObject obj )
{
    
if( obj == null ) return false;
    
string sql = obj.GetInsertSql( TableName );

    OleDbDataReader dbData 
= DataAccess.DoQuery( sql );
    
bool res = dbData != null;
    DataAccess.CloseDBConnection();

    
return res;
}

最后给一段调用的例子吧,里面包含了更加丰富的编程元素,比如Attribute,集合等等,不过基本的原理都跟前面是一样的。其实不管从测试驱动的开发原则,还是从循循善诱的行文思路,我都应该把这段例子放在文章的最前面的。谁叫我这么快地想进入主题呢,还是意识流一点吧,反正这只是关于编程的故事,不是计算机科学的论文。

public   class  Customer : DObject
{
    
public Customer()
    
{
    }


    
private decimal _customer_id;
    [DMainKey,DAutoIncrementing]
    [DField(
"customer_id","IDENTITY(1,1) PRIMARY KEY")]
    
public decimal customer_id
    
{
        
getreturn _customer_id; }
        
set{ _customer_id = value; }
    }


    
private string _customer_name = "";
    [DField(
"customer_name","varchar(30)")]
    
public string customer_name
    
{
        
getreturn _customer_name; }
        
set{ _customer_name = value; }
    }


    
private string _customer_address = "";
    [DField(
"customer_address","varchar(256)")]
    
public string customer_address
    
{
        
getreturn _customer_address; }
        
set{ _customer_address = value; }
    }

}


public   class  CustomerManager : DObjectManager
{
    
public CustomerManager() : base( “CustomerTable”, typeof(Customer ) )
    
{
    }

}


private   void  QueryCustomerList()
{
    CustomerManager cm 
= new CustomerManager();
    DObjectCollection clist 
= cm.SelectAll();
    
foreach(Customer c in clist )
    
{
        
//
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值