【asp.net core 系列】9 實戰之 UnitOfWork以及自定義代碼生成

0. 前言

在前一篇中咱們建立了一個基於EF的數據查詢接口實現基類,這一篇我將帶領你們講一下爲這EF補充一些功能,而且提供一個解決避免寫大量配置類的方案。數據庫

圖片

1. SaveChanges的外移

在以前介紹EF Core的時候,咱們提到過使用EF須要在每次使用以後,調用一次SaveChanges將數據提交給數據庫。在實際開發中,咱們不能添加一條數據或者作一次修改就調用一次SaveChanges,這徹底不現實。由於每次調用SaveChanges是EF向數據庫提交變動的時候,因此EF推薦的是每次執行完用戶的請求以後統一提交數據給數據庫。app

這樣就會形成一個問題,可能也不是問題:咱們須要一個接口來管理EF 的SaveChanges操做。ide

1.1 建立一個IUnitOfWork接口

一般咱們會在Domain項目中添加一個IUnitOfWork接口,這個接口有一個方法就是SaveChanges,代碼以下:工具

namespace Domain.Insfrastructure
{
   public interface IUnitOfWork
   {
       void SaveChanges();
   }
}

這個方法的意思表示到執行該方法的時候,一個完整的工做流程執行完成了。也就是說,當執行該方法後,當前請求不會再與數據庫發生鏈接。spa

1.2 實現IUnitOfWork接口

在 Domain.Implement中添加IUnitOfWork實現類:code

using Domain.Insfrastructure;
using Microsoft.EntityFrameworkCore;

namespace Domain.Implements.Insfrastructure
{
   public class UnitOfWork: IUnitOfWork
   {
       private DbContext DbContext;
       public UnitOfWork(DbContext context)
       {
           DbContext = context;
       }

       public void SaveChanges()
       {
           DbContext.SaveChanges();
       }
   }
}

1.3 調用時機

到如今咱們已經建立了一個UnitOfWork的方法,那麼問題來了,咱們該在何時調用呢,或者說如何調用呢?orm

個人建議是建立一個ActionFilter,針對全部的控制器進行SaveChanges進行處理。固然了,也能夠在控制器中持有一個IUnitOfWork的示例,而後在Action結束的時候,執行SaveChanges。不過這樣存在一個問題,可能會存在遺漏的方法。因此我推薦這樣操做,這裏簡單演示一下如何建立攔截器:blog

在Web的根目錄下,建立一個Filters目錄,這個目錄裏用來存儲一些過濾器,建立咱們須要的過濾器:繼承

using Domain.Insfrastructure;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Web.Filters
{
   public class UnitOfWorkFilterAttribute : ActionFilterAttribute
   {
       public IUnitOfWork UnitOfWork;

       public override void OnActionExecuted(ActionExecutedContext context)
       {
           UnitOfWork.SaveChanges();
       }
   }
}

使用一個ActionFilter能夠很方便的解決一些容易遺漏但又必須執行的代碼。這裏就先不介紹如何配置Filter的啓用和詳細介紹了,請容許我賣個關子。固然了,有些小夥伴確定也能猜到這是一個Attribute類,因此能夠按照Attribute給Controller打標記。接口

2. 建立一個簡單的代碼生成方法

以前在介紹EF的時候,有個小夥伴跟我說,還要寫配置文件啊,太麻煩了。是的,以前我介紹了不少關於寫配置文件不使用特性的好處,但不解決這個問題就沒法真正體檢配置類的好處。

雖說,EF Core約定優先,可是若是默認約定的話,得在DBContext中聲明 DbSet<T> 來聲明這個字段,實體類少的話,比較簡單。若是多個數據表的話,就會很是麻煩。

因此這時候就要使用工具類, 那麼簡單的分析一下,這個工具類須要有哪些功能:

  • 第一步,找到實體類並解析出實體類的類名

  • 第二步,生成配置文件

  • 第三步,建立對應的Repository接口和實現類

很簡單的三步,可是難點就是找實體類並解析出實體類名。

在Util項目中添加一個Develop目錄,並建立Develop類:

namespace Utils.Develop
{
   public class Develop
   {

   }
}

定位當前類所在目錄,經過

Directory.GetCurrentDirectory()

這個方法能夠獲取當前執行的DLL所在目錄,固然不一樣的編譯器在執行的時候,會有微妙的不一樣。因此咱們須要以此爲根據而後獲取項目的根目錄,一個簡單的方法,查找*.sln 所在目錄:

public static string CurrentDirect
{
   get
   {
       var execute = Directory.GetCurrentDirectory();
       var parent = Directory.GetParent(execute);
       while(parent.GetFiles("*.sln",SearchOption.TopDirectoryOnly).Length == 0)
       {
           parent = parent.Parent;
           if(parent == null)
           {
               return null;
           }
       }
       return parent.FullName;
   }
}

