自己编写BuildProvider来实现ORM以及BuildProvider的调试

 

   最近在看《asp.net2.0高级编程》,看到了自定义BuildProvider的编写方法,学习期间也遇到了一些问题,发现网络上这方面的东西还不多,我把我学习过程中的东西记录下来,贴在这里备查。
   build提供程序是一个可以插入ASP.NET编译系统,为某些文件类型提供自定义编译支持的组件。通过在编译时解析源文件内容,build提供程序可以自动生成一个合适的代理类。build提供程序生成可编译的代码,并使它与源文件保持同步。当源文件发生变化时,build提供程序再次执行,并更新一切。

      比如Asp.net内置的一些BuildProvider,如:
System.Web.Compilation.PageBuildProvider
System.Web.Compilation.UserControlBuildProvider
System.Web.Compilation.MasterPageBuildProvider
....

   这里为了实现数据库表到类的自动转换,需要编写一个自定义的OrmBuildProvider,我们在web.config中配置好OrmBuildProvider,在App_Code中增加一个.map文件后,VS.net会自动帮我们把数据库表转换成类:
<compilation debug="true">
            <!--自定义的ORM provider-->
            <buildProviders>
                <add extension=".map" type="OrmBuildProviderSln.OrmBuildProvider"/>
            </buildProviders>
</compilation>

我们的目标,开发一个自定义的OrmBuildProvider,实现数据库表到类的自动转换,并且要同时支持SQL Server和Oracle数据库。

我们准备用一个xml文件来定义.map文件的结构:
<?xml version="1.0" encoding="utf-8" ?>

<Mappings namespace="OrmBuildProviderSln">
    <!--databaseType = "Sql|Oracle"-->
  <Mapping databaseType="Sql"
           connectionString  ="Data Source=localhost\sqlexpress;Initial Catalog=MsPetShop4;Integrated Security=SSPI;Timeout=5;Application Name=OrmBuildProviderWeb"
           tableName ="Product"
           className ="Product"
           selectCommand ="SELECT [ProductId] as [ProductId]
                                             ,[CategoryId] as CategoryId
                                             ,[Name] as Name
                                             ,[Descn] as Descn
                                             ,[Image] as Image
                                   FROM [MSPetShop4].[dbo].[Product]"
           allowPartialClass="true"
           allowCollectionClass="true"
           collectionClassName="ProductCollection">
  </Mapping>

    <Mapping databaseType="Oracle"
       connectionString ="Data Source=ORA252;User Id=trig;Password=trig;"
       tableName ="Customer"
       className ="Customer"
       selectCommand ="select customercode, regielicenceno, customername, address, phone, booker, zhuanmailx, businessform, organizemode, jingyingfs, issuedate, disable, issuedept, specdelivery from tg_customer"
       allowPartialClass="true"
       allowCollectionClass="true"
       collectionClassName="CustomerCollection">
    </Mapping>
</Mappings>

我们新建一个类库项目,命名为OrmBuildProvider,新添加一个类OrmBuildProvider,这个类需要集成BuildProvider,并冲写GenerateCode方法。

public class OrmBuildProvider : BuildProvider
   {

     public override void GenerateCode(AssemblyBuilder assemblyBuilder)
     {
     }

}

