【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用来确保一次请求一个工做流程,简单的代码生成类让咱们能让咱们忽略那些繁重的建立同类代码的工做。

相关文章
相关标签/搜索