2.1 獲取實體類

那麼獲取到根目錄以後,咱們下一步就是獲取實體類。由於咱們的實體類都要求是繼承BaseEntity或者命名空間都是位於Data.Models下面。固然這個名稱都是根據實際業務場景約束的,這裏只是以當前項目舉例。那麼,咱們能夠經過如下方法找到咱們設置的實體類:

public static Type[] LoadEntities()
{
   var assembly = Assembly.Load("Data");
   var allTypes = assembly.GetTypes();
   var ofNamespace = allTypes.Where(t => t.Namespace == "Data.Models" || t.Namespace.StartsWith("Data.Models."));
   var subTypes = allTypes.Where(t => t.BaseType.Name == "BaseEntity`1");
   return ofNamespace.Union(subTypes).ToArray();
}

經過 Assembly加載Data的程序集,而後選擇出符合咱們要求的實體類。

2.2 編寫Repository接口

咱們先約定Model的Repository接口定義在 Domain/Repository目錄下,因此它們的命名空間應該是:

namespace Domain.Repository
{
}

假設目錄狀況與Data/Models下面的代碼結構保持一致,而後生成代碼應該以下:

public static void CreateRepositoryInterface(Type type)
{
   var targetNamespace = type.Namespace.Replace("Data.Models", "");
   if (targetNamespace.StartsWith("."))
   {
       targetNamespace = targetNamespace.Remove(0);
   }
   var targetDir = Path.Combine(new[]{CurrentDirect,"Domain", "Repository"}.Concat(
       targetNamespace.Split('.')).ToArray());
   if (!Directory.Exists(targetDir))
   {
       Directory.CreateDirectory(targetDir);
   }

   var baseName = type.Name.Replace("Entity","");

   if (!string.IsNullOrEmpty(targetNamespace))
   {
       targetNamespace = $".{targetNamespace}";
   }
   var file = $"using {type.Namespace};\r\n"
       + $"using Domain.Insfrastructure;\r\n"
       + $"namespace Domain.Repository{targetNamespace}\r\n"
       + "{\r\n"
       + $"\tpublic interface I{baseName}ModifyRepository : IModifyRepository<{type.Name}>\r\n" +
       "\t{\r\n\t}\r\n"
       + $"\tpublic interface I{baseName}SearchRepository : ISearchRepository<{type.Name}>\r\n" +
       "\t{\r\n\t}\r\n}";

   File.WriteAllText(Path.Combine(targetDir, $"{baseName}Repository.cs"), file);
}

2.3 編寫Repository的實現類

由於咱們提供了一個基類,因此咱們在生成方法的時候,推薦繼承這個類,那麼實現方法應該以下:

public static void CreateRepositoryImplement(Type type)
{
   var targetNamespace = type.Namespace.Replace("Data.Models", "");
   if (targetNamespace.StartsWith("."))
   {
       targetNamespace = targetNamespace.Remove(0);
   }

   var targetDir = Path.Combine(new[] {CurrentDirect, "Domain.Implements", "Repository"}.Concat(
       targetNamespace.Split('.')).ToArray());
   if (!Directory.Exists(targetDir))
   {
       Directory.CreateDirectory(targetDir);
   }
   var baseName = type.Name.Replace("Entity", "");
   if (!string.IsNullOrEmpty(targetNamespace))
   {
       targetNamespace = $".{targetNamespace}";
   }

   var file = $"using {type.Namespace};" +
       $"\r\nusing Domain.Implements.Insfrastructure;" +
       $"\r\nusing Domain.Repository{targetNamespace};" +
       $"\r\nusing Microsoft.EntityFrameworkCore;" +
       $"namespace Domain.Implements.Repository{targetNamespace}\r\n" +
       "{" +
       $"\r\n\tpublic class {baseName}Repository :BaseRepository<{type.Name}> ,I{baseName}ModifyRepository,I{baseName}SearchRepository " +
       "\r\n\t{" +
       $"\r\n\t\tpublic {baseName}Repository(DbContext context) : base(context)"+
       "\r\n\t\t{"+
       "\r\n\t\t}\r\n"+
       "\t}\r\n}";
   File.WriteAllText(Path.Combine(targetDir, $"{baseName}Repository.cs"), file);
}

2.4 配置文件的生成

仔細觀察一下代碼,能夠發現總體都是十分簡單的。因此這篇就不掩飾如何生成配置文件了,小夥伴們能夠自行嘗試一下哦。具體實現能夠等一下篇哦。

3. 總結

這一篇粗略的介紹了兩個用來輔助EF Core實現的方法或類,這在開發中很重要。UnitOfWork用來確保一次請求一個工做流程,簡單的代碼生成類讓咱們能讓咱們忽略那些繁重的建立同類代碼的工做。

相關文章
相關標籤/搜索