ORM框架-工具-产品开发之四 开发代码生成器 Template Studio Development (二)

ORM工具开发系列的代码生成工具的开发,接上回,继续来设计基于模板的代码生成器。

模板编辑器 Template Editor

编辑器的基本要求是,文件编辑(Copy,Cut,Paste,Find/Replace)功能,语法高亮显示,智能提示。

对于.NET系统的内置类型,可以预先加载,并提供智能提示功能。

image

如图所示,可以直接引用系统内置的类型,在编辑模板时,会自动调出智能提示窗口。

如果能做到自定义的变量可达到这种效果,给模板的编写带来极大的方面,如下图所示,自定义属性Math的提示窗口

image

自定义的类型的解析的一个困难之处在于,它是动态增加进来的,无法预先知道它的类型定义。比如

先输入属性IncludeDelete,是简单类型,不需要智能提示支持
<%@ Property Name="IncludeDelete" Type="System.Int32" Default="123" Category="Options" Description="If true delete statements will be generated." %>

再写一个自定义类型MathProgram,名字是Math

<%@ Property Name="Math" Type="MathProgram"  Category="Text"Description="Namespace for this class" %>

敲完了这一句,解析器仍然无法工作,因为它不知道MathProgram类型来自于哪里,是哪一层命名空间下的。(.NET允许同一程序集,在不同的命名空间下,有相同的类型名称)。当敲完了下面的语句

<%@ Assembly Name="TestClassLibrary" %>
<%@ Import Namespace="EPN.Common" %>

解析器会到程序集TestClassLibrary的命名空间EPN.Common下面去找MathProgram类型,如果能找到类型定义,则把它加入到智能提示窗口中,否则不加入。

再复杂一点的情况,有两个程序集引用和两个命名空间导入,代码如下

<%@ Assembly Name="TestClassLibrary" %>
<%@ Import Namespace="EPN.Common" %>

<%@ Assembly Name="TestProviderLibrary" %>
<%@ Import Namespace="Paradox.Common" %>

有一个类型声明,代码如下所示

<%@ Property Name="Math" Type="MathProgram"  Category="Text"Description="Namespace for this class" %>

这样,需要到以上两个程序集的两个命名空间中去匹配,找到指定的类型定义则可以加入智能提示成员。情况再糟糕一点,程序集TestClassLibrary的EPN.Common命名空间下和程序集TestProviderLibrary的Paradox.Common命名空间下,都含有类型MathProgram的定义。这时应当主动报编译错误。

 

模板代码生成的原型

通过以下的倒退流程,由生成的代码,推回到模板定义,以解释模板代码生成的基础模型。

假设目标代码是这样的,一个简单的类型定义

public class MathProgram

{

   public MathProgram()

   {

   }

}

用简单的变量替换,可以写成这样的代码,也就是把类型名字换成字符串值

<% private string classname = “MathProgram”; %>

public class <%=classname%>

{

  public <%=classname%> ()

  {

   }

}

如何做到变量可以替换?把上面的代码,放到一个Stream中,在执行时替换变量定义即可

string classname = “MathProgram”;

MemoryStream mStream = new MemoryStream();

StreamWriter writer = new

StreamWriter(mStream,System.Text.Encoding.UTF8);

writer.Write(@"public class );

writer.Write(classname);

writer.Write(@"{

public );

writer.Write(classname);

writer.Write(@"()

{

}

});

StreamReader sr = new StreamReader(mStream);

writer.Flush();

mStream.Position = 0;

string code = sr.ReadToEnd();

最终的变量code就是我们需要的结果。

再推进一下,字符串值可以由用户来输入或是通过Properties窗体来设置,那就是像这样

<%@ Property Name="classname" Type="String" Category="Name" Default="MathProgram" %>

public class <%=classname%>

{

  public <%=classname%> ()

  {

   }

}

这样,可以通过Properties窗体来改变类型的名字,而不用每次都改模板里面的Default的值"MathProgram"。

执行时的替换是什么意思?再体会这句话的含义:模板会被解析引擎转化为一个类型定义,然后会被动态编译成类型,调用它的方法输入类型的结果。在以前的文章《工作多年后才明白的.NET底层开发技术》中提到过的工资公式编译器,也是这个原理:通过构造一个类型,执行它的方法,最后取出执行结果,就是我们需要的生成后的代码。

