SHARE & TOP

我会变成童话里,你爱的那个天使

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

虽然说我们可以使用Hibernate等OR Mapping工具来实现数据访问,但我一直习惯于写SQL语句来访问数据库。我经常采用的结构是这样的:针对每个数据表实现一个对应的值对象,然后将SQL语句封装在存储过程中,实现一个Persister类调用这些存储过程。客户端就可以使用Persister来操作数据库,值对象作为数据载体。

我一直觉得这样的结构实现起来比较简单,而且执行效率也应该比OR Mapping来的高一些。在大多数的应用中,这样的结构已够用了。但是,这种实现方法最大的问题在于代码量比较大,而且是重复(类似)的代码非常多。

于是,我想到用CodeDOM来实现代码的自动生成应该是可行的。感谢博客园的lichdr的一系列文章,让我很快地实现这一功能。

首先,介绍一下VO的实现,就是从数据库表生成相应的VO类,步骤其实很简单。

我的考虑是,虽然是一个O-R的过程,但我不喜欢映射文件,所以,只能从命名约定来做文章了。我是这样为表命名的,"t_类名",例如"t_Business"表示业务表,字段命名也很规范,如"f_SysID"表示系统ID字段,"f_LoginName"表示登录名字段。

有了这样的严格的命名约定,做映射就非常方便了。三步完成:

1.先查询出库中有多少张以"t_"开头的表,一句话:

select name from sysobjects where substring(name,1,2)='t_'

2.对每张表,查询出它有哪些字段,同样一句话:

select t1.name,t3.name,t1.isnullable from syscolumns t1 join systypes t3 on t1.xusertype=t3.xusertype oin sysobjects t4 on 
t1.id
=t4.id where t4.name=@tname


 这里得到的分别是"字段名","类型","是否可为空"。

3.然后就是创建类、成员变量和属性了:

 1//cname生成的类名(表名去掉t_字符后加上Info) dbcolumn 字段列表
 2        private void generateVOCode(string cname,ArrayList dbcolumn)
 3        {
 4            CSharpCodeProvider provider = new CSharpCodeProvider();
 5            ICodeGenerator generator = provider.CreateGenerator();
 6
 7            //conf读配置文件得到输出目录
 8            StreamWriter writer = new StreamWriter(conf.getOutputDirectory()+@"\info\"+cname+".cs",false);
 9                
10            CodeCompileUnit unit = new CodeCompileUnit();
11            CodeNamespace nspace = new CodeNamespace(conf.getNamespace()+".info");
12            nspace.Imports.Add(new CodeNamespaceImport("System"));
13                                    
14            CodeTypeDeclaration info = new CodeTypeDeclaration(cname);
15            
16            foreach(string[] item in dbcolumn)
17            {
18                //私有成员变量,直接以字段名(包括f_)作变量名
19                CodeMemberField f = new CodeMemberField();
20                f.Name=item[0];
21                f.Type=new CodeTypeReference(mag.getTypeString(item[1]));
22                info.Members.Add(f);
23                //公共属性,以字段名(去掉f_)作属性名
24                CodeMemberProperty p = new CodeMemberProperty();
25                p.Attributes=MemberAttributes.Public;
26                p.Name=item[0].Substring(2);
27                p.Type=f.Type;
28                p.GetStatements.Add(new CodeMethodReturnStatement(
29                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(),f.Name)
30                    ));
31                p.SetStatements.Add(new CodeAssignStatement(
32                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(),f.Name),
33                    new CodePropertySetValueReferenceExpression()
34                    ));
35                info.Members.Add(p);
36
37            }

38            nspace.Types.Add(info);
39            unit.Namespaces.Add(nspace);                
40
41            generator.GenerateCodeFromCompileUnit(unit,writer,new CodeGeneratorOptions());
42            writer.Close();
43        }

4.就OK了。

实现VO的确是非常简单,如果要自动生成Persister的代码就稍复杂一点了。

在Persister类中,我们采用了一个Sington模式(私有构造函数+静态getInstance方法),然后方法与存储过程对应。同样,我们也不用映射文件而是采用约定存储过程命名的方式。例如"p_Business_find"表示业务表的一个查询单条记录过程,这个名称被处理成"p_"表示存储过程,Business加上Persister构成类名"BusinessPersister",find即为方法名。

1.同样,查询一下有多少个存储过程类,就是切出中间的类名,因为"p_Business_find"与"p_Business_list"都会放成BusinessPersister类中,所以只要Business这一段:

