再谈orm和代码生成

近几日试用了NHibernate和ibatisnet这两个比较流行,功能也算不错的orm framework,感觉有收获也有遗憾。

说收获是因为这两者确实对orm支持得很不错,结构框架的定义模式也很值得学习,从效果和使用的难易程度来讲应该说各有特点,个人更喜欢ibatisnet一点。

说遗憾是不管是哪一个,感觉都摆脱不了使用反射的固有性能损失,同时,对于需要动态构造sql的复杂查询等等的支持,感觉也都不是很完美。

想到反射,总是忍不住去想代码生成,因为,虽然这两者的出发点都是为了减少编程人员的工作量,但某种程度是对立的,前者可能减少了总代码量和生成的程序集的大小,换来了清晰的结构,后者,则可以获得更好的性能,同时,个人感觉似乎扩展性更强。

所以,看完这两个orm构架,一度的code review时的兴奋过后,还是,直观上,更倾向于用代码生成,而不是orm来较少我们的工作量。

下面想再举例说明一下以上倾向性的来源(用于比较的代码分别采用ibatisnet所带的NPetShop的数据库表及其代码,和我自己的一个代码生成工具以同样的数据表为基础生成的代码):

1、首先来看看两者需要的DomainObject,我个人更愿意称呼ValueObject,这样的对象,在orm来讲,肯定是与数据库表Accounts相对应,代码生成的话也是类似的

NPetShop的Account.cs

using System;

namespace NPetshop.Domain.Accounts
{
    
/// <summary>
    
/// Business entity used to model user account
    
/// </summary>

    [Serializable]
    
public class Account 
    
{

        
Private Fields

        
Properties

    }

}


CodeGen生成的AccountInfo.cs
// Code Generated By Code Generator For CN.Teddy.Helper.Data V2.0
// 2005-4-10 15:43:27

using System;

namespace NPetshop.ValueObject
{
    [Serializable]
    
public class AccountInfo
    
{
        
private string _Account_Id;
        
private string _Account_Email;
        
private string _Account_FirstName;
        
private string _Account_LastName;
        
private string _Account_Status;
        
private string _Account_Addr1;
        
private string _Account_Addr2;
        
private string _Account_City;
        
private string _Account_State;
        
private string _Account_Zip;
        
private string _Account_Country;
        
private string _Account_Phone;

        
public string Account_Id
        
{
            
get return _Account_Id; }
            
set { _Account_Id = value; }
        }


        
public string Account_Email
        
{
            
get return _Account_Email; }
            
set { _Account_Email = value; }
        }


        
public string Account_FirstName
        
{
            
get return _Account_FirstName; }
            
set { _Account_FirstName = value; }
        }


        
public string Account_LastName
        
{
            
get return _Account_LastName; }
            
set { _Account_LastName = value; }
        }


        
public string Account_Status
        
{
            
get return _Account_Status; }
            
set { _Account_Status = value; }
        }


        
public string Account_Addr1
        
{
            
get return _Account_Addr1; }
            
set { _Account_Addr1 = value; }
        }


        
public string Account_Addr2
        
{
            
get return _Account_Addr2; }
            
set { _Account_Addr2 = value; }
        }


        
public string Account_City
        
{
            
get return _Account_City; }
            
set { _Account_City = value; }
        }


        
public string Account_State
        
{
            
get return _Account_State; }
            
set { _Account_State = value; }
        }


        
public string Account_Zip
        
{
            
get return _Account_Zip; }
            
set { _Account_Zip = value; }
        }


        
public string Account_Country
        
{
            
get return _Account_Country; }
            
set { _Account_Country = value; }
        }


        
public string Account_Phone
        
{
            
get return _Account_Phone; }
            
set { _Account_Phone = value; }
        }


        
Static Members
    }

}



比较两者的DomainObject,当然都可以用工具来生成,CodeGen的代码在基本的属性之外要多一些辅助的静态函数,这些静态函数的作用就是用于避免使用反射而被DbHelper内部调用的,这些对业务逻辑是透明的。 从总的手工编码量来讲,都可以借助工具生成,所以工作量都为0。

2、下面再来看看逻辑层分别怎么调用各自的数据处理层

首先是NPetShop的AccountSqlMapDao.cs(可能是出于演示目的该代码没有加入事务保护,不过它实际是支持)

using System;
using System.Collections;

using NPetshop.Domain.Accounts;
using NPetshop.Persistence.Interfaces.Accounts;
using NPetshop.Persistence.MapperDao;

