自己编写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);
}
嗯,第一次写技术博客,写了这么多,不容易啊,以后正确多研究些,多记录些。刚开始研究,有什么不对的地方,请各位大牛批评指正。