系列目錄
前言:
這應該是本系統最后一次重構,將重構BLL層和Model層。來完全取代代碼生成器生成的BLL層和DAL層。完全廢掉了代碼生成器的DAL,BLL,MODEL層。
全自動生成增,刪,改,查的通用方法和模型轉換與BLL層的模型事務脫離,后續文章,會以一些插件或功能為目的,繼續完善,進行分享,最后60節的文章會對本系統做一個總結
(但是還沒時間寫,相信60節的文章能讓你快速了解到本系統的優勢和架構,就算你從未閱讀之前的所有文章)
繼上次的DAL層重構(上一節),本來只想重構DAL層算了,但是鑒於本人是代碼強迫症患者,所以花了些時間把BLL層重構。
在此務必共鳴一個問題,代碼重構不是架構改變,這個系統的架構完全還是原來的接口多層注入架構!如下圖所示完全不變
最后必須讓初學者理解一個知識點:分部類 partial 關鍵字,因為我們的重構是圍繞分部類而實現,包括接口
partial 關鍵字指示可在命名空間中定義該類、結構或接口的其他部分。所有部分都必須使用 partial 關鍵字。在編譯時,各個部分都必須可用來形成最終的類型。各個部分必須具有相同的可訪問性,如 public、private 等。
如果將任意部分聲明為抽象的,則整個類型都被視為抽象的。如果將任意部分聲明為密封的,則整個類型都被視為密封的。如果任意部分聲明基類型,則整個類型都將繼承該類。
指定基類的所有部分必須一致,但忽略基類的部分仍繼承該基類型。各個部分可以指定不同的基接口,最終類型將實現所有分部聲明所列出的全部接口。在某一分部定義中聲明的任何類、結構或接口成員可供所有其他部分使用。最終類型是所有部分在編譯時的組合。
下列聲明:
partial class Earth : Planet, IRotate { } partial class Earth : IRevolve { }
等效於下列聲明:
class Earth : Planet, IRotate, IRevolve { }
1.改變現狀
相比我們DAL層,重構BLL層是有技術難度的,因為業務層涉及模型的轉換構成,雖然只重構模塊的(增、刪、改、查),下面我們開始
下載上一節代碼(https://yunpan.cn/cYUdjssbmiLrL 訪問密碼 e622)來分析業務層。
分析:IBLL,BLL
IBLL層不用說了,跟IDAL層是一致的
所以我們直接復制IDAL的TT模版修改后如下

<#@ template language="C#" debug="false" hostspecific="true"#> <#@ include file="../../Apps.Models/Common.ttinclude"#><#@ output extension=".cs"#> <# const string inputFile = @"../../Apps.Models/DB.edmx"; var textTransform = DynamicTextTransformation.Create(this); var code = new CodeGenerationTools(this); var ef = new MetadataTools(this); var typeMapper = new TypeMapper(code, ef, textTransform.Errors); var fileManager = EntityFrameworkTemplateFileManager.Create(this); var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile); var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef); if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile)) { return string.Empty; } WriteHeader(codeStringGenerator, fileManager); foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection)) { fileManager.StartNewFile("I"+entity.Name + "BLL.cs"); #> using System; using Apps.Common; using System.Collections.Generic; using Apps.Models<#=entity.Name.IndexOf("_")>0?"."+entity.Name.Substring(0,entity.Name.IndexOf("_")):".Sys" #>; namespace Apps.IBLL { public partial interface I<#=entity.Name #>BLL { List<<#=entity.Name #>Model> GetList(ref GridPager pager, string queryStr); bool Create(ref ValidationErrors errors, <#=entity.Name #>Model model); bool Delete(ref ValidationErrors errors, string id); bool Delete(ref ValidationErrors errors, string[] deleteCollection); bool Edit(ref ValidationErrors errors, <#=entity.Name #>Model model); <#=entity.Name #>Model GetById(string id); bool IsExists(string id); } <# EndNamespace(code); } fileManager.Process(); #>
非常好。業務層完成跟預期是一樣的!這樣我們直接可以看到我們原來的ISysSample可以由
using System.Collections.Generic; using Apps.Common; using Apps.Models.Sys; namespace Apps.IBLL { public interface ISysSampleBLL { List<SysSampleModel> GetList(ref GridPager pager, string queryStr); bool Create(ref ValidationErrors errors, SysSampleModel model); bool Delete(ref ValidationErrors errors, string id); bool Delete(ref ValidationErrors errors, string[] deleteCollection); bool Edit(ref ValidationErrors errors, SysSampleModel model); SysSampleModel GetById(string id); bool IsExist(string id); } }
變為--->
using System.Collections.Generic; using Apps.Common; using Apps.Models.Sys; namespace Apps.IBLL { public partial interface ISysSampleBLL { } }
代碼行數發生質的改變,可以我們就可以擴展自己的接口方法,利用partial類
照樣畫葫蘆,業務層也生成
直接上TT代碼

<#@ template language="C#" debug="false" hostspecific="true"#> <#@ include file="../../Apps.Models/Common.ttinclude"#><#@ output extension=".cs"#> <# const string usingName = ""; const string inputFile = @"../../Apps.Models/DB.edmx"; var textTransform = DynamicTextTransformation.Create(this); var code = new CodeGenerationTools(this); var ef = new MetadataTools(this); var typeMapper = new TypeMapper(code, ef, textTransform.Errors); var fileManager = EntityFrameworkTemplateFileManager.Create(this); var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile); var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef); if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile)) { return string.Empty; } WriteHeader(codeStringGenerator, fileManager); foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection)) { if(entity.Name.StartsWith("Sys") || entity.Name.StartsWith("JOB")) { fileManager.StartNewFile(""+entity.Name + "BLL.cs"); var simpleProperties = typeMapper.GetSimpleProperties(entity); #> using System; using System.Collections.Generic; using System.Linq; using Apps.Models; using Apps.Common; using Microsoft.Practices.Unity; using System.Transactions; using Apps.IBLL; using Apps.IDAL; using Apps.BLL.Core; using Apps.Locale; using Apps.Models<#=entity.Name.IndexOf("_")>0?"."+entity.Name.Substring(0,entity.Name.IndexOf("_")):".Sys" #>; namespace Apps.BLL { public partial class <#=entity.Name #>BLL:I<#=entity.Name #>BLL { [Dependency] public I<#=entity.Name #>Repository m_Rep { get; set; } public virtual List<<#=entity.Name #>Model> GetList(ref GridPager pager, string queryStr) { IQueryable<<#=entity.Name #>> queryData = null; if (!string.IsNullOrWhiteSpace(queryStr)) { queryData = m_Rep.GetList( <# int i =0; if (simpleProperties.Any()){foreach(var edmProperty in simpleProperties){ if(i==0) { #> <#=codeStringGenerator.Property(edmProperty).ToString().IndexOf("string")>0?"a=>a."+edmProperty+".Contains(queryStr)":""#> <# if(codeStringGenerator.Property(edmProperty).ToString().IndexOf("string")>0) { i=1; } } else if(i==1) {#> <#=codeStringGenerator.Property(edmProperty).ToString().IndexOf("string")>0?"|| a."+edmProperty+".Contains(queryStr)":""#> <# } #> <#} }#> ); } else { queryData = m_Rep.GetList(); } pager.totalRows = queryData.Count(); //排序 queryData = LinqHelper.SortingAndPaging(queryData, pager.sort, pager.order, pager.page, pager.rows); return CreateModelList(ref queryData); } public virtual List<<#=entity.Name #>Model> CreateModelList(ref IQueryable<<#=entity.Name #>> queryData) { List<<#=entity.Name #>Model> modelList = (from r in queryData select new <#=entity.Name #>Model { <# if (simpleProperties.Any()) { foreach(var edmProperty in simpleProperties) { #> <#=edmProperty#> = r.<#=edmProperty#>, <# } } #> }).ToList(); return modelList; } public virtual bool Create(ref ValidationErrors errors, <#=entity.Name #>Model model) { try { <#=entity.Name #> entity = m_Rep.GetById(model.Id); if (entity != null) { errors.Add(Resource.PrimaryRepeat); return false; } entity = new <#=entity.Name #>(); <# if (simpleProperties.Any()) { foreach(var edmProperty in simpleProperties) { #> entity.<#=edmProperty#> = model.<#=edmProperty#>; <# } } #> if (m_Rep.Create(entity)) { return true; } else { errors.Add(Resource.InsertFail); return false; } } catch (Exception ex) { errors.Add(ex.Message); ExceptionHander.WriteException(ex); return false; } } public virtual bool Delete(ref ValidationErrors errors, string id) { try { if (m_Rep.Delete(id) == 1) { return true; } else { return false; } } catch (Exception ex) { errors.Add(ex.Message); ExceptionHander.WriteException(ex); return false; } } public virtual bool Delete(ref ValidationErrors errors, string[] deleteCollection) { try { if (deleteCollection != null) { using (TransactionScope transactionScope = new TransactionScope()) { if (m_Rep.Delete(deleteCollection) == deleteCollection.Length) { transactionScope.Complete(); return true; } else { Transaction.Current.Rollback(); return false; } } } return false; } catch (Exception ex) { errors.Add(ex.Message); ExceptionHander.WriteException(ex); return false; } } public virtual bool Edit(ref ValidationErrors errors, <#=entity.Name #>Model model) { try { <#=entity.Name #> entity = m_Rep.GetById(model.Id); if (entity == null) { errors.Add(Resource.Disable); return false; } <# if (simpleProperties.Any()) { foreach(var edmProperty in simpleProperties) { #> entity.<#=edmProperty#> = model.<#=edmProperty#>; <# } } #> if (m_Rep.Edit(entity)) { return true; } else { errors.Add("沒有數據改變"); return false; } } catch (Exception ex) { errors.Add(ex.Message); ExceptionHander.WriteException(ex); return false; } } public virtual <#=entity.Name #>Model GetById(string id) { if (IsExists(id)) { <#=entity.Name #> entity = m_Rep.GetById(id); <#=entity.Name #>Model model = new <#=entity.Name #>Model(); <# if (simpleProperties.Any()) { foreach(var edmProperty in simpleProperties) { #> model.<#=edmProperty#> = entity.<#=edmProperty#>; <# } } #> return model; } else { return null; } } public virtual bool IsExists(string id) { return m_Rep.IsExist(id); } public void Dispose() { } } <# EndNamespace(code); } } fileManager.Process(); #>
由於每一個業務模型的屬性都不一致,這里不能用List<T>來做,所以,一個表會生成一個BLL類。(圖中紅色部分)
如果生成紅色部分。主要看下面代碼
<# if (simpleProperties.Any()) { foreach(var edmProperty in simpleProperties) { #> <#=edmProperty#> = r.<#=edmProperty#>, <# } } #>
獲取表模型的所有屬性,所有這段對很多人是有幫助的,請收藏,說不定你以后要用到
OK,編譯通過,運行正確,還是熟悉的面孔
但是至此。我們的業務層和數據訪問層,可以說是一行代碼都沒寫。足夠體現了TT模版的強大之處,相比我們之前要用代碼生成器來得極其方便
2.引發問題
直到上面步驟,一切都很順利,沒有一點不妥。
有經驗的園友會發現,里面東西都是寫死的。而且分部類不可以重寫自己。
比如說。我在處理 entity.Name = model.Name;時候我想entity.Name = model.Name.TrimStart() 去掉字符串前面的空格,那么可以看到根本無法操作。
然而我們需要重寫,但是又發現無法重寫分部類的方法,怎么做?必須用一張圖來看,我是這么做的
- 綠色是我們已經重構完成的。
- 紫色是我們需要重構的一個TT模版,這是所有都是虛方法的類
- 粉色是我們自己擴張的業務方法,目前為空
虛方法是可以重寫的關鍵字是virtual 以下重寫之后優先級高於前者 用override。用代碼來說明
改變一下CommonBLL.tt

<#@ template language="C#" debug="false" hostspecific="true"#> <#@ include file="../../Apps.Models/Common.ttinclude"#><#@ output extension=".cs"#> <# const string usingName = ""; const string inputFile = @"../../Apps.Models/DB.edmx"; var textTransform = DynamicTextTransformation.Create(this); var code = new CodeGenerationTools(this); var ef = new MetadataTools(this); var typeMapper = new TypeMapper(code, ef, textTransform.Errors); var fileManager = EntityFrameworkTemplateFileManager.Create(this); var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile); var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef); if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile)) { return string.Empty; } WriteHeader(codeStringGenerator, fileManager); foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection)) { if(entity.Name.StartsWith("Sys") || entity.Name.StartsWith("JOB")) { fileManager.StartNewFile("Virtual_"+entity.Name + "BLL.cs"); var simpleProperties = typeMapper.GetSimpleProperties(entity); #> using System; using System.Collections.Generic; using System.Linq; using Apps.Models; using Apps.Common; using Microsoft.Practices.Unity; using System.Transactions; using Apps.IBLL; using Apps.IDAL; using Apps.BLL.Core; using Apps.Locale; using Apps.Models<#=entity.Name.IndexOf("_")>0?"."+entity.Name.Substring(0,entity.Name.IndexOf("_")):".Sys" #>; namespace Apps.BLL { public class Virtual_<#=entity.Name #>BLL { [Dependency] public I<#=entity.Name #>Repository m_Rep { get; set; } public virtual List<<#=entity.Name #>Model> GetList(ref GridPager pager, string queryStr) { IQueryable<<#=entity.Name #>> queryData = null; if (!string.IsNullOrWhiteSpace(queryStr)) { queryData = m_Rep.GetList( <# int i =0; if (simpleProperties.Any()){foreach(var edmProperty in simpleProperties){ if(i==0) { #> <#=codeStringGenerator.Property(edmProperty).ToString().IndexOf("string")>0?"a=>a."+edmProperty+".Contains(queryStr)":""#> <# if(codeStringGenerator.Property(edmProperty).ToString().IndexOf("string")>0) { i=1; } } else if(i==1) {#> <#=codeStringGenerator.Property(edmProperty).ToString().IndexOf("string")>0?"|| a."+edmProperty+".Contains(queryStr)":""#> <# } #> <#} }#> ); } else { queryData = m_Rep.GetList(); } pager.totalRows = queryData.Count(); //排序 queryData = LinqHelper.SortingAndPaging(queryData, pager.sort, pager.order, pager.page, pager.rows); return CreateModelList(ref queryData); } public virtual List<<#=entity.Name #>Model> CreateModelList(ref IQueryable<<#=entity.Name #>> queryData) { List<<#=entity.Name #>Model> modelList = (from r in queryData select new <#=entity.Name #>Model { <# if (simpleProperties.Any()) { foreach(var edmProperty in simpleProperties) { #> <#=edmProperty#> = r.<#=edmProperty#>, <# } } #> }).ToList(); return modelList; } public virtual bool Create(ref ValidationErrors errors, <#=entity.Name #>Model model) { try { <#=entity.Name #> entity = m_Rep.GetById(model.Id); if (entity != null) { errors.Add(Resource.PrimaryRepeat); return false; } entity = new <#=entity.Name #>(); <# if (simpleProperties.Any()) { foreach(var edmProperty in simpleProperties) { #> entity.<#=edmProperty#> = model.<#=edmProperty#>; <# } } #> if (m_Rep.Create(entity)) { return true; } else { errors.Add(Resource.InsertFail); return false; } } catch (Exception ex) { errors.Add(ex.Message); ExceptionHander.WriteException(ex); return false; } } public virtual bool Delete(ref ValidationErrors errors, string id) { try { if (m_Rep.Delete(id) == 1) { return true; } else { return false; } } catch (Exception ex) { errors.Add(ex.Message); ExceptionHander.WriteException(ex); return false; } } public virtual bool Delete(ref ValidationErrors errors, string[] deleteCollection) { try { if (deleteCollection != null) { using (TransactionScope transactionScope = new TransactionScope()) { if (m_Rep.Delete(deleteCollection) == deleteCollection.Length) { transactionScope.Complete(); return true; } else { Transaction.Current.Rollback(); return false; } } } return false; } catch (Exception ex) { errors.Add(ex.Message); ExceptionHander.WriteException(ex); return false; } } public virtual bool Edit(ref ValidationErrors errors, <#=entity.Name #>Model model) { try { <#=entity.Name #> entity = m_Rep.GetById(model.Id); if (entity == null) { errors.Add(Resource.Disable); return false; } <# if (simpleProperties.Any()) { foreach(var edmProperty in simpleProperties) { #> entity.<#=edmProperty#> = model.<#=edmProperty#>; <# } } #> if (m_Rep.Edit(entity)) { return true; } else { errors.Add(Resource.NoDataChange); return false; } } catch (Exception ex) { errors.Add(ex.Message); ExceptionHander.WriteException(ex); return false; } } public virtual <#=entity.Name #>Model GetById(string id) { if (IsExists(id)) { <#=entity.Name #> entity = m_Rep.GetById(id); <#=entity.Name #>Model model = new <#=entity.Name #>Model(); <# if (simpleProperties.Any()) { foreach(var edmProperty in simpleProperties) { #> model.<#=edmProperty#> = entity.<#=edmProperty#>; <# } } #> return model; } else { return null; } } public virtual bool IsExists(string id) { return m_Rep.IsExist(id); } public void Dispose() { } } <# EndNamespace(code); } } fileManager.Process(); #>
更Common代碼基本一致,只是頭部變了,文件名稱變了
public class Virtual_SysSampleBLL
那么重新創建一個CommonBLL.tt

<#@ template language="C#" debug="false" hostspecific="true"#> <#@ include file="../../Apps.Models/Common.ttinclude"#><#@ output extension=".cs"#> <# const string usingName = ""; const string inputFile = @"../../Apps.Models/DB.edmx"; var textTransform = DynamicTextTransformation.Create(this); var code = new CodeGenerationTools(this); var ef = new MetadataTools(this); var typeMapper = new TypeMapper(code, ef, textTransform.Errors); var fileManager = EntityFrameworkTemplateFileManager.Create(this); var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile); var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef); if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile)) { return string.Empty; } WriteHeader(codeStringGenerator, fileManager); foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection)) { if(entity.Name.StartsWith("Sys") || entity.Name.StartsWith("JOB")) { fileManager.StartNewFile(entity.Name + "BLL.cs"); var simpleProperties = typeMapper.GetSimpleProperties(entity); #> using System; using System.Collections.Generic; using System.Linq; using Apps.Models; using Apps.Common; using Microsoft.Practices.Unity; using System.Transactions; using Apps.IBLL; using Apps.IDAL; using Apps.BLL.Core; using Apps.Locale; using Apps.Models<#=entity.Name.IndexOf("_")>0?"."+entity.Name.Substring(0,entity.Name.IndexOf("_")):".Sys" #>; namespace Apps.BLL { public partial class <#=entity.Name #>BLL: Virtual_<#=entity.Name #>BLL,I<#=entity.Name #>BLL { } <# EndNamespace(code); } } fileManager.Process(); #>
代碼生成后如下,什么都沒有實現繼承接口,和上面的TT模版的類
namespace Apps.BLL { public partial class SysSampleBLL: Virtual_SysSampleBLL,ISysSampleBLL { } }
好吧,我只是想省掉寫: Virtual_SysSampleBLL,ISysSampleBLL
OK,運行之后還是熟悉的面孔,但是可以重載了,我們重載一下,好處理我們的業務!
新建SysSampleBLL.cs
namespace Apps.BLL { public partial class SysSampleBLL { public override bool Create(ref ValidationErrors errors, SysSampleModel model) { try { SysSample entity = m_Rep.GetById(model.Id); if (entity != null) { errors.Add(Resource.PrimaryRepeat); return false; } entity = new SysSample(); entity.Id = model.Id; entity.Name = model.Name.TrimStart(); entity.Age = model.Age; entity.Bir = model.Bir; entity.Photo = model.Photo; entity.Note = model.Note; entity.CreateTime = model.CreateTime; if (m_Rep.Create(entity)) { return true; } else { errors.Add(Resource.InsertFail); return false; } } catch (Exception ex) { errors.Add(ex.Message); ExceptionHander.WriteException(ex); return false; } } } }
同樣的。我們可以對Model層進行重構,類似BLL層。利用虛屬性,可以對屬性進行注解。來獲得優先級,和一次生成編譯通過
//------------------------------------------------------------------------------ // <auto-generated> // 此代碼已從模板生成。 // // 手動更改此文件可能導致應用程序出現意外的行為。 // 如果重新生成代碼,將覆蓋對此文件的手動更改。 // </auto-generated> //------------------------------------------------------------------------------ using Apps.Models; using System; namespace Apps.Models.Sys { public class Virtual_SysSampleModel { public virtual string Id { get; set; } public virtual string Name { get; set; } public virtual Nullable<int> Age { get; set; } public virtual Nullable<System.DateTime> Bir { get; set; } public virtual string Photo { get; set; } public virtual string Note { get; set; } public virtual Nullable<System.DateTime> CreateTime { get; set; } } }
//------------------------------------------------------------------------------ // <auto-generated> // 此代碼已從模板生成。 // // 手動更改此文件可能導致應用程序出現意外的行為。 // 如果重新生成代碼,將覆蓋對此文件的手動更改。 // </auto-generated> //------------------------------------------------------------------------------ using Apps.Models; using System; namespace Apps.Models.Sys { public partial class SysSampleModel:Virtual_SysSampleModel { } }
然后自己建Model對其重載
-------------------------------------------------------------------丑陋的分割線----------------------------------------------------------------------------------------
到此,我們重構了DAL層和BLL層。對比原來的代碼生成器方式。我們新建一個表不用再生成DAL層和BLL層的代碼。直達界面
利用代碼生成器獲得控制器和View視圖。直接得到界面。一個字爽。大家可以下載代碼來研究
代碼生成器在第一節下載,但是代碼生成器本人很久沒有維護,可能生成的index.cshtml會有一些問題,但是好很好解決。自己花點時間來設計成自己的前端生成器。
OK、本文到此結束,謝謝