namespace NPetshop.Persistence.MapperDao.Accounts
{
    
/// <summary>
    
/// Summary description for AccountSqlMapDao
    
/// </summary>

    public class AccountSqlMapDao : BaseSqlMapDao, IAccountDao
    
{
        
IAccountDao Members
    }

}


接下来是CodeGen的AccountLogic.cs
// Code Generated By Code Generator For CN.Teddy.Helper.Data V2.0
// 2005-4-10 15:58:19

using System;
using System.Collections;
using System.Data;
using CN.Teddy.Helper.Data;
using CN.Teddy.Helper.DataAccess;
using CN.Teddy.Helper.Logic;
using NPetshop.ValueObject;
using NPetshop.DataAccess;

namespace NPetshop.TransactionLogic
{
    
public sealed class AccountLogic
    
{
        
private AccountLogic()
        
{
        }


        
public static void CreateAccountInfo(AccountInfo obj)
        
{
//            IDbTransaction tran = Configuration.BeginTransaction();
            try
            
{
//                If need transaction, uncomment these commented lines, and add other operations here
//                AccountAccess.InsertAccountInfo(obj, tran);
                
                AccountAccess.InsertAccountInfo(obj);

//                tran.Commit();
            }

            
catch(Exception e)
            
{
//                tran.Rollback();
                throw e;
            }

//            finally
//            {
//                if (tran.Connection != null && tran.Connection.State == ConnectionState.Open)
//                {
//                    tran.Connection.Close();
//                }
//            }
        }


        
public static void UpdateAccountInfo(AccountInfo obj)
        
{
//            IDbTransaction tran = Configuration.BeginTransaction();
            try
            
{
//                If need transaction, uncomment these commented lines, and add other operations here
//                AccountAccess.UpdateAccountInfo(obj, new Condition("ID", "ID", OP.Equals, obj.ID), tran);

                AccountAccess.UpdateAccountInfo(obj, 
new Condition("ID""ID", OP.Equals, obj.ID));

//                tran.Commit();
            }

            
catch(Exception e)
            
{
//                tran.Rollback();
                throw e;
            }

//            finally
//            {
//                if (tran.Connection != null && tran.Connection.State == ConnectionState.Open)
//                {
//                    tran.Connection.Close();
//                }
//            }
        }


        
public static void UpdateAccountInfo(string[] updateColumns, object[] updateValues, Condition condition)
        
{
//            IDbTransaction tran = Configuration.BeginTransaction();
            try
            
{
//                If need transaction, uncomment these commented lines, and add other operations here
//                AccountAccess.UpdateAccountInfo(updateColumns, updateValues, condition, tran);

                AccountAccess.UpdateAccountInfo(updateColumns, updateValues, condition);

//                tran.Commit();
            }

            
catch(Exception e)
            
{
//                tran.Rollback();
                throw e;
            }

//            finally
//            {
//                if (tran.Connection != null && tran.Connection.State == ConnectionState.Open)
//                {
//                    tran.Connection.Close();
//                }
//            }
        }


        
public static void UpdateAccountInfo(AccountInfo obj, Condition condition)
        
{
//            IDbTransaction tran = Configuration.BeginTransaction();
            try
            
{
//                If need transaction, uncomment these commented lines, and add other operations here
//                AccountAccess.UpdateAccountInfo(obj, condition, tran);

                AccountAccess.UpdateAccountInfo(obj, condition);

//                tran.Commit();
            }

            
catch(Exception e)
            
{
//                tran.Rollback();
                throw e;
            }

//            finally
//            {
//                if (tran.Connection != null && tran.Connection.State == ConnectionState.Open)
//                {
//                    tran.Connection.Close();
//                }
//            }
        }


        
public static void DeleteAccountInfo(Condition condition)
        
{
//            IDbTransaction tran = Configuration.BeginTransaction();
            try
            
{
//                If need transaction, uncomment these commented lines, and add other operations here
//                AccountAccess.DeleteAccountInfo(condition, tran);

                AccountAccess.DeleteAccountInfo(condition);

//                tran.Commit();
            }

            
catch(Exception e)
            
{
//                tran.Rollback();
                throw e;
            }

//            finally
//            {
//                if (tran.Connection != null && tran.Connection.State == ConnectionState.Open)
//                {
//                    tran.Connection.Close();
//                }
//            }
        }


        
public static void DeleteAccountInfo(int id)
        
{
//            IDbTransaction tran = Configuration.BeginTransaction();
            try
            
{
//                If need transaction, uncomment these commented lines, and add other operations here
//                AccountAccess.DeleteAccountInfo(new Condition("ID", "ID", OP.Equals, id), tran);

                AccountAccess.DeleteAccountInfo(
new Condition("ID""ID", OP.Equals, id));

//                tran.Commit();
            }

            
catch(Exception e)
            
{
//                tran.Rollback();
                throw e;
            }

//            finally
//            {
//                if (tran.Connection != null && tran.Connection.State == ConnectionState.Open)
//                {
//                    tran.Connection.Close();
//                }
//            }
        }


        
public static AccountInfo GetAccountInfo(int id)
        
{
//            IDbTransaction tran = Configuration.BeginTransaction();
            try
            
{
//                If need transaction, uncomment these commented lines, and add other operations here
//                return AccountAccess.SelectAccountInfo(id, tran);

                
return AccountAccess.SelectAccountInfo(id);

//                tran.Commit();
            }

            
catch(Exception e)
            
{
//                tran.Rollback();
                throw e;
            }

//            finally
//            {
//                if (tran.Connection != null && tran.Connection.State == ConnectionState.Open)
//                {
//                    tran.Connection.Close();
//                }
//            }
        }


        
public static IList GetAccountInfo(Condition condition, OrderList orderList)
        
{
//            IDbTransaction tran = Configuration.BeginTransaction();
            try
            
{
//                If need transaction, uncomment these commented lines, and add other operations here
//                return AccountAccess.SelectAccountInfo(condition, orderList, tran);

                
return AccountAccess.SelectAccountInfo(condition, orderList);

//                tran.Commit();
            }

            
catch(Exception e)
            
{
//                tran.Rollback();
                throw e;
            }

//            finally
//            {
//                if (tran.Connection != null && tran.Connection.State == ConnectionState.Open)
//                {
//                    tran.Connection.Close();
//                }
//            }
        }


        
public static int[] GetAccountInfoIDs(Condition condition)
        
{
//            IDbTransaction tran = Configuration.BeginTransaction();
            try
            
{
//                If need transaction, uncomment these commented lines, and add other operations here
//                return AccountAccess.SelectAccountInfoIDs(condition, tran);

                
return AccountAccess.SelectAccountInfoIDs(condition);

//                tran.Commit();
            }

            
catch(Exception e)
            
{
//                tran.Rollback();
                throw e;
            }

//            finally
//            {
//                if (tran.Connection != null && tran.Connection.State == ConnectionState.Open)
//                {
//                    tran.Connection.Close();
//                }
//            }
        }


        
public static PageAdapter GetAccountInfoPageAdapter(Condition condition, OrderList orderList)
        
{
            
try
            
{
                
return new PageAdapter(AccountAccess.SelectAccountInfoPageSplit(condition, orderList), new CreateObjectListHandler(AccountInfo.CreateObjectList));
            }

            
catch(Exception e)
            
{
                
throw e;
            }

        }


        
    }

}


