【asp.net core 系列】9 实战之 UnitOfWork以及自定义代码生成
0. 前言
在前一篇中我们创建了一个基于EF的数据查询接口实现基类,这一篇我将带领大家讲一下为这EF补充一些功能,并且提供一个解决避免写大量配置类的方案。
1. SaveChanges的外移
在之前介绍EF Core的时候,我们提到过使用EF需要在每次使用之后,调用一次SaveChanges将数据提交给数据库。在实际开发中,我们不能添加一条数据或者做一次修改就调用一次SaveChanges,这完全不现实。因为每次调用SaveChanges是EF向数据库提交变更的时候,所以EF推荐的是每次执行完用户的请求之后统一提交数据给数据库。
这样就会造成一个问题,可能也不是问题:我们需要一个接口来管理EF 的SaveChanges操作。
1.1 创建一个IUnitOfWork接口
通常我们会在Domain项目中添加一个IUnitOfWork接口,这个接口有一个方法就是SaveChanges,代码如下:
namespace Domain.Insfrastructure
{
public interface IUnitOfWork
{
void SaveChanges();
}
}
这个方法的意思表示到执行该方法的时候,一个完整的工作流程执行完成了。也就是说,当执行该方法后,当前请求不会再与数据库发生连接。
1.2 实现IUnitOfWork接口
在 Domain.Implement中添加IUnitOfWork实现类:
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的方法,那么问题来了,我们该在什么时候调用呢,或者说如何调用呢?
我的建议是创建一个ActionFilter,针对所有的控制器进行SaveChanges进行处理。当然了,也可以在控制器中持有一个IUnitOfWork的示例,然后在Action结束的时候,执行SaveChanges。不过这样存在一个问题,可能会存在遗漏的方法。所以我推荐这样操作,这里简单演示一下如何创建拦截器:
在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用来确保一次请求一个工作流程,简单的代码生成类让我们能让我们忽略那些繁重的创建同类代码的工作。
更多内容烦请关注我的博客《高先生小屋》

【asp.net core 系列】9 实战之 UnitOfWork以及自定义代码生成的更多相关文章
- asp.net core 系列之中间件进阶篇-编写自定义中间件(middleware)
中间件是被用到管道(pipeline)上来处理请求(request)和响应的(response). asp.net core 本身提供了一些内置的中间件,但是有一些场景,你可能会需要写一些自定义的中间 ...
- 【asp.net core 系列】10 实战之ActionFilter
0.前言 在上一篇中,我们提到了如何创建一个UnitOfWork并通过ActionFilter设置启用.这一篇我们将简单介绍一下ActionFilter以及如何利用ActionFilter,顺便补齐一 ...
- 【asp.net core 系列】6 实战之 一个项目的完整结构
0. 前言 在<asp.net core 系列>之前的几篇文章中,我们简单了解了路由.控制器以及视图的关系以及静态资源的引入,让我们对于asp.net core mvc项目有了基本的认识. ...
- 【asp.net core 系列】8 实战之 利用 EF Core 完成数据操作层的实现
0. 前言 通过前两篇,我们创建了一个项目,并规定了一个基本的数据层访问接口.这一篇,我们将以EF Core为例演示一下数据层访问接口如何实现,以及实现中需要注意的地方. 1. 添加EF Core 先 ...
- Asp.net Core 系列之--4.事务、日志及错误处理
ChuanGoing 2019-11-17 这篇原本时想把事务处理.日志处理.错误处理.授权于鉴权一并介绍完的,授权和鉴权我想结合自定义权限来介绍,全部放到这里篇幅可能太长,因此权限部分将会在下篇来介 ...
- 【目录】asp.net core系列篇
随笔分类 - asp.net core系列篇 asp.net core系列 68 Filter管道过滤器 摘要: 一.概述 本篇详细了解一下asp.net core filters,filter叫&q ...
- Asp.Net Core 中IdentityServer4 实战之角色授权详解
一.前言 前几篇文章分享了IdentityServer4密码模式的基本授权及自定义授权等方式,最近由于改造一个网关服务,用到了IdentityServer4的授权,改造过程中发现比较适合基于Role角 ...
- 【asp.net core 系列】 1 带你了解一下asp.net core
0. 前言 这是一个新的系列,名字是<ASP.NET Core 入门到实战>.这个系列主讲ASP.NET Core MVC,辅助一些前端的基础知识(能用来实现我们需要的即可,并非主讲).同 ...
- 【asp.net core 系列】- 11 Service层的实现样板
0.前言 在<asp.net core 系列>之实战系列中,我们在之前的篇幅中对项目有了一个大概的认知,也搭建了一个基础的项目骨架.那么就让我们继续完善这个骨架,让它更加丰满.这一篇,我将 ...
随机推荐
- poj2987 最大闭合权子图基础题
Firing Time Limit: 5000MS Memory Limit: 131072K Total Submissions: 10905 Accepted: 3291 Descript ...
- JavaSE案例-Bank
项目介绍 完成基本的银行业务功能 存款 取款 转账 查询余额 修改密码 修改预留手机号 注销账号 退出 任务分解: 定义三个基本类: BankTest():银行业务主程序 Bank(): 银行类,包含 ...
- 附件1:setTimeout与闭包
我在详细图解作用域链与闭包一文中的结尾留下了一个关于setTimeout与循环闭包的思考题. 利用闭包,修改下面的代码,让循环输出的结果依次为1, 2, 3, 4, 5 for (var i=1; i ...
- Android_存储之SharedPreferences
一.概述 SharedPreferences是一种轻量级的数据存储方式,采用键值对的存储方式. SharedPreferences只能存储少量数据,大量数据不能使用该方式存储,支持存储的数据类型有bo ...
- 【JVM】GCRoots和JVM的参数配置
如何理解GCRoots? 为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法.GC Roots是一组活跃的引用,通过一系列名为GC Roots的对象作为起始点,沿着该对象向下搜索,如果一 ...
- 【朝夕专刊】RabbitMQ消息的持久化优先级
欢迎大家阅读<朝夕Net社区技术专刊> 我们致力于.NetCore的推广和落地,为更好的帮助大家学习,方便分享干货,特创此刊!很高兴你能成为忠实读者,文末福利不要错过哦! 上篇文章介绍了R ...
- Rocket - decode - Term
https://mp.weixin.qq.com/s/CbBWdTlc_DESlUzd8KFcSg 介绍Term类各方法的实现. 1. prime 表示一个项是否是质项,即这 ...
- 【大厂面试02期】Redis过期key是怎么样清理的?
PS:本文已收录到1.1K Star数开源学习指南--<大厂面试指北>,如果想要了解更多大厂面试相关的内容,了解更多可以看 http://notfound9.github.io/inter ...
- 【JSP】el、jstl、MVC、三层架构
JSP[Java Server Pages] [概念] java服务器页面 简化的servlet设计 html语法中扩展java 跨平台 JSP全名为Java Server Pages,中文名叫jav ...
- 前端HTML 定位position 绝对定位 相对定位
>>>position:absolute;绝对定位 当前元素相对于父级元素位置[该父级元素必须也设定了position,不然会继续往上找祖先元素,直到body为止]的定位 >& ...