DDD CRUD 数据库操作日志 单元测试
从零开始写一个领域模型的框架
每篇文章都会打一个对应的 tag
Github 仓库地址
完成一个业务模块 (单表增删改查,应用层,领域层,仓储层,工作单元)
创建一个单元测试 (在开始写前端代码之前都使用单元测试调试代码)
EF操作数据库的CRUD操作都记录一下日志 (暂时把日志写入到了文本文件,后续要转成 Redis或者消息队列 在保存到mongoDB)
领域模型
应用层 和 领域层
一个业务代码是放在业务层还是领域层要看这个业务每一步的具体情况。
根据查询条件获取用户数据
直接在应用层构建查询参数,通过仓储层就可以得到用户的领域对象
获取某个用户的部门,手机号,朋友,客户,领导
通过仓储层获取到这个用户的信息,返回一个UserDomain
姓名、年龄、手机这些属性是用户的基本信息就在UserEntity上面,通过领域模型的Get方法可以直接获取
个人所在部门、朋友、客户也是用户的属性,调用UserDomain中的方法获取这些属性
UserDomain.GetDept() 返回 DempDomain
UserDomain.GetFriends() 返回 List<FriendDomain>
获取指定条件的数据就需要用仓储层去获取
UserRepository.Finds(参数,指定的朋友) 返回 List<FriendDomain>
其他一些复杂的操作
给该用户发送短信,邮件
调用接口
领域对象转Dto
只要感觉不能划分到某个领域对象上并且也不是数据操作就放到业务层
通过领域对象获取数据和执行SQL获取数据的对比
SQL获取员工和所在部门的数据一条SQL就完事了
领域模型要先获取员工信息,再把 UserEntity 转成 UserDomain,再通过 UserDomain 获取到 DeptDomain
SQL可以随意写
领域模型的每次都只能是根据Id获取单条或者多条数据
三层执行SQL Select 后面只需要返回指定的字段
领域模型执行SQL Select 后面是这个表的全部字段
领域模型的优缺点
开发维护成本低
运行效率比较低
举例:
公司内部新增了一个业务,每个员工都可以参与,员工完成业绩之后给直属部门的领导发个消息。。。。。。等一系列的操作
领域模型
应用层写一个新的方法
调用仓储获取到一个UserDomain
通过UserDomain获取到直属领导的UserDomain
调用UserDomain发送消息的动作
通过UserDomain获取到部门信息DeptDomain
部门业绩提升
执行完成之后就把事务整个提交了
编写整个逻辑的时候,是以业务为维度进行编写的,一个应用层的方法就是一个业务,这个业务涉及到那些领域就调用那些领域中的方法
不管是老员工还是新员工只要大家都按照这个规范写不会差太多,整个项目运行几年后也不会太混乱,不会有太多的技术债。
单元测试
创建一个单元测试项目
创建一个基类,对项目进行初始化
TestClassBase.cs 单元测试基类,初始化配置信息,初始化IoC容器,初始化项目全局 Global 类 using Autofac; using Core2022.Framework; using Core2022.Framework.Commons.Autofac; using Microsoft.Extensions.Configuration; namespace Core2022.Application.Services.Test { public class TestClassBase { ContainerBuilder builder = new ContainerBuilder(); IContainer rootContainer; public TestClassBase() { // 初始化 appsettings IConfiguration configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); Global.InitAppSettings(configuration); // 初始化 Autofac 容器 // Autofac 注入Orm对象 builder.AutofacInjectionOrmModel(); // Autofac 注入各层之间的依赖 builder.AutofacInjectionServices(); rootContainer = builder.Build(); Global.AppAutofacContainer(rootContainer); } } }
UserAppServiceTest.cs 完成 CreateUser 方法的单元测试 using Core2022.Application.Services.DTO.User; using Core2022.Application.Services.Interface; using Core2022.Framework; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Core2022.Application.Services.Test { [TestClass] public class UserAppServiceTest : TestClassBase { IUserAppService userAppService; public UserAppServiceTest() { userAppService = Global.GetT<IUserAppService>(); } [TestMethod] public void CreateUser() { var respDto = userAppService.CreateUser(new UserRequestDto() { UserName = "222333", PassWord = "111111" }); } } }
EF操作日志
using Core2022.Framework.Entity; using Core2022.Framework.Settings; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; namespace Core2022.Framework.UnitOfWork { public class AppUnitOfWork : DbContext, IUnitOfWork { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(Global.ConnectionString); base.OnConfiguring(optionsBuilder); } protected override void OnModelCreating(ModelBuilder modelBuilder) { Global.OrmModelInit.ForEach(t => { modelBuilder.Model.AddEntityType(t); }); base.OnModelCreating(modelBuilder); } public DbSet<OrmEntity> CreateSet<OrmEntity>() where OrmEntity : class { return base.Set<OrmEntity>(); } public new int SaveChanges() { EFLog(); return base.SaveChanges(); } #region EF Log private void EFLog() { IEnumerable<EntityEntry> list = this.ChangeTracker.Entries(); foreach (var item in list) { //对应的表名 string tableName = ""; #region 获取表名 Type type = item.Entity.GetType(); Type patientMngAttrType = typeof(TableAttribute); TableAttribute attribute = null; if (type.IsDefined(patientMngAttrType, true)) { attribute = type.GetCustomAttributes(patientMngAttrType, true).FirstOrDefault() as TableAttribute; if (attribute != null) { tableName = attribute.Name; } } if (string.IsNullOrEmpty(tableName)) { tableName = type.Name; } #endregion BaseOrmModel model = item.Entity as BaseOrmModel; #region EntityState switch (item.State) { //case EntityState.Detached: //case EntityState.Unchanged: //case EntityState.Deleted: case EntityState.Modified: model.UpdateTime = DateTime.Now; model.Version = ++model.Version; WriteEFUpdateLog(item, tableName); break; case EntityState.Added: model.UpdateTime = DateTime.Now; model.CreateTime = DateTime.Now; model.IsDelete = false; model.Version = 0; WriteEFCreateLog(item, tableName); break; } #endregion } } private void WriteEFCreateLog(EntityEntry entry, string tableName) { var propertyList = entry.CurrentValues.Properties; string userName = "系统用户"; PropertyEntry keyEntity = entry.Property("KeyId"); Dictionary<string, object> dic = new Dictionary<string, object>(); foreach (var prop in propertyList) { PropertyEntry entity = entry.Property(prop.Name); dic.Add(prop.Name, entity.CurrentValue); } DBLogCreateModel createLog = new DBLogCreateModel() { CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TableName = tableName, OperatorUserName = userName, PrimaryKeyId = Guid.Parse(keyEntity.CurrentValue.ToString()), CreateValue = JsonConvert.SerializeObject(dic) }; WriteLog(createLog); } /// <summary> /// 记录EF修改操作日志 /// </summary> /// <param name="entry"></param> /// <param name="tableName"></param> private void WriteEFUpdateLog(EntityEntry entry, string tableName) { var propertyList = entry.CurrentValues.Properties.Where(i => entry.Property(i.Name).IsModified); string userName = "系统用户"; PropertyEntry keyEntity = entry.Property("KeyId"); foreach (var prop in propertyList) { PropertyEntry entity = entry.Property(prop.Name); DBLogUpdateModel updateLog = new DBLogUpdateModel() { CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), CulumnName = prop.Name, TableName = tableName, OperatorUserName = userName, CurrentValue = entity.CurrentValue, OriginalValue = entity.OriginalValue, PrimaryKeyId = Guid.Parse(keyEntity.CurrentValue.ToString()), }; if (entity.OriginalValue == null || entity.CurrentValue == null) { if (entity.OriginalValue != entity.CurrentValue) { WriteLog(updateLog); } continue; } if (!entity.OriginalValue.Equals(entity.CurrentValue)) { WriteLog(updateLog); } } } private void WriteLog(DBLogBaseModel updateLog) { updateLog.OperatorKeyId = ""; DBLog.WriteLog(updateLog); } #endregion } }
增加、按照每行数据为单位记录 (操作人)
修改删除、按照每个字段为单位记录 (原始值,修改值,修改时间,修改人等信息)
查询、查询条件,消耗时间等信息 (下个版本完成)
操作人信息由于缺少全局的验证所以该版本是写死的