以上后者是CodeGen默认生成的代码,如果要加入事务保护,则需要用户取消注释代码,并加入需要的代码;而前者是需要手工写的,不过观察其代码内容,除了事务相关的信息还是可能从xml映射文件生成的,也就是说,如果前者也采用工具来辅助生成代码的话,两种方式的工作量都是只要为事务写很小一部分代码,工作量还是基本相当。

以上两者的逻辑层调用的各自的数据访问层的代码对调用者都是透明的,不过观察后者的代码中的Condition类可以发现,后者对复杂查询的支持能够做到更灵活,如果允许表现层调用者构造并传入Condition实例的话,可以支持不可预知的条件的查询,而orm似乎不是很容易做到,这是CodeGen相较于SqlMap的优势。

3、第三点就不用列出代码了,orm不论是用xml还是自定义Attribute来描述映射关系,都不得不用反射,即使加入充分的缓存,总有必然的性能损失,而CodeGen可以做到完全不使用反射,性能有保证。

综上所述,使用代码生成,不论在哪个层面,在性能和编程人员的工作量和扩充性灵活性来比较,都比orm只好不差!

所以,orm很好,如果项目适合,那么用吧,但是没有必要迷信它~~
posted @ 2005-04-10 16:16  Teddy's Knowledge Base  Views(2940)  Comments(15Edit  收藏  举报