不多说,完善一下。
完善数据核心层
在数据实体层(SF.Framework.Entity)中,我们以一级文件夹区分了不同数据库的数据实体,如下图:
也是因此,在数据核心层(SF.Framework.DataCore)中定义EF数据上下文(MasterEntityContext)时,特地以Master作为类前缀来进行区分,实体配置工厂(MasterConfigurationFactory)也是同样的处理。
观察这2个类,简单有规则,完全可以用T4来自动生成。反射数据实体层,获取每个数据实体的命名空间的最后的名称(比如从S.Framework.Entity.Master获取Master),作为前缀去生成EF数据库上下文类和实体配置工厂类。
就这么办。创建2个模板,分别作为EF数据库上下文模板和实体配置工厂模板。
数据库上下文模板:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <#+ 2 // <copyright file="EntityContext.tt" company=""> 3 // Copyright © . All Rights Reserved. 4 // </copyright> 5 6 public class EntityContext : CSharpTemplate 7 { 8 private string _prefixName; 9 10 public EntityContext(string prefixName) 11 { 12 _prefixName = prefixName; 13 } 14 public override string TransformText() 15 { 16 base.TransformText(); 17 #> 18 19 using System; 20 using System.Collections.Generic; 21 using System.Linq; 22 using System.Text; 23 using System.Threading.Tasks; 24 using System.Data.Entity; 25 using System.Data.Entity.ModelConfiguration.Conventions; 26 27 using S.Framework.DataCore.EntityFramework.ConfigurationFactories; 28 29 namespace S.Framework.DataCore.EntityFramework.EntityContexts 30 { 31 /// <summary> 32 /// 数据库上下文 33 /// </summary> 34 public class <#= _prefixName #>EntityContext : System.Data.Entity.DbContext 35 { 36 /// <summary> 37 /// 构造函数 38 /// </summary> 39 /// <param name="nameOrConnectionString">数据库名称或连接字符串。</param> 40 public <#= _prefixName #>EntityContext(string nameOrConnectionString) 41 : base(nameOrConnectionString) 42 { } 43 44 /// <summary> 45 /// 模型配置重写 46 /// </summary> 47 /// <param name="modelBuilder">数据实体生成器</param> 48 protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder) 49 { 50 // 禁用一对多级联删除 51 modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 52 // 禁用多对多级联删除 53 modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>(); 54 // 禁用表名自动复数规则 55 modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); 56 57 <#= _prefixName #>ConfigurationFactory.ConfigurationsInit(modelBuilder.Configurations); 58 } 59 } 60 } 61 <#+ 62 return this.GenerationEnvironment.ToString(); 63 } 64 } 65 #> 66
实体配置工厂模板:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <#+ 2 // <copyright file="ConfigurationFactory.tt" company=""> 3 // Copyright © . All Rights Reserved. 4 // </copyright> 5 6 public class ConfigurationFactory : CSharpTemplate 7 { 8 private string _prefixName; 9 10 public ConfigurationFactory(string prefixName) 11 { 12 _prefixName = prefixName; 13 } 14 public override string TransformText() 15 { 16 base.TransformText(); 17 #> 18 19 using System; 20 using System.Collections.Generic; 21 using System.Linq; 22 using System.Text; 23 using System.Threading.Tasks; 24 using System.Data.Entity.ModelConfiguration.Configuration; 25 26 using S.Utilities; 27 28 namespace S.Framework.DataCore.EntityFramework.ConfigurationFactories 29 { 30 /// <summary> 31 /// 实体配置工厂类 32 /// </summary> 33 public static class <#= _prefixName #>ConfigurationFactory 34 { 35 /// <summary> 36 /// 同步对象 37 /// </summary> 38 private static readonly object sync = new object(); 39 40 /// <summary> 41 /// 唯一实例 42 /// </summary> 43 private static IEnumerable<IEntityConfiguration> singleton; 44 45 /// <summary> 46 /// 实体配置 47 /// </summary> 48 private static IEnumerable<IEntityConfiguration> Configurations 49 { 50 get 51 { 52 if (singleton == null) 53 { 54 lock (sync) 55 { 56 if (singleton == null) 57 { 58 var types = typeof(IEntityConfiguration).GetSubClass().Where(w => !w.IsAbstract && w.Namespace.EndsWith("<#= _prefixName #>")); 59 60 singleton = types.Select(m => Activator.CreateInstance(m) as IEntityConfiguration); 61 } 62 } 63 } 64 65 return singleton; 66 } 67 } 68 69 /// <summary> 70 /// 初始化实体模型生成器 71 /// </summary> 72 /// <param name="configurations">实体模型生成器</param> 73 public static void ConfigurationsInit(ConfigurationRegistrar configurations) 74 { 75 foreach (var configuration in Configurations) 76 { 77 configuration.RegistTo(configurations); 78 } 79 } 80 } 81 } 82 <#+ 83 return this.GenerationEnvironment.ToString(); 84 } 85 } 86 #> 87
设计好模板之后,调整一下执行器文件的代码,就可以了。修改后的执行器代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <#@ template language="C#" debug="True" #> 2 <#@ assembly name="System.Core" #> 3 <#@ output extension="cs" #> 4 <#@ import namespace="System.IO" #> 5 <#@ import namespace="System.Text" #> 6 <#@ import namespace="System.Reflection" #> 7 <#@ import namespace="System.Linq" #> 8 <#@ import namespace="System.Collections.Generic" #> 9 <#@ include file="T4Toolbox.tt" #> 10 <#@ include file="Configuration.tt" #> 11 <#@ include file="ConfigurationFactory.tt" #> 12 <#@ include file="EntityContext.tt" #> 13 <# 14 15 string coreName = "S.Framework", projectName = coreName + ".DataCore", entityProjectName = coreName + ".Entity"; 16 string entityBaseModelName = entityProjectName + ".EntityBaseModel"; 17 string entityBaseModelNameForReflection = entityProjectName + ".EntityModelBaseForReflection"; 18 //当前完整路径 19 string currentPath = Path.GetDirectoryName(Host.TemplateFile); 20 //T4文件夹的父级文件夹路径 21 string projectPath = currentPath.Substring(0, currentPath.IndexOf(@"T4")); 22 //解决方案路径 23 string solutionFolderPath = currentPath.Substring(0, currentPath.IndexOf(@"" + projectName)); 24 25 //加载数据实体.dll 26 string entityFilePath = string.Concat(solutionFolderPath, ("\"+ entityProjectName +"\bin\Debug\" + entityProjectName + ".dll")); 27 byte[] fileData = File.ReadAllBytes(entityFilePath); 28 Assembly assembly = Assembly.Load(fileData); 29 //反射出实体类,不知道为啥此处不能成功判定“是否继承EntityModelBaseForReflection类” 30 //因此只能通过名称比较的方式来判定 31 IEnumerable<Type> modelTypes = assembly.GetTypes().Where(m => m.IsClass && !m.IsAbstract && (m.BaseType.FullName.Equals(entityBaseModelName) || m.BaseType.FullName.Equals(entityBaseModelNameForReflection))); 32 33 //循环实体类 34 List<string> prefixNames = new List<string>(); 35 foreach (Type item in modelTypes) 36 { 37 //找 实体文件夹 名称 38 string tempNamespace= item.Namespace, nameSpaceWithoutProjectName = tempNamespace.Substring(entityProjectName.Length); 39 if(nameSpaceWithoutProjectName.IndexOf(".") != 0 || nameSpaceWithoutProjectName.LastIndexOf(".") > 0) 40 { continue; } 41 42 //是否直接继承实体基本类 43 bool purity = item.BaseType.FullName.Equals(entityBaseModelNameForReflection); 44 //实体所在的数据库标识名称 45 string targetName = nameSpaceWithoutProjectName.Substring(1); 46 if(!prefixNames.Any(a => a == targetName)){ prefixNames.Add(targetName); } 47 //目标文件的路径和名称(嵌套Generate文件夹是为了标识T4生成的类文件) 48 string fileName= targetName + @"Generate" + item.Name + "Configuration.cs"; 49 50 //配置文件 51 string folderName= @"Configurations"; 52 Configuration configuration = new Configuration(item.Name, targetName, purity); 53 configuration.Output.Encoding = Encoding.UTF8; 54 string path = projectPath + folderName + fileName; 55 configuration.RenderToFile(path); 56 } 57 58 foreach(string prefixName in prefixNames) 59 { 60 //配置工厂文件 61 string fileName = prefixName + "ConfigurationFactory.Generate.cs"; 62 ConfigurationFactory configurationFactory = new ConfigurationFactory(prefixName); 63 configurationFactory.Output.Encoding = Encoding.UTF8; 64 string path = string.Format(@"{0}ConfigurationFactories", projectPath) + fileName; 65 configurationFactory.RenderToFile(path); 66 67 //数据库上下文文件 68 fileName = prefixName + "EntityContext.Generate.cs"; 69 EntityContext entityContext = new EntityContext(prefixName); 70 entityContext.Output.Encoding = Encoding.UTF8; 71 path = string.Format(@"{0}EntityContexts", projectPath) + fileName; 72 entityContext.RenderToFile(path); 73 } 74 #> 75
运行一下执行器,发现EF数据库上下文和实体配置工厂都自动创建了。此时可以删除旧的EF数据库上下文和实体配置工厂。文档结构目录如下图:
编译,重新登录一下试试,没问题就好。
完善数据实现层
同样的,在数据实现层(S.Framework.DataAchieve)中,数据库初始化操作类(MasterDatabaseInitializer)也可以用相同的做法来自动生成。
创建2个模板文件,分别表示数据库初始化策略配置类模板和数据库初始化类模板,如下图:
数据库初始化策略配置模板:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <#+ 2 // <copyright file="MigrateConfiguration.tt" company=""> 3 // Copyright © . All Rights Reserved. 4 // </copyright> 5 6 public class MigrateConfiguration : CSharpTemplate 7 { 8 private string _prefixName; 9 10 public MigrateConfiguration(string prefixName) 11 { 12 this._prefixName = prefixName; 13 } 14 15 public override string TransformText() 16 { 17 base.TransformText(); 18 #> 19 using System; 20 using System.Collections.Generic; 21 using System.Linq; 22 using System.Text; 23 using System.Threading.Tasks; 24 using System.Data.Entity.Migrations; 25 26 using S.Framework.DataCore.EntityFramework.EntityContexts; 27 28 namespace S.Framework.DataAchieve.EntityFramework.Initializes 29 { 30 /// <summary> 31 /// <#= _prefixName #> 数据库迁移策略 32 /// </summary> 33 internal sealed class <#= _prefixName #>MigrateConfiguration : DbMigrationsConfiguration<<#= _prefixName #>EntityContext> 34 { 35 /// <summary> 36 /// 构造方法 37 /// </summary> 38 public <#= _prefixName #>MigrateConfiguration() 39 { 40 //表示开启自动合并 41 this.AutomaticMigrationsEnabled = true; 42 //表示将导致数据丢失的合并也允许进行 43 this.AutomaticMigrationDataLossAllowed = true; 44 } 45 46 /// <summary> 47 /// 数据初始化 48 /// </summary> 49 /// <param name="context">数据库上下文</param> 50 protected override void Seed(<#= _prefixName #>EntityContext context) 51 { 52 //<#= _prefixName #>DataInit.DataInit(context); 53 } 54 } 55 } 56 <#+ 57 return this.GenerationEnvironment.ToString(); 58 } 59 } 60 #> 61
数据库初始化模板:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <#+ 2 // <copyright file="DatabaseInitializer.tt" company=""> 3 // Copyright © . All Rights Reserved. 4 // </copyright> 5 6 public class DatabaseInitializer : CSharpTemplate 7 { 8 private string _prefixName; 9 10 public DatabaseInitializer(string prefixName) 11 { 12 this._prefixName = prefixName; 13 } 14 15 public override string TransformText() 16 { 17 base.TransformText(); 18 #> 19 20 using System; 21 using System.Collections.Generic; 22 using System.Linq; 23 using System.Text; 24 using System.Threading.Tasks; 25 using System.Data.Entity; 26 27 using S.Framework.DataCore.EntityFramework.EntityContexts; 28 29 namespace S.Framework.DataAchieve.EntityFramework.Initializes 30 { 31 /// <summary> 32 /// <#= _prefixName #> 数据库初始化操作类 33 /// </summary> 34 public class <#= _prefixName #>DatabaseInitializer 35 { 36 /// <summary> 37 /// 设置数据库初始化策略 38 /// </summary> 39 /// <param name="migrate">是否合并(自动迁移)。若是,则会检查数据库是否存在,若不存在则创建,若存在则进行自动迁移。若否,则不进行初始化操作(这样能避开EF访问sys.databases检测数据库是否存在,项目稳定后可将参数设置为false)。</param> 40 public void Initialize(bool migrate) 41 { 42 if (!migrate) 43 { 44 System.Data.Entity.Database.SetInitializer<<#= _prefixName #>EntityContext>(null); 45 } 46 else 47 { 48 System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<<#= _prefixName #>EntityContext, <#= _prefixName #>MigrateConfiguration>(true)); 49 } 50 } 51 } 52 } 53 <#+ 54 return this.GenerationEnvironment.ToString(); 55 } 56 } 57 #> 58
设计好模板之后,调整一下执行器文件的代码,就可以了。修改后的执行器代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <#@ template language="C#" debug="True" #> 2 <#@ assembly name="System.Core" #> 3 <#@ output extension="cs" #> 4 <#@ import namespace="System.IO" #> 5 <#@ import namespace="System.Text" #> 6 <#@ import namespace="System.Reflection" #> 7 <#@ import namespace="System.Linq" #> 8 <#@ import namespace="System.Collections.Generic" #> 9 <#@ include file="T4Toolbox.tt" #> 10 <#@ include file="Repository.tt" #> 11 <#@ include file="MigrateConfiguration.tt" #> 12 <#@ include file="DatabaseInitializer.tt" #> 13 <# 14 15 string coreName = "S.Framework", projectName = coreName + ".DataAchieve", entityProjectName = coreName + ".Entity"; 16 string entityBaseModelName = entityProjectName + ".EntityBaseModel"; 17 string entityBaseModelNameForReflection = entityProjectName + ".EntityModelBaseForReflection"; 18 //当前完整路径 19 string currentPath = Path.GetDirectoryName(Host.TemplateFile); 20 //T4文件夹的父级文件夹路径 21 string projectPath = currentPath.Substring(0, currentPath.IndexOf(@"T4")); 22 //解决方案路径 23 string solutionFolderPath = currentPath.Substring(0, currentPath.IndexOf(@"" + projectName)); 24 25 //加载数据实体.dll 26 string entityFilePath = string.Concat(solutionFolderPath, ("\"+ entityProjectName +"\bin\Debug\" + entityProjectName + ".dll")); 27 byte[] fileData = File.ReadAllBytes(entityFilePath); 28 Assembly assembly = Assembly.Load(fileData); 29 //反射出实体类,不知道为啥此处不能成功判定“是否继承EntityModelBaseForReflection类” 30 //因此只能通过名称比较的方式来判定 31 IEnumerable<Type> modelTypes = assembly.GetTypes().Where(m => m.IsClass && !m.IsAbstract && (m.BaseType.FullName.Equals(entityBaseModelName) || m.BaseType.FullName.Equals(entityBaseModelNameForReflection))); 32 33 //循环实体类 34 Dictionary<string, List<Type>> prefixModelTypes = new Dictionary<string, List<Type>>();//存储[数据库标识名称]和[实体类型集合] 35 foreach (Type item in modelTypes) 36 { 37 //找 实体文件夹 名称 38 string tempNamespace= item.Namespace, nameSpaceWithoutProjectName = tempNamespace.Substring(entityProjectName.Length); 39 if(nameSpaceWithoutProjectName.IndexOf(".") != 0 || nameSpaceWithoutProjectName.LastIndexOf(".") > 0) 40 { continue; } 41 42 //是否直接继承实体基本类 43 bool purity = item.BaseType.FullName.Equals(entityBaseModelNameForReflection); 44 //实体所在的数据库标识名称 45 string targetName = nameSpaceWithoutProjectName.Substring(1); 46 List<Type> temp; 47 if(prefixModelTypes.TryGetValue(targetName, out temp)) 48 { 49 temp.Add(item); 50 } 51 else 52 { 53 temp = new List<Type>{ item }; 54 prefixModelTypes.Add(targetName, temp); 55 } 56 57 //目标文件的路径和名称(嵌套Generate文件夹是为了标识T4生成的类文件) 58 string fileName= targetName + @"Generate" + item.Name + "Repository.cs"; 59 //仓储文件 60 string folderName= @"Repositories"; 61 Repository repository = new Repository(item.Name, targetName); 62 repository.Output.Encoding = Encoding.UTF8; 63 string path = projectPath + folderName + fileName; 64 repository.RenderToFile(path); 65 } 66 67 foreach(KeyValuePair<string, List<Type>> item in prefixModelTypes) 68 { 69 //数据库初始化策略配置文件 70 string fileName = "/"+ item.Key +"/" + item.Key + "MigrateConfiguration.Generate.cs"; 71 MigrateConfiguration mc = new MigrateConfiguration(item.Key); 72 mc.Output.Encoding = Encoding.UTF8; 73 string path = string.Format(@"{0}Initializes", projectPath) + fileName; 74 mc.RenderToFile(path); 75 76 //数据库初始化类文件 77 fileName = "/"+ item.Key +"/" + item.Key + "DatabaseInitializer.Generate.cs"; 78 DatabaseInitializer temp = new DatabaseInitializer(item.Key); 79 temp.Output.Encoding = Encoding.UTF8; 80 path = string.Format(@"{0}Initializes", projectPath) + fileName; 81 temp.RenderToFile(path); 82 } 83 #> 84
T4执行后的文件结构目录如下图:
其中的MasterDataInit.cs是手动创建的,用来个性化地往数据库中添加指定初始数据:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using S.Framework.DataCore.EntityFramework.EntityContexts; 8 using S.Framework.Entity.Master; 9 10 namespace S.Framework.DataAchieve.EntityFramework.Initializes 11 { 12 internal class MasterDataInit 13 { 14 internal static void DataInit(MasterEntityContext context) 15 { 16 //判断admin是否存在,若存在,不新增 17 var dbSet = context.Set<SysUser>(); 18 if (!dbSet.Any(a => a.UserName == "admin")) 19 { 20 var entity = new SysUser { ID = Guid.NewGuid().ToString(), UserName = "admin", Password = "123456", CreateDate = DateTime.Now, CreateUser = "admin" }; 21 //将用户对象附加给数据库上下文(这仅仅是内存级别的操作) 22 dbSet.Add(entity); 23 //数据库上下文保存更改(提交变更到数据库执行) 24 context.SaveChanges(); 25 } 26 } 27 } 28 } 29
然后还需要修改MasterMigrateConfiguration.Generate.cs,在Seed中调用一下MasterDataInit的方法即可。
编译,重新登录一下试试,没问题就好。
统一数据库配置信息
创建类库项目,名称S.Framework,定义一个数据库配置类(DatabaseConfig.cs):
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using S.Utilities; 8 9 namespace S.Framework 10 { 11 public class DatabaseConfig 12 { 13 /// <summary> 14 /// web.config文件中数据库连接字符串的名称 15 /// </summary> 16 public static string ConnectionStringName = "matrixkey"; 17 /// <summary> 18 /// 默认的数据库管道名称 19 /// </summary> 20 public static readonly string DefaultProviderName = "System.Data.SqlClient"; 21 22 private static string _connectioonString = string.Empty; 23 private static string _providerName = string.Empty; 24 25 /// <summary> 26 /// 数据连接字符串 27 /// </summary> 28 public static string ConnectionString 29 { 30 get 31 { 32 if (string.IsNullOrEmpty(_connectioonString)) 33 { 34 string tempConnectionString = string.Empty, tempProviderName = string.Empty; 35 if (ConfigHelper.TryGetConnectionInfo(ConnectionStringName, out tempConnectionString, out tempProviderName)) 36 { 37 _connectioonString = tempConnectionString; 38 _providerName = tempProviderName; 39 } 40 } 41 if (string.IsNullOrWhiteSpace(_connectioonString)) 42 { 43 throw ExceptionHelper.ThrowConfigException("不存在名为“" + ConnectionStringName + "”的数据库连接信息,请检查web.config文件中的设置。"); 44 } 45 return _connectioonString; 46 } 47 } 48 49 /// <summary> 50 /// 数据提供程序 51 /// </summary> 52 public static string ProviderName 53 { 54 get 55 { 56 if (string.IsNullOrEmpty(_providerName)) 57 { 58 _providerName = ConfigHelper.ProviderName(ConnectionStringName); 59 } 60 if (string.IsNullOrWhiteSpace(_providerName)) 61 { 62 _providerName = DefaultProviderName; 63 } 64 return _providerName; 65 } 66 } 67 68 /// <summary> 69 /// 表示WEB应用程序在初次使用数据库时,是否自动合并/迁移数据库。该属性只在 <see cref="DataDriven"/> 值为 EntityFramework 时有效。 70 /// 该属性配置项由 Web.config/configuration/appSettings/Database:Migrate 参数指定。 71 /// </summary> 72 public static bool IsMigrateDatabase 73 { 74 get 75 { 76 string tempIsMigrateDatabase = string.Empty; 77 if (ConfigHelper.TryGetAppSettingInfo("Database:Migrate", out tempIsMigrateDatabase)) 78 { } 79 return string.IsNullOrEmpty(tempIsMigrateDatabase) ? false : tempIsMigrateDatabase.ToBoolean(false); 80 } 81 } 82 } 83 } 84
在Web.config/appSettings中增加配置参数:
1 <!--表示WEB应用程序在初次使用数据库时,是否自动合并/迁移数据库。--> 2 <add key="Database:Migrate" value="true" />
然后就是使用定义的配置。
让WebUI层引用S.Framework,然后把WebUI层中的Global.asax中的
1 new MasterDatabaseInitializer().Initialize(true);
修改成
1 new MasterDatabaseInitializer().Initialize(S.Framework.DatabaseConfig.IsMigrateDatabase);
让S.Framework.DataAchieve层引用S.Framework,然后修改BaseRepository.cs的构造方法,把
1 this.Db = new MasterEntityContext("matrixkey");
修改成
1 this.Db = new MasterEntityContext(S.Framework.DatabaseConfig.ConnectionStringName);
总结一下,这篇日志演示了3处整理:
1、利用T4自动生成EF数据库上下文和实体配置工厂
2、利用T4自动生成数据库初始化策略和初始化类
3、统一配置数据库参数
下个章节将演示仓储接口的创建。
截止本章节,项目源码下载:点击下载(存在百度云盘中)