代码看起来是这样的,生成程序集,调用指定的类型的方法,取方法的执行结果即可

Assembly assembly=CreateAssembly(sourceTemplate,parameters)
Type type=assembly.GetType(“Builder”);

InvokeMethod(type,”Render”, new object []{ “MathProgram”  });
自定义的程序集,可以通过下面的方式统一放到指定的目录中

<runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <probing privatePath="bin;AddIns;Providers"/>
        </assemblyBinding>
    </runtime>

这个设置允许bin,AddIns,Providers目录都可以存放程序集文件,而不会发生Load时FileNotFoundedException。

 

多种语法形式的模板

.NET如今正统的语言是VB.NE和C#,曾经红火的J#,Delphi.NET都已经退出市场。Template Studio也要支持这两种语言的模板,语法仍然兼容于Code Smith的语法声明

<%@ CodeTemplate Language="C#" TargetLanguage="C#" Description="Generates a very simple business object." %>

VB.NET的写法如下

<%@ CodeTemplate Language="VB" TargetLanguage="C#" Description="Generates a very simple business object." %>

如果是用Language是VB,代码中需要定义变量或是包含代码段的地方,都应该用VB的语法方式
属性定义
<% Private Const RUN_MULTIPLIER As Integer = 10  %>
代码片段

<% For i = 0 To RUN_MULTIPLIER – 1  %>
     <%=Math.ApplictionName%> <%=i%>
<% Next i  %>

此外,在设计生成的类型时,还需要注意,要全部用VB的语法,而不是C#的语法代码。

在编译时,需要把编译器由CSharpCodeProvider换成VBCodeProvider,以正确编译生成的源代码类型。

 

Flexible and Plug-in-based using Provider Pattern  应用Provider模式实现灵活的插件式编程

ASP.NET发行之初,通过著名的论坛程序Community Sever和ASP.NET Forum,首先提出了Provider模式。

先抄写一段赞美Provider模式的段落语句

ASP.net 2.0 的 Provider 模型为开发者提供了将他们自己的实现作为一种特性加入到运行时的可扩展方法。Membership Provider 与 Role Provider 在 ASP.net 2.0 中都通过细化一个接口或者协议来遵循 Provider 的模型。如果你创建你的组件来实现 Provider 模型定义的协议,你可以插入你的代码到 ASP.net 运行时并且替换或者扩展已经存在的 Provider。在 ASP.net 2.0 的 Provider 模型包括一个 Provider 配置与初始化的基础结构。

再以下面的代码中的例子,帮助理解Provider模式

public abstract class ProviderBase
{
        protected string name;
        protected string description;
        public string Name { get; }
        public string Description { get; }
        public abstract void Initialize();

        public abstract void GenerateSchema();        
    }
    public abstract class  SQLServerProvider : ProviderBase
    {

        public override void GenerateSchema()   {  }
    }
    public class MySqlProvider : ProviderBase
    {  
       public override void GenerateSchema()   {  }

    }

    public static class SchemaProvider
    {
        static  SchemaProvider()
        {
        }
        static ProviderBase  provider;
        public static ProviderBase  Provider { get { return provider; } }
        public static ProviderBase Instance
        {

            if(provider=null)

             {

                  swith(databaseType)

                  {

                       case DatabaseType.SQLServer:

                              provider=new  SQLServerProvider();

                              break;

                       case DatabaseType.MySql:

                              provider=new  MySqlProvider ();

                              break;

                   }

              }
            return provider
        }
}

正式的Provider的例子,会用到配置文件,我这里作了简化,直接用代码判断。

Provider模式在Template Studio中的应用举例
1)连接到数据库服务器,获取元数据的方法

2)模板代码的生成,VB和C#两种代码生成器,分别相同的类型的不同代码实现(VB.NET和C#)

 

代码生成器的高级主题

1)要支持批量代码生成。比如对一个数据表GBITEM,有实体ItemEntity对应,需要为它生成四个类型:ItemEntity.cs,ItemValidation.cs,ItemInterface.cs,ItemManager.cs

要支持,在一步的情况下,同时生成这四个类型,通常是四个UTF8格式的代码文件。

像LLBL Gen 3.x这样,把多个模板绑定到一个templatebinding文件中,传入一个llblgenproj项目文件

