mvc实用架构设计三efcode first3使用t4模板生成相似代码.docx
《mvc实用架构设计三efcode first3使用t4模板生成相似代码.docx》由会员分享,可在线阅读,更多相关《mvc实用架构设计三efcode first3使用t4模板生成相似代码.docx(21页珍藏版)》请在冰豆网上搜索。
mvc实用架构设计三efcodefirst3使用t4模板生成相似代码
MVC实用架构设计(三)——EF-CodeFirst(3):
使用T4模板生成相似代码
〇、目录一、前言二、工具准备三、T4代码生成预热
(一)单文件生成:
HelloWorld.cs
(二)多文件生成四、生成数据层实体相关相似代码
(一)生成准备
(二)生成实体相关相似代码生成实体映射配置类生成实体仓储接口生成实体仓储实现五、源码获取系列导航一、前言 经过前面EF的《第一篇》与《第二篇》,我们的数据层功能已经较为完善了,但有不少代码相似度较高,比如负责实体映射的EntityConfiguration,负责仓储操作的IEntityRepository与EntityRepository。
而且每添加一个实体类型,就要手动去添加一套相应的代码,也是比较累的工作。
如果能有一个根据实体类型自动生成这些相似度较高的代码的解决方案,那将会减少大量的无聊的工作。
VS提供的“文本模板”(俗称T4)功能,就是一个较好的解决方案。
要添加一个实体类型,只要把实体类型定义好,然后运行一下定义好的T4模板,就可以自动生成相应的类文件。
二、工具准备 为了更好的使用T4模板功能,我们需要给VS安装如下两个插件:
DevartT4Editor:
为VS提供智能提示功能。
T4Toolbox:
在生成多文件时很有用。
三、T4代码生成预热
(一)单文件生成:
HelloWorld.cs 下面,我们先来体验一个最简单的T4代码生成功能,输出一个最简单的类文件。
首先,在GMF.Demo.Core.Data中添加一个名为T4的文件夹,用于存放生成本工程内的代码的T4模板文件。
并在其中添加一个名为HelloWorld.tt的“文本模板”的项。
HelloWorld.tt定义如下:
1<#@templatedebug="false"hostspecific="false"language="C#"#>
2<#@assemblyname="System.Core"#>
3<#@importnamespace="System.Linq"#>
4<#@importnamespace="System.Text"#>
5<#@importnamespace="System.Collections.Generic"#>
6<#@outputextension=".cs"#>
7usingSystem;
8
9namespaceGMF.Demo.Core.Data.T4
10{
11publicclassHelloWorld
12{
13privatestring_word;
14
15publicHelloWorld(stringword)
16{
17_word=word;
18}
19}
20} 直接保存文件(T4的生成将会在保存模板,模板失去焦点等情况下自动触发生成。
),将会在模板的当前位置生成一个同名的类文件:
HelloWorld.cs的内容如下:
1usingSystem;
2
3namespaceGMF.Demo.Core.Data.T4
4{
5publicclassHelloWorld
6{
7privatestring_word;
8
9publicHelloWorld(stringword)
10{
11_word=word;
12}
13}
14} 这样,我们的HelloWorld之旅就结束了,非常简单。
(二)多文件生成 当前位置方案的方案只能生成如下所示的代码:
生成的文件会与T4模板在同一目录中,这里就不详述了,可以参考蒋金楠一个简易版的T4代码生成"框架"。
本项目的多文件需要生成到指定文件夹中,但又想对T4模板进行统一的管理,T4文件夹里放置T4模板文件,但生成的映射文件EntityConfiguration将放置到文件夹Configurations中,仓储操作的文件IEntityRepository与EntityRepository将放置到Repositories文件夹中。
且生成的代码文件应能自动的添加到解决方案中,而不是只是在文件夹中存在。
要实现此需求,一个简单的办法就是通过T4Toolbox来进行生成。
想了解T4Toolbox的细节,可以自己使用ILSpy来对T4Toolbox.dll文件进行反编译来查看源代码,如果是通过T4Toolbox是通过VS的插件来安装的,将在“C:
\Users\Administrator\AppData\Local\Microsoft\VisualStudio\11.0\Extensions\dca4f0lt.jdx”文件夹中(我的机器上)。
下面,我们直接进行多文件生成的演示。
首先,添加一个名为HelloWorldTemplate.tt的T4Toolbox的代码模板。
此模板将继承于T4Toolbox的CSharpTemplate类:
1<#+
2//<copyrightfile="HelloWorldTemplate.tt"company="郭明锋@中国">
3//Copyright?
郭明锋@中国.AllRightsReserved.
4//</copyright>
5
6publicclassHelloWorldTemplate:
CSharpTemplate
7{
8privatestring_className;
9
10publicHelloWorldTemplate(stringclassName)
11{
12_className=className;
13}
14
15publicoverridestringTransformText()
16{
17#>
18usingSystem;
19
20namespaceGMF.Demo.Core.Data.T4
21{
22publicclass<#=_className#>
23{
24privatestring_word;
25
26public<#=_className#>(stringword)
27{
28_word=word;
29}
30}
31}
32<#+
33returnthis.GenerationEnvironment.ToString();
34}
35}
36#> 模板类中定义了一个className参数,用于接收一个表示要生成的类名的值。
所有生成类的代码都以字符串的形式写在重写的TransformText方法中。
再定义一个T4模板文件HelloWorldMulti.tt,用于调用上面定义的代码模板进行代码文件的生成。
1<#@templatedebug="false"hostspecific="false"language="C#"#>
2<#@assemblyname="System.Core"#>
3<#@importnamespace="System.IO"#>
4<#@importnamespace="System.Linq"#>
5<#@importnamespace="System.Text"#>
6<#@importnamespace="System.Collections.Generic"#>
7<#@includefile="T4Toolbox.tt"#>
8<#@includefile="HelloWorldTemplate.tt"#>
9<#
10stringcurPath=Path.GetDirectoryName(Host.TemplateFile);
11stringdestPath=Path.Combine(curPath,"outPath");
12if(!
Directory.Exists(destPath))
13{
14Directory.CreateDirectory(destPath);
15}
16string[]classNames=new[]{"HelloWorld1","HelloWorld2","HelloWorld3"};
17foreach(stringclassNameinclassNames)
18{
19HelloWorldTemplatetemplate=newHelloWorldTemplate(className);
20stringfileName=string.Format(@"{0}\{1}.cs",destPath,className);
21template.Output.Encoding=Encoding.UTF8;
22template.RenderToFile(fileName);
23}
24#> 以上是整个T4模板的执行方,在执行方中,要引用所有需要用到的类库文件,命名空间,包含的模板文件等。
最后,文件的生成是调用T4Toolbox的Template基类中定义的RenderToFile(stringfilename)方法来生成各个文件的,输入的参数为生成文件的文件全名。
在这里,生成将如下所示:
outPPath文件夹中生成了HelloWorld1.cs、HelloWorld2.cs、HelloWorld3.cs文件,而HelloWorldMulti.tt所在文件夹中也会生成一个空的HelloWorldMulti.cs类文件。
四、生成数据层实体相关相似代码
(一)生成准备 我们的生成代码是完全依赖于业务实体的,所以,需要有一个类来对业务实体的信息进行提取封装。
1namespaceGMF.Component.Tools.T4
2{
3///<summary>
4///T4实体模型信息类
5///</summary>
6publicclassT4ModelInfo
7{
8///<summary>
9///获取模型所在模块名称
10///</summary>
11publicstringModuleName{get;privateset;}
12
13///<summary>
14///获取模型名称
15///</summary>
16publicstringName{get;privateset;}
17
18///<summary>
19///获取模型描述
20///</summary>
21publicstringDescription{get;privateset;}
22
23publicIEnumerable<PropertyInfo>Properties{get;privateset;}
24
25publicT4ModelInfo(TypemodelType)
26{
27var@namespace=modelType.Namespace;
28if(@namespace==null)
29{
30return;
31}
32varindex=@namespace.LastIndexOf('.')+1;
33ModuleName=@namespace.Substring(index,@namespace.Length-index);
34Name=modelType.Name;
35vardescAttributes=modelType.GetCustomAttributes(typeof(DescriptionAttribute),true);
36Description=descAttributes.Length==1?
((DescriptionAttribute)descAttributes[0]).Description:
Name;
37Properties=modelType.GetProperties();
38}
39}
40} 另外,通过模板生成的代码,与我们手写的代码有如下几个区别:
手写代码可以自由定义,生成代码是根据模板生成的,必然遵守模板定义的规范。
手写代码的修改可以永久保存,生成代码的修改将会在下次生成后丢失,即生成代码不应该进行修改。
基于以上几个区别,我提出如下解决方案,来解决生成代码的修改问题通过模板生成的类应尽量定义为分部类(partialclass),在需要进行修改及扩展的时候可以定义另外的同名分部类来进行修改。
对于需要外部实现的功能,定义分部方法来对外提供扩展。
生成代码的类文件命名把扩展名由“.cs”更改为“generated.cs”,以区分生成代码与手写代码文件。
(二)生成实体相关相似代码 1.生成实体映射配置类 实体映射配置类模板EntityConfigurationTemplate.tt定义:
1<#+
2//<copyrightfile="EntityConfigurationTemplate.tt"company="郭明锋@中国">
3//Copyright?
郭明锋@中国.AllRightsReserved.
4//</copyright>
5
6publicclassEntityConfigurationTemplate:
CSharpTemplate
7{
8privateT4ModelInfo_model;
9
10publicEntityConfigurationTemplate(T4ModelInfomodel)
11{
12_model=model;
13}
14
15///<summary>
16///获取生成的文件名,根据模型名定义
17///</summary>
18publicstringFileName
19{
20get
21{
22returnstring.Format("{0}Configuration.generated.cs",_model.Name);
23}
24}
25
26publicoverridestringTransformText()
27{
28#>
29//------------------------------------------------------------------------------
30//<auto-generated>
31//此代码由工具生成。
32//对此文件的更改可能会导致不正确的行为,并且如果
33//重新生成代码,这些更改将会丢失。
34//如存在本生成代码外的新需求,请在相同命名空间下创建同名分部类实现<#=_model.Name#>ConfigurationAppend分部方法。
35//</auto-generated>
36//
37//<copyrightfile="<#=_model.Name#>Configuration.generated.cs">
38//Copyright(c)2013GMFCN.Allrightsreserved.
39//CLR版本:
4.0.30319.239
40//开发组织:
郭明锋@中国
41//公司网站:
42//所属工程:
GMF.Demo.Core.Data
43//生成时间:
<#=DateTime.Now.ToString("yyyy-MM-ddHH:
mm")#>
44//</copyright>
45//------------------------------------------------------------------------------
46
47usingSystem;
48usingSystem.Data.Entity.ModelConfiguration;
49usingSystem.Data.Entity.ModelConfiguration.Configuration;
50
51usingGMF.Component.Data;
52usingGMF.Demo.Core.Models;
53
54
55namespaceGMF.Demo.Core.Data.Configurations
56{
57///<summary>
58///实体类-数据表映射——<#=_model.Description#>
59///</summary>
60internalpartialclass<#=_model.Name#>Configuration:
EntityTypeConfiguration<<#=_model.Name#>>,IEntityMapper
61{
62///<summary>
63///实体类-数据表映射构造函数——<#=_model.Description#>
64///</summary>
65public<#=_model.Name#>Configuration()
66{
67<#=_model.Name#>ConfigurationAppend();
68}
69
70///<summary>
71///额外的数据映射
72///</summary>
73partialvoid<#=_model.Name#>ConfigurationAppend();
74
75///<summary>
76///将当前实体映射对象注册到当前数据访问上下文实体映射配置注册器中
77///</summary>
78///<paramname="configurations">实体映射配置注册器</param>
79publicvoidRegistTo(ConfigurationRegistrarconfigurations)
80{
81configurations.Add(this);
82}
83}
84}
85<#+
86returnthis.GenerationEnvironment.ToString();
87}
88}
89#> 生成模板调用方EntityCodeScript.tt定义1<#@templatelanguage="C#"debug="True"#>
2<#@outputextension="cs"#>
3<#@AssemblyName="System.Core"#>
4<#@AssemblyName="$(SolutionDir)\GMF.Component.Tools\bin\Debug\GMF.Component.Tools.dll"#>
5<#@importnamespace="System.IO"#>
6<#@ImportNamespace="System.Linq"#>
7<#@ImportNamespace="System.Text"#>
8<#@importnamespace="System.Reflection"#>
9<#@ImportNamespace="System.Collections.Generic"#>
10<#@ImportNamespace="GMF.Component.Tools"#>
11<#@ImportNamespace="GMF.Component.Tools.T4"#>
12<#@includefile="T4Toolbox.tt"#>
13<#@includefile="Include\EntityConfigurationTemplate.tt"#>
14<#
15stringcurrentPath=Path.GetDirectoryName(Host.TemplateFile);
16stringprojectPath=currentPath.Substring(0,currentPath.IndexOf(@"\T4"));
17stringsolutionPath=currentPath.Substring(0,currentPath.IndexOf(@"\GMF.Demo.Core.Data"));
18
19stringmodelFile=Path.Combine(solutionPath,@"GMF.Demo.Core.Models\bin\Debug\GMF.Demo.Core.Models.dll");
20byte[]fileData=File.ReadAllBytes(modelFile);
21Assemblyassembly=Assembly.Load(fileData);
22IEnumerable<Type>modelTypes=assembly.GetTypes().Where(m=>typeof(Entity).IsAssignableFrom(m)&&!
m.IsAbstract);
23foreach(TypemodelTypeinmodelTypes)