实现GenerateCode有两种方法可以选择,CodeDOM API或者TextWriter直接输出。使用CodeDOM需要了解一大堆的API,但生成的代码可以适用于VB,C#;使用TextWriter的方法比较直接,但只能生成针对某种语言的代码。《aspnet 2.0高级编程》上给出了使用CodeDOM API的例子,峻祁连实现了使用TextWriter的代码,并进行了扩展,使得OrmBuildProvider同时支持Sql Server和Oracle两种数据库。

  public override void GenerateCode(AssemblyBuilder assemblyBuilder)
{
     ///////////Using CodeDOM API
     //// 略,具体代码可以参考http://www.yuedu.cc/chapter/10482188/

     //////////////////////////Using TextWriter.Only for C#   by 杜长宇
     TextWriter writer = assemblyBuilder.CreateCodeFile(this);
     if (writer == null)
     {
        return;
     }
     try
     {
        String code = ParseFileAndCreateCode(base.VirtualPath);
        writer.Write(code);
      }
    finally
    {
        writer.Close();
    }
}

 

        private string ParseFileAndCreateCode(string fileName)
        {
            OrmDescriptor desc = ExtractInfo(fileName);
            //OrmDescriptor desc = new OrmDescriptor();
            StringBuilder code = new StringBuilder();
            // add some file header
            code.AppendLine("/////////////////////////////////////////////////////////");
            code.AppendLine("//  This file generated automatically. DO NOT change anything.");
            code.AppendLine("//  Because your change maybe dispeared when the application restarted.");
            code.AppendLine("//  Generated by OrmBuildProvider " + System.DateTime.Now.ToString());
            code.AppendLine("//  Implemented by Duchangyu     changyudu@163.com");
            code.AppendLine("////////////////////////////////////////////////////////");

            code.AppendLine("using System.Collections.Generic;");
            code.AppendLine();
            code.AppendLine("namespace " + desc.NameSpace);
            code.AppendLine("{");
            //add a class present the table
            for (int i = 0; i < desc.Descriptors.Length; i++)
            {
                OrmTableDescriptor t = desc.Descriptors[i];

                //add a comment here
                code.AppendLine("///----------"+ t.ClassName +"   Class-----------");

                code.AppendLine("   public class " + t.ClassName);
                code.AppendLine("   {");

                //add the properties present the table columns
                DataAdapter adapter = CreateDataAdapter(t.DatabaseType, t.SelectCommand, t.ConnectionString);

                DataSet ds = new DataSet();
                adapter.FillSchema(ds, SchemaType.Mapped);
                 DataTable dt = ds.Tables[0];
                for (int j = 0; j < dt.Columns.Count; j++)
                {
                    DataColumn column = dt.Columns[j];
                    string colName = column.ColumnName;
                    Type colType = column.DataType;
                    string filedName = "_" + colName.ToLower();

                    code.AppendLine("     private " + colType.ToString() + " " + filedName + ";" );
                    code.AppendLine("     public " + colType.ToString() + " " + colName );
                    code.AppendLine("     {");
                    code.AppendLine("         set{ " + filedName + " = value;}");
                    code.AppendLine("         get{ return " + filedName + ";}");
                    code.AppendLine("     }");
                    code.AppendLine();
                }

                code.AppendLine("   }");
                code.AppendLine();

                //add the collectionClass--generic
                if (t.AllowCollectionClass)
                {
                    code.AppendLine("  public class " + t.CollectionClassName + ": List<" + t.ClassName + "> ");
                    code.AppendLine("  { }");
                    code.AppendLine();
                }
            }

            code.AppendLine("}");

            return code.ToString();
        }

 

 

public OrmDescriptor ExtractInfo(string fileName)
{
    //load the *.map document
    XmlDocument doc = new XmlDocument();
    using (Stream stream = VirtualPathProvider.OpenFile(fileName))
    {
        doc.Load(stream);
    }

    //get the namespace information
    XmlNode root = doc.DocumentElement;
    string ns = root.Attributes["namespace"].Value;

    //visite the <mapping nodes>
    XmlNodeList mappings = doc.SelectNodes("Mappings/Mapping");
    OrmTableDescriptor[] descriptors = new OrmTableDescriptor[mappings.Count]; //allocate resource;
    //List<OrmTableDescriptor> descriptors = new List<OrmTableDescriptor>(mappings.Count);
    for (int i = 0; i < mappings.Count; i++)
    {
        XmlNode mapping = mappings[i];

        OrmTableDescriptor desc = new OrmTableDescriptor();
        desc.ConnectionString = mapping.Attributes["connectionString"].Value;
        desc.TableName = mapping.Attributes["tableName"].Value;
        desc.ClassName = mapping.Attributes["className"].Value;
        desc.SelectCommand = mapping.Attributes["selectCommand"].Value;
        bool allowPartialClass;
        bool.TryParse(mapping.Attributes["allowPartialClass"].Value, out allowPartialClass);
        desc.AllowPartialClass = allowPartialClass;
        bool allowCollection = false;
        Boolean.TryParse(mapping.Attributes["allowCollectionClass"].Value, out allowCollection);
        desc.AllowCollectionClass = allowCollection;
        if (allowCollection)
        {
            desc.CollectionClassName = mapping.Attributes["collectionClassName"].Value;
        }
        desc.DatabaseType = mapping.Attributes["databaseType"].Value;

        descriptors[i] = desc;
        //descriptors.Add(desc);
    }

    //pack all info and return
    OrmDescriptor ormDescriptor = new OrmDescriptor();
    ormDescriptor.NameSpace = ns;
    ormDescriptor.Descriptors = descriptors;
    return ormDescriptor;

}

 