image_thumb3


2) 模板生成的代码,要可以被第三方的工具调用。在我开源工具Smith Builder中,它的最有价值的代码,就是获取参数值,传放到模板中,批次生成代码。

如下面的代码所示,可以以代码方式调用,脱离IDE Template Studio的限制,灵活运用

用Code Smith SDK中的代码示例子,如下所示

Dim compiler As CodeTemplateCompiler
          compiler = New CodeTemplateCompiler("StoredProcedures.cst")
          compiler.Compile()
          If compiler.Errors.Count = 0 Then
              Dim template As CodeTemplate
              template = compiler.CreateInstance()

              Dim database As DatabaseSchema
              database = New DatabaseSchema(New SqlSchemaProvider(), "Data Source=.\SQLEXPRESS;AttachDbFilename=PetShop.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True")
              Dim table As TableSchema
              table = database.Tables("Inventory")

              template.SetProperty("SourceTable", table)
              template.SetProperty("IncludeDrop", False)
              template.SetProperty("InsertPrefix", "Insert")

              template.Render(Console.Out)
          Else
              Dim i As Integer
              For i = 0 To compiler.Errors.Count
                  Console.Error.WriteLine(compiler.Errors(i).ToString())
                  Console.Read()
              Next
          End If

这种能力的威力是巨大的,这意味着模板可以脱离IDE的存在,被第三方的工具集成。Code Smith 本身是不收费的,它只收费Code Smith Studio,集成化的编辑,调试模板的IDE。

也只有这个原因,它才会被ORM工具以API的方式所调用,如下图所示

image

ORM.NET是.NET 1.x时代流行ORM工具,可惜后来停止了更新。似乎可以应验开源死的说法,如果不开源,或是转向商业化,或许发展会好很多。许多项目一放到互联网上,就慢慢的停止更新了。

 

3)支持主流的数据库。SQL Server,Oracle,MySQL这些常见的数据库,都需要写程序集来获取元数据,用于代码生成。这不是难点,有现成的开源的Code Smith的Prover作例子。这里也可以参考《LLBL Gen 3.x 源代码追踪与解析 查询命令的追踪》中提到的Dynamic Query Enginee,DQE,动态查询引擎,对不同的数据库,引用不同的程序集。

在Template Studio这边,这不算是难点,但是放到ORM框架-工具-产品开发的全局来看(big picture),这一部分是ORM框架重要的特性部分:多数据库平台支持。ORM天生是数据库独立的,不依赖于数据库特性。

在讲解ORM框架-工具-产品开发的ORM框架时,这一部分再做重点讲解。

 

4).NET程序集中的多重定向

先回忆一个场景,VS2005发布之后,微软又陆续发布了WCF,WPF等.NET 3.0/3.5的技术。如果要编译的原代码的Target=.NET 2.0,而要引用的程序集的版本可以是.NET 3.0/3.5;当时可以用VS2005做到这一点。但是后来VS2008发布,这种好处消失,即使是现在的VS2010也做不到,无法用低版本Target的程序集引用高版本Target的程序集。

image

这会影响到什么? 假设TestClassLibrary编译是的Target是.NET 4.0,而运行模板的程序集,Template Studio的Target是.NET 20,如下所示的类型引用:

<%@ Assembly Name="TestClassLibrary" %>
<%@ Import Namespace="EPN.Common" %>

这会影响到执行结果。这个困扰,可以用下面的办法解决,将应用程序设置为在版本 3.5 上运行

<runtime>
<compatibilityversion major="3" minor="0"/>
</runtime> <startup> <supportedRuntime version="v3.5.7000"/>
</startup>