select distinct substring(substring(name,3,len(name)-2),1,charindex('_',substring(name,3,len(name)-2))-1from sysobjects where substring(name,1,2)='p_'

2.对于每个类(例如BusinessPersister),利用CodeDOM生成代码,先把几个基本的方法实现掉:

private void generatePOCode(string cname,ArrayList procs)
        
{
            CSharpCodeProvider provider 
= new CSharpCodeProvider();
            ICodeGenerator generator 
= provider.CreateGenerator();

            StreamWriter writer 
= new StreamWriter(conf.getOutputDirectory()+@"\"+cname+".cs",false);
                
            CodeCompileUnit unit 
= new CodeCompileUnit();
            CodeNamespace nspace 
= new CodeNamespace(conf.getNamespace());

            
//导入多个包
            nspace.Imports.Add(new CodeNamespaceImport("System"));
            nspace.Imports.Add(
new CodeNamespaceImport("System.Collections"));
            nspace.Imports.Add(
new CodeNamespaceImport("System.Data"));
            nspace.Imports.Add(
new CodeNamespaceImport("System.Data.SqlClient"));
            nspace.Imports.Add(
new CodeNamespaceImport("Microsoft.ApplicationBlocks.Data21"));
            nspace.Imports.Add(
new CodeNamespaceImport(conf.getNamespace()+".info"));
                                    
            CodeTypeDeclaration po 
= new CodeTypeDeclaration(cname);
            
            
//创建字段conStr,连接字串
            CodeMemberField constr = new CodeMemberField("System.String","conStr");
            constr.Attributes
=MemberAttributes.Private|MemberAttributes.Final;
            po.Members.Add(constr);
            
            
//静态字段,aInstance,实例自己
            CodeMemberField ainst = new CodeMemberField(cname,"aInstance");
            ainst.Attributes
=MemberAttributes.Private|MemberAttributes.Static;
            ainst.InitExpression
=new CodePrimitiveExpression(null);
            po.Members.Add(ainst);

            
//构造函数(私有化)
            CodeConstructor ct = new CodeConstructor();
            ct.Attributes
=MemberAttributes.Private;
            ct.Statements.Add(
new CodeSnippetStatement("this.conStr=System.Configuration.ConfigurationSettings.AppSettings[\"DBConnStr\"];")); //限定
            ct.Statements.Add(new CodeSnippetStatement("if( this.conStr==null || this.conStr.Length==0) throw new Exception();"));            //限定

            po.Members.Add(ct);

            
//静态方法,getInstance,用来创建实例
            CodeMemberMethod getinst = new CodeMemberMethod();
            getinst.Name
="getInstance";
            getinst.ReturnType
=new CodeTypeReference(cname);
            getinst.Attributes
=MemberAttributes.Public|MemberAttributes.Static;
            getinst.Statements.Add(
                
new CodeConditionStatement(new CodeSnippetExpression("aInstance==null"
                ,
new CodeStatement[]{new CodeAssignStatement(new CodeVariableReferenceExpression("aInstance"),new CodeObjectCreateExpression(cname,new CodeExpression[]{}))}
                ,
new CodeStatement[]{}
                )
            );
            getinst.Statements.Add(
new CodeMethodReturnStatement(new CodeVariableReferenceExpression("aInstance")));
            po.Members.Add(getinst);
            
            
//私有方法 setParameter(从VO向object[]中填值)
            string votype = procs[0].ToString().Split(new char[]{'_'},3)[1]+"Info";
            ArrayList cols 
= mag.getTableColumn("t_"+procs[0].ToString().Split(new char[]{'_'},3)[1]);

            CodeMemberMethod setp 
= new CodeMemberMethod();
            setp.Name
="setParameter";
            setp.Attributes
=MemberAttributes.Private|MemberAttributes.Final;
            setp.ReturnType
=new CodeTypeReference("System.Object[]");
            setp.Parameters.Add(
new CodeParameterDeclarationExpression(votype,"op"));
            setp.Statements.Add(
new CodeVariableDeclarationStatement("System.Object[]","objs",new CodeSnippetExpression("new object["+cols.Count+"]")));
            
for(int i=0;i<cols.Count;i++)
            
{
                
string[] col = (string[])cols[i];
                setp.Statements.Add(
new CodeSnippetStatement("objs["+i+"]=op."+col[0].ToString().Substring(2)+";"));
            }

            setp.Statements.Add(
new CodeMethodReturnStatement(new CodeVariableReferenceExpression("objs")));
            po.Members.Add(setp);
            
            
//私有方法 getRecord(从reader中读数据到VO中)
            CodeMemberMethod getr = new CodeMemberMethod();
            getr.Name
="getRecord";
            getr.Attributes
=MemberAttributes.Private|MemberAttributes.Final;
            getr.ReturnType
=new CodeTypeReference(votype);
            getr.Parameters.Add(
new CodeParameterDeclarationExpression("SqlDataReader","reader"));
            getr.Statements.Add(
new CodeVariableDeclarationStatement(votype,"op",new CodeObjectCreateExpression(votype,new CodeExpression[]{})));
            
for(int i=0;i<cols.Count;i++)
            
{
                
string[] col = (string[])cols[i];
                getr.Statements.Add(
new CodeSnippetStatement("op."+col[0].ToString().Substring(2)+"="+getReadString(col[1],(col[2]=="1"),i)+";"));
            }

            getr.Statements.Add(
new CodeMethodReturnStatement(new CodeVariableReferenceExpression("op")));


            po.Members.Add(getr);

            
//多个公共方法,用于具体工作.(每个存储过程对应一个方法)
            foreach(string item in procs)
            
{
                CodeMemberMethod method 
= generatePOMethod(item);
                po.Members.Add(method);
            }


            nspace.Types.Add(po);
            unit.Namespaces.Add(nspace);                

            generator.GenerateCodeFromCompileUnit(unit,writer,
new CodeGeneratorOptions());
            writer.Close();
        }

3.再查询一下这个类包括几个存储过程,也就是说它有几个方法:

select name from sysobjects where substring(name,1,"+len.ToString()+")='p_"+cname+"_'

4.针对每个方法,生成代码,加到类里面:

private CodeMemberMethod generatePOMethod(string proc)
{
    CodeMemberMethod method 
= new CodeMemberMethod();
    method.Name
=proc.Split(new char[]{'_'},3)[2];
    method.Attributes
=MemberAttributes.Public|MemberAttributes.Final;
            
    
//对应的VO类名
    string votype = proc.Split(new char[]{'_'},3)[1]+"Info";

    ArrayList pars 
= mag.getProcParameters(proc);

    
if( method.Name.StartsWith("list") ) //list开头的方法
    {
        method.Statements.Add(
            
new CodeVariableDeclarationStatement("System.Object[]","objs"
                ,
new CodeSnippetExpression("new object["+pars.Count+"]"))
            );
            
int i=0;
            
foreach(string[] par in pars)
            
{
                method.Parameters.Add(
                    
new CodeParameterDeclarationExpression(mag.getTypeString(par[2])
                        ,par[
1].Substring(1))
                );
                method.Statements.Add(
                    
new CodeSnippetStatement("objs["+(i++)+"]="+par[1].Substring(1)+";")
                );
            }

            
if( method.Name.IndexOf("PageCount")>0 ) //listPageCount的方法
            {
                method.ReturnType
=new CodeTypeReference("System.Int32");
                
            }

            
else  
            
{
                method.ReturnType
=new CodeTypeReference("ArrayList");
                
            }

    
    }

    
else if( method.Name.StartsWith("find") )  //find开头的方法
    {
        method.ReturnType
=new CodeTypeReference(votype);
        
    }

    
else 
    
{
        method.ReturnType
=new CodeTypeReference("System.Void");
        
if( method.Name.StartsWith("create"|| method.Name.StartsWith("update") ) //create and update 一样的处理
        {
        }

        
else if( method.Name.StartsWith("delete") )
        
{
        }

        
else
        
{
        }

    }
        
    
return method;
}

在这里,还是依靠名称的严格约定,find表示只查一记录,list表示列出多条记录,create创建新记录,delete删除一条记录,update更新记录...并且,严格约束存储过程的参数,例如create与update的参数依顺序与表字段一样.delete和find则只接收一个SysID主键作为参数.在这样严格的命名约束下,要做的代码与就非常容易了.(我的确是很懒,不想考虑太多的灵活性).

当然,存储过程的参数相对还是比较容易解决,因为可以这样得到参数列表:

select t1.colid,t1.name,t2.name from syscolumns t1 join systypes t2 on t1.xusertype=t2.xusertype join sysobjects t3 on t3.id=t1.id where t3.name='@pname'

但是存储过程的返回值就不是那么好折腾了.一开始我想到用DataSet来处理(因为它可以得到字段的元数据),但是发现不妥,要得到DataSet就需要先执行一下这个SP,但那些需要参数的SP呢?先给个假的数据吧,反正又不是真的要结果,只要结果集的元数据,但是&%##)@...这假数据还真不好给,null?0?呵呵,反正麻烦...而且,象这个存储过程就瞎了,根本没有返回我要的元...

/*
 * 根据业务类型列表
 
*/

CREATE PROCEDURE [dbo].[p_Business_listPageByType]
(
 
@BizType bigint,@start int,@size int
)
AS
declare @str nvarchar(1000)
set @str=  'SELECT f_SysID,f_LoginID,f_CompanyName,f_CompanyCode,f_BizType,f_BizTyepDesc,f_Status,f_Review,f_OpDate FROM 
    (SELECT TOP 
' + cast@size as nvarchar(8) ) +'  *  FROM (SELECT TOP ' + cast( (@start+@sizeas nvarchar(8) )
        
+'  *  WHERE f_BizType='+ cast(@BizType as nvarchar(8)) +'  FROM t_Business ORDER BY f_OpDate DESC) t1  ORDER BY f_OpDate) t2
    ORDER BY f_OpDate DESC
'
execute sp_executesql  @str

GO

所以,只能想一个笨办法了,约定存储过程的返回值(呵呵...大家看我的这篇文章,是不是发现出现最多的词就是约定,约束.)...对于find/list等SP返回列的顺序与表一样....这样又解决这个问题了....懒人自有笨办法...

OK...在严格的约束下,这个自动生成访问数据库代码的工具就算完工了.

其实我觉得,就算有这么多的约束,但这些约束本身就是我平时的编码习惯,所以,也不能算是太过苛刻.就算这个工具只给自己用,也能节省不少时间啊...

 

posted on 2005-08-25 10:44  Android@SHARETOP  阅读(965)  评论(0)    收藏  举报