这里引入了类OrmDescriptor和OrmTableDescriptor

using System;
using System.Collections.Generic;
using System.Text;

namespace OrmBuildProviderSln
{
    public class OrmDescriptor
    {
        private string nameSpace;

        public string NameSpace
        {
            get { return nameSpace; }
            set { nameSpace = value; }
        }
        private OrmTableDescriptor[] descriptors;

        public OrmTableDescriptor[] Descriptors
        {
            get { return descriptors; }
            set { descriptors = value; }
        }

        //private List<OrmTableDescriptor> descriptors;

        //internal List<OrmTableDescriptor> Descriptors
        //{
        //    get { return descriptors; }
        //    set { descriptors = value; }
        //}

    }
}

 

 

using System;
using System.Collections.Generic;
using System.Text;

namespace OrmBuildProviderSln
{
    public class OrmTableDescriptor
    {
        private string connectionString;

        public string ConnectionString
        {
            get { return connectionString; }
            set { connectionString = value; }
        }

        private string tableName;

        public string TableName
        {
            get { return tableName; }
            set { tableName = value; }
        }

        private string className;

        public string ClassName
        {
            get { return className; }
            set { className = value; }
        }

        private string selectCommand;

        public string SelectCommand
        {
            get { return selectCommand; }
            set { selectCommand = value; }
        }

        private bool allowPartialClass;

        public bool AllowPartialClass
        {
            get { return allowPartialClass; }
            set { allowPartialClass = value; }
        }

        private bool allowCollectionClass;

        public bool AllowCollectionClass
        {
            get { return allowCollectionClass; }
            set { allowCollectionClass = value; }
        }

        private string collectionClassName;

        public string CollectionClassName
        {
            get { return collectionClassName; }
            set { collectionClassName = value; }
        }

        private string databaseType;

        public string DatabaseType
        {
            get { return databaseType; }
            set { databaseType = value; }
        }
    }
}

 

这样,当我们在App_Code中加入orm.map文件时,Asp.net就会监测到,自动在aspnet的临时目录下生成一个对应的类。

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\ormbuildproviderweb\be94c8e8\9adb5db3\Sources_App_Code\orm.map.72cecc2a.cs

/////////////////////////////////////////////////////////
//  This file generated automatically. DO NOT change anything.
//  Because your change maybe dispeared when the application restarted.
//  Generated by OrmBuildProvider 2008-02-29 21:25:03
//  Implemented by Duchangyu     changyudu@163.com
////////////////////////////////////////////////////////
using System.Collections.Generic;

namespace OrmBuildProviderSln
{
///----------Product   Class-----------
   public class Product
   {
     private System.String _productid;
     public System.String ProductId
     {
         set{ _productid = value;}
         get{ return _productid;}
     }

     private System.String _categoryid;
     public System.String CategoryId
     {
         set{ _categoryid = value;}
         get{ return _categoryid;}
     }

     private System.String _name;
     public System.String Name
     {
         set{ _name = value;}
         get{ return _name;}
     }

     private System.String _descn;
     public System.String Descn
     {
         set{ _descn = value;}
         get{ return _descn;}
     }

     private System.String _image;
     public System.String Image
     {
         set{ _image = value;}
         get{ return _image;}
     }

   }

  public class ProductCollection: List<Product>
  { }

///----------Category   Class-----------
   public class Category
   {
     private System.String _categoryid;
     public System.String CategoryId
     {
         set{ _categoryid = value;}
         get{ return _categoryid;}
     }

     private System.String _name;
     public System.String Name
     {
         set{ _name = value;}
         get{ return _name;}
     }

     private System.String _descn;
     public System.String Descn
     {
         set{ _descn = value;}
         get{ return _descn;}
     }

   }

