自己编写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);
}

 

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

作者: 峻祁连
邮箱:junqilian@163.com 
出处: http://junqilian.cnblogs.com 
转载请保留此信息。



本文转自峻祁连. Moving to Cloud/Mobile博客园博客,原文链接:http://www.cnblogs.com/junqilian/archive/2008/02/29/1086652.html ,如需转载请自行联系原作者
相关文章
|
2月前
|
算法 JavaScript 前端开发
Fitten Code:自动生成代码注释工具
【9月更文挑战第02天】
306 7
|
6月前
|
SQL 存储 前端开发
Django框架ORM操作(一)
Django框架ORM操作(一)
Django框架ORM操作(一)
|
6月前
|
Oracle 关系型数据库 MySQL
Django框架ORM操作(二)
Django框架ORM操作(二)
|
6月前
|
C++
win32编程 -- 通过空项目学习自动生成的代码框架
win32编程 -- 通过空项目学习自动生成的代码框架
38 0
|
6月前
|
IDE 开发工具 C语言
QT案例IDE编写 -- 代码编写
QT案例IDE编写 -- 代码编写
69 0
|
6月前
|
IDE 开发工具 C语言
QT案例IDE编写 -- 编译操作
QT案例IDE编写 -- 编译操作
50 0
|
6月前
|
编解码 IDE 开发工具
QT案例IDE编写 -- 通过枚举实现编码切换
QT案例IDE编写 -- 通过枚举实现编码切换
54 0
|
6月前
|
Oracle Java 关系型数据库
Generator【SpringBoot集成】代码生成+knife4j接口文档(2种模板设置、逻辑删除、字段填充 含代码粘贴可用)保姆级教程(注意事项+建表SQL+代码生成类封装+测试类)
Generator【SpringBoot集成】代码生成+knife4j接口文档(2种模板设置、逻辑删除、字段填充 含代码粘贴可用)保姆级教程(注意事项+建表SQL+代码生成类封装+测试类)
103 0
|
缓存 前端开发 JavaScript
使用 webdriver API 编写自动化脚本的基本语法
1. 打开和关闭浏览器 1)打开浏览器并访问 URL 2)关闭浏览器窗口 2. 元素的定位 1)以 id 定位元素 2)以 name 定位元素 3)以 tag name 定位元素 4)以 class name 定位元素 5)以 xpath 定位元素 6)以 css selector 定位元素 7)以 link text 定位元素 8)以 partial link text 定位元素 3. 操作测试元素 1)键盘输入与鼠标点击 2)submit 提交表单 3)获取元素内容 4. 添加等待 1)sleep 休眠 2)智能等待 5. 打印网页 title 和 URL 6. 浏览器操作 1)设置浏览器
176 0
|
JSON 数据格式
如果编写 if 时不带 else
如果编写 if 时不带 else
104 0
如果编写 if 时不带 else