对象关系映射架构(DBFramework)及代码生成器源码 一、使用对象关系映射组件Kenly.DBFramework.dll不用编写任何SQL或者存储过程即可实现下列功能: 1、数据表、视图和存储过程与对象之间的转换。 2、数据表、视图的自定义条件查询。 3、数据表、视图的分页查询。 4、根据ID、主键或自定义条件对数据表进行增、删、改操作。 5、实现一对一、一对多、多对一和多对多的关系映射。 6、支持单个对象和多个对象之间的事务控制。 7、支持查询结果排序。 8、支持查询表达式生成。 9、支持延迟加载。 代码生成器 1、根据指定的数据库连接,自动生成数据表、视图和存储过程对应的对象代码(C#代码)。 2、自动生成相关的工程文件,生成完整的业务层项目文件。 3、可以帮助生成自定义查询方法。 4、支持SQLServer2000、SQLServer2005和Oracle代码生成插件,支持插件扩展。 提供 1、对象关系映射组件: Kenly.DBFramework.dll 2、代码生成器源码:CodeHelper。 3、代码生成器插件源码(支持SQLServer2000、SQLServer2005和Oracle):Plugin。 4、使用手册:DBFramework.Manual V4.5.3.pdf。 主要API: public abstract class ViewGateway where T: new() { // Methods static ViewGateway(); protected ViewGateway(); protected static int Count(); protected static int Count(string condition); protected static List CountGroup(string groupBy); protected static List CountGroup(string condition, string groupBy); public static List CreateInstances(int count); protected static List Distinct(string columnName); protected static List Distinct(string columnName, string condition); protected static List Distinct(string columnName, string condition, bool ignoreNull); protected static bool Exists(string condition); public bool ExistsById(); public bool ExistsByPK(); protected static List Find(PagingArg pagingArg, params string[] propertyNames); protected static List Find(string condition, params string[] propertyNames); protected static List Find(PagingArg pagingArg, bool sqlServer2000, params string[] propertyNames); protected static List Find(string condition, PagingArg pagingArg, params string[] propertyNames); protected static List Find(string condition, PagingArg pagingArg, bool sqlServer2000, params string[] propertyNames); protected static List FindAll(params string[] propertyNames); protected static void FindTop(T firstOne); protected static List FindTop(int topCount, params string[] propertyNames); protected static void FindTop(string condition, T firstOne); protected static List FindTop(string condition, int topCount, params string[] propertyNames); protected static void InitializeGateway(GatewayConfig config); protected static void InitializeGateway(DatabaseType dbType, string connectionString); protected void InitializeInstance(T entity); protected static object Max(string columnName); protected static object Max(string columnName, string condition); protected static List MaxGroup(string columnName, string groupBy); protected static List MaxGroup(string columnName, string condition, string groupBy); protected static object Min(string columnName); protected static object Min(string columnName, string condition); protected static List MinGroup(string columnName, string groupBy); protected static List MinGroup(string columnName, string condition, string groupBy); protected static DataTable Query(PagingArg pagingArg, params string[] propertyNames); protected static DataTable Query(string condition, params string[] propertyNames); protected static DataTable Query(PagingArg pagingArg, bool sqlServer2000, params string[] propertyNames); protected static DataTable Query(string condition, PagingArg pagingArg, params string[] propertyNames); protected static DataTable Query(string condition, PagingArg pagingArg, bool sqlServer2000, params string[] propertyNames); protected static DataTable QueryAll(params string[] propertyNames); protected static DataTable QueryTop(int topCount, params string[] propertyNames); protected static DataTable QueryTop(string condition, int topCount, params string[] propertyNames); public ArrayList RetrieveAssociations(); public ArrayList RetrieveAssociations(Type elementType); public ArrayList RetrieveAssociations(params Type[] elementTypes); public void RetrieveById(); public void RetrieveByPK(); protected internal virtual void RetrieveBySql(string sql); public void ShadowCopyTo(object targetEntity); public void ShadowCopyTo(T targetEntity); protected static double Sum(string columnName); protected static double Sum(string columnName, string condition); protected static List SumGroup(string columnName, string groupBy); protected static List SumGroup(string columnName, string condition, string groupBy); public static string ToColumnName(string propertyName); public static T ToObject(DataRow adaptedRow); public static List ToObjects(DataTable adaptedTable); public static List ToObjects(DataRow[] adaptedRows); public static string ToPropertyName(string columnName); public static DataTable ToTable(IList entities); public static DataTable ToTable(params T[] entities); public static DataTable ToTable(bool isAdapted, params T[] entities); public static DataTable ToTable(bool isAdapted, IList entities); // Properties protected internal static string CommandText { get; } protected internal static GatewayConfig Config { get; } internal T Entity { get; set; } public object EntityId { get; } public object PrimaryKey { get; } protected static Order SortedOrder { get; set; } } public abstract class TableGateway : ViewGateway where T: TableGateway, new() { // Methods static TableGateway(); protected TableGateway(); public int AddNew(); public static int AddNew(IList entities); public static int AddNew(T entity); public int AddNew(bool returnIdentity); public static int AddNew(T entity, bool returnIdentity); public static int AddNew(IList entities, out Transaction transaction); public static int AddNew(IList entities, bool returnIdentity); public static int AddNew(T entity, out Transaction transaction); public static int AddNew(IList entities, bool returnIdentity, out Transaction transaction); public static int AddNew(T entity, bool returnIdentity, out Transaction transaction); public IDbTransaction BeginTransaction(); public IDbTransaction BeginTransaction(IsolationLevel isolationLevel); public IDbTransaction BeginTransaction(double activeTime); public IDbTransaction BeginTransaction(IsolationLevel isolationLevel, double activeTime); public bool Commit(); protected static int Delete(string condition); protected static int Delete(string condition, out Transaction transaction); public int DeleteById(); public static int DeleteById(T entity); public static int DeleteById(IList entities); public static int DeleteById(IList entities, out Transaction transaction); public static int DeleteById(T entity, out Transaction transaction); public int DeleteByPK(); public static int DeleteByPK(T entity); public static int DeleteByPK(IList entities); public static int DeleteByPK(IList entities, out Transaction transaction); public static int DeleteByPK(T entity, out Transaction transaction); protected void OnPropertyChanged(MethodBase propertyMethod); protected void OnPropertyChanged(string propertyName); protected internal override void RetrieveBySql(string sql); public bool Rollback(); protected static int Update(T entity, string condition); protected static int Update(T entity, string condition, out Transaction transaction); public int UpdateById(); public static int UpdateById(IList entities); public static int UpdateById(T entity); public static int UpdateById(T entity, out Transaction transaction); public static int UpdateById(IList entities, out Transaction transaction); public int UpdateByPK(); public static int UpdateByPK(IList entities); public static int UpdateByPK(T entity); public static int UpdateByPK(IList entities, out Transaction transaction); public static int UpdateByPK(T entity, out Transaction transaction); // Properties public bool Changed { get; } public List ChangedPropertyNames { get; } } public class StoredProcedure : IStoredProcedure { public bool BeginTransaction(); public void CloseReader(IDataReader reader); public bool Commit(); public DataSet ExecuteDataSet(); public DataTable ExecuteDataTable(); public DataTable ExecuteDataTable(); public List ExecuteEntity(); public int ExecuteNonQuery(); public IDataReader ExecuteReader(); public object ExecuteScalar(); protected static void InitializeGateway(GatewayConfig config); protected static void InitializeGateway(DatabaseType dbType, string connectionString); protected void InitializeInstance(T entity); public bool Rollback(); } public class AggregateEntity where T: AggregateEntity, new() { public static List Execute(); protected static List Execute(string condition); public static DataTable ExecuteDataTable(); protected static DataTable ExecuteDataTable(string condition); protected static void InitializeGateway(GatewayConfig config); protected static void InitializeGateway(DatabaseType dbType, string connectionString); // Properties protected static Order SortedOrder { get; set; } } public static class EntityMapper { // Methods public static void AdaptToDatabase(DataTable adaptedTable); public static void AdaptToEntity(DataTable rawTable); public static void CopyToEntities(IList entities, IList adaptedRows); public static void CopyToEntities(IList entities, DataTable adaptedTable); public static void CopyToEntity(T entity, DataRow row); public static List CreateEntities(int count); public static DataTable CreateTable(); public static DataTable CreateTable(bool isAdapted); public static string ToColumnName(string propertyName); public static List ToEntities(IList adaptedRows); public static List ToEntities(DataTable table); public static T ToEntity(DataRow adaptedRow); public static string ToPropertyName(string columnName); public static DataTable ToTable(IList entities); public static DataTable ToTable(params T[] entities); public static DataTable ToTable(bool isAdapted, params T[] entities); public static DataTable ToTable(bool isAdapted, IList entities); } public static class EntityUtility { // Methods public static List Inherit(IList entities); public static T Inherit(object entity); public static T Inherit(Tbase baseEntity, TransformAction method); public static List Inherit(IList baseEntities, TransformAction method); public static void ShadowCopy(IList targetEntities, IList sourceEntities); public static void ShadowCopy(object targetEntity, object sourceEntity); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值