  public class CategoryCollection: List<Category>
  { }

///----------Customer   Class-----------
   public class Customer
   {
     private System.String _customercode;
     public System.String CUSTOMERCODE
     {
         set{ _customercode = value;}
         get{ return _customercode;}
     }

     private System.String _regielicenceno;
     public System.String REGIELICENCENO
     {
         set{ _regielicenceno = value;}
         get{ return _regielicenceno;}
     }

     private System.String _customername;
     public System.String CUSTOMERNAME
     {
         set{ _customername = value;}
         get{ return _customername;}
     }

     private System.String _address;
     public System.String ADDRESS
     {
         set{ _address = value;}
         get{ return _address;}
     }

     private System.String _phone;
     public System.String PHONE
     {
         set{ _phone = value;}
         get{ return _phone;}
     }

     private System.String _booker;
     public System.String BOOKER
     {
         set{ _booker = value;}
         get{ return _booker;}
     }

     private System.String _zhuanmailx;
     public System.String ZHUANMAILX
     {
         set{ _zhuanmailx = value;}
         get{ return _zhuanmailx;}
     }

     private System.String _businessform;
     public System.String BUSINESSFORM
     {
         set{ _businessform = value;}
         get{ return _businessform;}
     }

     private System.String _organizemode;
     public System.String ORGANIZEMODE
     {
         set{ _organizemode = value;}
         get{ return _organizemode;}
     }

     private System.String _jingyingfs;
     public System.String JINGYINGFS
     {
         set{ _jingyingfs = value;}
         get{ return _jingyingfs;}
     }

     private System.DateTime _issuedate;
     public System.DateTime ISSUEDATE
     {
         set{ _issuedate = value;}
         get{ return _issuedate;}
     }

     private System.String _disable;
     public System.String DISABLE
     {
         set{ _disable = value;}
         get{ return _disable;}
     }

     private System.String _issuedept;
     public System.String ISSUEDEPT
     {
         set{ _issuedept = value;}
         get{ return _issuedept;}
     }

     private System.String _specdelivery;
     public System.String SPECDELIVERY
     {
         set{ _specdelivery = value;}
         get{ return _specdelivery;}
     }

   }

  public class CustomerCollection: List<Customer>
  { }

}

这样我们在web项目中就可以利用VS2005中的智能提示功能,很方便的使用和数据库表对应的类Product,Category和Customer等,

protected void Page_Load(object sender, EventArgs e)
{
    OrmBuildProviderSln.Product p = new OrmBuildProviderSln.Product();
    p.Name = "product Name";
    p.ProductId = "2";

    OrmBuildProviderSln.ProductCollection pc = new OrmBuildProviderSln.ProductCollection();
    pc.Add(p);
}

下面说说BuildProvider的调试方法。

开发自定义的BuildProvide时,需要编写大量的代码,这就难免出错。调试的问题怎么解决么?我们在某个方法上设立断点,可代码的执行是aspnet自动调用的,vs2005中根本就不能捕获到这个断点。后来在网上查找到有人同时开两个VS2005,一个用于触发,一个用于调试,呵呵,这个到是有新意,不过太烦琐了http://devtalk.dk/2007/10/27/Expand+And+Debug+A+Custom+BuildProvider.aspx

后来转念一想,干嘛非得在BuildProvider类库的调试里折腾啊,我建一个web 站点程序,随便弄个页面,把BuildProvider当做普通的类来调试不就OK了么,下面是我调试ExtractInfo()方法。在最后一句设置断点,就可以跟踪到OrmBuildProvider 类里面了,呵呵。当然为了方便,把orm.map拷贝成一个orm.xml放在App_Code外面了,这样调试时好处理一些。

protected void Button1_Click(object sender, EventArgs e)
{
    OrmBuildProviderSln.OrmBuildProvider ormBuild = new OrmBuildProviderSln.OrmBuildProvider();
    string path = Request.ApplicationPath + "/orm.xml";
    ormBuild.ExtractInfo(path);
}

 

嗯,第一次写技术博客,写了这么多,不容易啊,以后正确多研究些,多记录些。刚开始研究,有什么不对的地方,请各位大牛批评指正。


Related Posts Plugin for WordPress, Blogger...