基于.net core 2.0+mysql+AceAdmin搭建一套快速开发框架 2018-10-23 15:32
前言
.net core已经出来一段时间了,相信大家对.net core的概念已经很清楚了,这里就不再赘述。笔者目前也用.net core做过一些项目,并且将以前framework下的一些经验移植到了.net core下,并结合.net core本身的一些特性整理成此框架,以供学习参考。如有不足之处,欢迎指正。
框架介绍
先来一张整体分层结构图
基础层
1.Cloud.Core项目是核心项目,主要实现缓存的操作、dapper操作、EF Repository、PageList、日志等操作
2.Cloud.Utility属于帮助类
领域层
3.Cloud.Entity实体对象,存放数据库映射实体、Fluent API配置、枚举字典、DbContext等
4.Cloud.UnitOfWork,操作数据库的网关,里面封装了对仓储的操作、dapper的操作、事务等
服务层
5.Cloud.Service 业务逻辑的实现
6.Cloud.Dto 数据传输对象,实体对象不直接和表现层接触,通过dto互转
表现层
7.Cloud.Framework,表现层框架,封装了超类controller,全局授权过滤器,全局异常过滤器,ActionFilter,HtmlHelper等操作
8.Cloud.Boss 启动项目
使用的技术
基于.net core 2.0的asp.net core mvc
基于.net core 2.0的ef
dapper
mysql
前端框架 aceAdmin
技术要点
1.实体基类定义
2.泛型仓储的封装
2.1仓储接口的定义,泛型约束T必须是BaseEntity类型
public interface IRepository<T> where T : BaseEntity { DatabaseFacade Database { get ; } IQueryable<T> Entities { get ; } int SaveChanges(); Task< int > SaveChangesAsync(); void Disposed(); bool Delete(List<T> entitys, bool isSaveChange = true ); bool Delete(T entity, bool isSaveChange = true ); Task< bool > DeleteAsync(List<T> entitys, bool isSaveChange = true ); Task< bool > DeleteAsync(T entity, bool isSaveChange = true ); Task<T> GetAsync(Expression<Func<T, bool >> predicate = null ); Task<List<T>> GetListAsync(Expression<Func<T, bool >> predicate = null ); T Get( object id); T Get(Expression<Func<T, bool >> predicate = null ); Task<T> GetAsync( object id); Task<IQueryable<T>> LoadAsync(Expression<Func<T, bool >> predicate = null ); bool Insert(List<T> entitys, bool isSaveChange = true ); bool Insert(T entity, bool isSaveChange = true ); Task< bool > InsertAsync(List<T> entitys, bool isSaveChange = true ); Task< bool > InsertAsync(T entity, bool isSaveChange = true ); bool Update(List<T> entitys, bool isSaveChange = true ); bool Update(T entity, bool isSaveChange = true , List< string > updatePropertyList = null ); Task< bool > UpdateAsync(List<T> entitys, bool isSaveChange = true ); Task< bool > UpdateAsync(T entity, bool isSaveChange = true , List< string > updatePropertyList = null ); } |
2.2仓储接口的实现
public class Repository<T> : IRepository<T> where T : BaseEntity { DbContext _dbContext; public Repository(DbContext dbContext) { _dbContext = dbContext; } public int SaveChanges() { return _dbContext.SaveChanges(); } public async Task< int > SaveChangesAsync() { return await _dbContext.SaveChangesAsync(); } public void Disposed() { throw new Exception( "不允许在这里释放上下文,请在UnitOfWork中操作" ); _dbContext.Dispose(); } #region 插入数据 public bool Insert(T entity, bool isSaveChange = true ) { _dbContext.Set<T>().Add(entity); if (isSaveChange) { return SaveChanges() > 0; } return false ; } public async Task< bool > InsertAsync(T entity, bool isSaveChange = true ) { _dbContext.Set<T>().Add(entity); if (isSaveChange) { return await SaveChangesAsync() > 0; } return false ; } public bool Insert(List<T> entitys, bool isSaveChange = true ) { _dbContext.Set<T>().AddRange(entitys); if (isSaveChange) { return SaveChanges() > 0; } return false ; } public async Task< bool > InsertAsync(List<T> entitys, bool isSaveChange = true ) { _dbContext.Set<T>().AddRange(entitys); if (isSaveChange) { return await SaveChangesAsync() > 0; } return false ; } #endregion #region 更新数据 public bool Update(T entity, bool isSaveChange = true , List< string > updatePropertyList = null ) { if (entity== null ) { return false ; } _dbContext.Set<T>().Attach(entity); if (updatePropertyList== null ) { _dbContext.Entry<T>(entity).State = EntityState.Modified; //全字段更新 } else { updatePropertyList.ForEach(c => { _dbContext.Entry(entity).Property(c).IsModified = true ; //部分字段更新的写法 }); } if (isSaveChange) { return SaveChanges() > 0; } return false ; } public bool Update(List<T> entitys, bool isSaveChange = true ) { if (entitys== null ||entitys.Count==0) { return false ; } entitys.ForEach(c => { Update(c, false ); }); if (isSaveChange) { return SaveChanges() > 0; } return false ; } public async Task< bool > UpdateAsync(T entity, bool isSaveChange = true , List< string > updatePropertyList = null ) { if (entity == null ) { return false ; } _dbContext.Set<T>().Attach(entity); if (updatePropertyList == null ) { _dbContext.Entry<T>(entity).State = EntityState.Modified; //全字段更新 } else { updatePropertyList.ForEach(c => { _dbContext.Entry(entity).Property(c).IsModified = true ; //部分字段更新的写法 }); } if (isSaveChange) { return await SaveChangesAsync() > 0; } return false ; } public async Task< bool > UpdateAsync(List<T> entitys, bool isSaveChange = true ) { if (entitys == null || entitys.Count == 0) { return false ; } entitys.ForEach(c => { _dbContext.Set<T>().Attach(c); _dbContext.Entry<T>(c).State = EntityState.Modified; }); if (isSaveChange) { return await SaveChangesAsync() > 0; } return false ; } #endregion #region 删除 public bool Delete(T entity, bool isSaveChange = true ) { _dbContext.Set<T>().Attach(entity); _dbContext.Set<T>().Remove(entity); return isSaveChange ? SaveChanges() > 0 : false ; } public bool Delete(List<T> entitys, bool isSaveChange = true ) { entitys.ForEach(entity => { _dbContext.Set<T>().Attach(entity); _dbContext.Set<T>().Remove(entity); }); return isSaveChange ? SaveChanges() > 0 : false ; } public virtual async Task< bool > DeleteAsync(T entity, bool isSaveChange = true ) { _dbContext.Set<T>().Attach(entity); _dbContext.Set<T>().Remove(entity); return isSaveChange ? await SaveChangesAsync() > 0 : false ; } public virtual async Task< bool > DeleteAsync(List<T> entitys, bool isSaveChange = true ) { entitys.ForEach(entity => { _dbContext.Set<T>().Attach(entity); _dbContext.Set<T>().Remove(entity); }); return isSaveChange ? await SaveChangesAsync() > 0 : false ; } #endregion public IQueryable<T> Entities => _dbContext.Set<T>().AsQueryable().AsNoTracking(); //public async Task<IQueryable<T>> EntitiesAsync => Task.Run(()=> _dbContext.Set<T>().AsQueryable().AsNoTracking()); public DatabaseFacade Database => _dbContext.Database; #region 查找 public T Get( object id) { return _dbContext.Set<T>().Find(id); } public T Get(Expression<Func<T, bool >> predicate = null ) { return _dbContext.Set<T>().Where(predicate).AsNoTracking().FirstOrDefault(); } public async Task<T> GetAsync( object id) { return await _dbContext.Set<T>().FindAsync(id); } public async Task<T> GetAsync(Expression<Func<T, bool >> predicate = null ) { return await _dbContext.Set<T>().Where(predicate).AsNoTracking().FirstOrDefaultAsync(); } public async Task<List<T>> GetListAsync(Expression<Func<T, bool >> predicate = null ) { return await _dbContext.Set<T>().Where(predicate).AsNoTracking().ToListAsync(); } public async Task<IQueryable<T>> LoadAsync(Expression<Func<T, bool >> predicate = null ) { if (predicate == null ) { predicate = c => true ; } return await Task.Run(() => _dbContext.Set<T>().Where(predicate).AsNoTracking()); } public void Dispose() { throw new NotImplementedException(); } #endregion } |
3.表部分字段更新实现
EF默认的更新方式是一个实体对应的表全部字段更新,那么我们想更新表的部分字段怎么处理?
首先定义需要更新的字段:
public class PropertyExpression<T> where T : BaseEntity { private PropertyExpression() { } private static List< string > propertyList = new List< string >(); public static PropertyExpression<T> Init { get { propertyList.Clear(); return new PropertyExpression<T>(); } } public PropertyExpression<T> Property(Expression<Func<T, object >> expr) { var rtn = "" ; if (expr.Body is UnaryExpression) { rtn = ((MemberExpression)((UnaryExpression)expr.Body).Operand).Member.Name; } else if (expr.Body is MemberExpression) { rtn = ((MemberExpression)expr.Body).Member.Name; } else if (expr.Body is ParameterExpression) { rtn = ((ParameterExpression)expr.Body).Type.Name; } propertyList.Add(rtn); return this ; } public List< string > ToList() { return propertyList; } } |
EF更新的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public bool Update(T entity, bool isSaveChange = true , List< string > updatePropertyList = null ) { if (entity== null ) { return false ; } _dbContext.Set<T>().Attach(entity); if (updatePropertyList== null ) { _dbContext.Entry<T>(entity).State = EntityState.Modified; //全字段更新 } else { updatePropertyList.ForEach(c => { _dbContext.Entry(entity).Property(c).IsModified = true ; //部分字段更新的写法 }); } if (isSaveChange) { return SaveChanges() > 0; } return false ; } |
使用
1 2 3 4 5 6 7 8 | var entity = _unitOfWork.SysRoleRep.Get(model.RoleId); if (entity == null ) { throw new Exception( "要查找的对象不存在" ); } entity.Name = model.RoleName; var updatedPropertyList = PropertyExpression<Sys_Role>.Init.Property(c => c.Name).ToList(); _unitOfWork.SysRoleRep.Update(entity, true , updatedPropertyList); |
4.动态加载实体到DbContext
1 2 3 4 5 6 7 8 9 10 11 | public class EntityTypeConfiguration<T> : IEntityTypeConfiguration<T> where T : class { public void Configure(EntityTypeBuilder<T> builder) { RelyConfigure(builder); } public virtual void RelyConfigure(EntityTypeBuilder<T> builder) { } } |
1 2 3 4 5 6 7 8 9 10 | public class Sys_Error_LogConfiguration : EntityTypeConfiguration<Sys_Error_Log> { public override void RelyConfigure(EntityTypeBuilder<Sys_Error_Log> builder) { builder.ToTable( "sys_error_log" ); builder.HasKey(x => x.Id); base .RelyConfigure(builder); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public class EfDbContext : DbContext { public EfDbContext(DbContextOptions<EfDbContext> options) : base (options) { } //配置数据库连接 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { // optionsBuilder.UseSqlServer("xxxx connection string"); base .OnConfiguring(optionsBuilder); } //第一次使用EF功能时执行一次,以后不再执行 protected override void OnModelCreating(ModelBuilder modelBuilder) { //获取当前程序集中有基类并且基类是泛型的类 var typesToRegister = Assembly.GetExecutingAssembly().GetTypes().Where(c => c.BaseType != null && c.BaseType.IsGenericType).ToList(); foreach ( var type in typesToRegister) { //泛型定义相同 if (type.BaseType.GetGenericTypeDefinition() == typeof (EntityTypeConfiguration<>)) { dynamic configurationInstance = Activator.CreateInstance(type); modelBuilder.ApplyConfiguration(configurationInstance); } } base .OnModelCreating(modelBuilder); } } |
5.工作单元
工作单元是对仓储和事务的封装
原理参考:https://docs.microsoft.com/zh-cn/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | public class EfUnitOfWork : IUnitOfWork { private EfDbContext _dbContext; //每次请求上下文只会创建一个 public EfUnitOfWork(EfDbContext context) { this ._dbContext = context; } public int SaveChanges() { return _dbContext.SaveChanges(); } public async Task< int > SaveChangesAsync() { return await _dbContext.SaveChangesAsync(); } private bool disposed = false ; protected virtual void Dispose( bool disposing) { if (! this .disposed) { if (disposing) { _dbContext.Dispose(); //随着工作单元的销毁而销毁 } } this .disposed = true ; } public void Dispose() { Dispose( true ); GC.SuppressFinalize( this ); } public IDbContextTransaction BeginTransaction() { var scope = _dbContext.Database.BeginTransaction(); return scope; } public List<T> SqlQuery<T>( string sql, object param = null ) where T : class { var con= _dbContext.Database.GetDbConnection(); if (con.State!= ConnectionState.Open) { con.Open(); } var list= MysqlDapperReader.SqlQuery<T>(con, sql, param); return list; //throw new NotImplementedException(); } public Task<List<T>> SqlQueryAsync<T>( string sql, object param = null ) where T : class { throw new NotImplementedException(); } #region Sys Repository private IRepository<Sys_User> _sysUserRep; public IRepository<Sys_User> SysUserRep { get { if (_sysUserRep == null ) { //var s= HttpContext.Current.Items["currentUser"]; //var s = HttpContext.Current.RequestServices.GetService<IRepository<Sys_User>>(); //HttpContext.RequestServices.GetService<IRepository<Sys_User>>(); _sysUserRep = new Repository<Sys_User>(_dbContext); } return _sysUserRep; } } private IRepository<Sys_Role> _sysRoleRep; public IRepository<Sys_Role> SysRoleRep { get { if (_sysRoleRep == null ) { _sysRoleRep = new Repository<Sys_Role>(_dbContext); } return _sysRoleRep; } } private IRepository<Sys_Role_User> _sysRoleUserRep; public IRepository<Sys_Role_User> SysRoleUserRep { get { if (_sysRoleUserRep == null ) { _sysRoleUserRep = new Repository<Sys_Role_User>(_dbContext); } return _sysRoleUserRep; } } private IRepository<Sys_Permission> _sysPermissionRep; public IRepository<Sys_Permission> SysPermissionRep { get { if (_sysPermissionRep == null ) { _sysPermissionRep = new Repository<Sys_Permission>(_dbContext); } return _sysPermissionRep; } } private IRepository<Sys_Module> _sysModuleRep; public IRepository<Sys_Module> SysModuleRep { get { if (_sysModuleRep == null ) { _sysModuleRep = new Repository<Sys_Module>(_dbContext); } return _sysModuleRep; } } private IRepository<Sys_Error_Log> _sysErrorLogRep; public IRepository<Sys_Error_Log> SysErrorLogRep { get { if (_sysErrorLogRep == null ) { _sysErrorLogRep = new Repository<Sys_Error_Log>(_dbContext); } return _sysErrorLogRep; } } private IRepository<Sys_Operation_Log> _sysOperationLogRep; public IRepository<Sys_Operation_Log> SysOperationLogRep { get { if (_sysOperationLogRep == null ) { _sysOperationLogRep = new Repository<Sys_Operation_Log>(_dbContext); } return _sysOperationLogRep; } } #endregion } |
6.业务的实现方式
以前我是service中直接创建仓储然后用仓储操作数据库,方式如下:
这种方式比较繁琐,后来我将创建仓储统一放在工作单元中进行,在service中直接创建UnitOfWork,方式如下:
7.Service的动态注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public static class AutoIocRegister { /// <summary> /// 动态注入IOC,注意类和接口的命名规则,接口在类名前面加"I" /// </summary> /// <param name="services"></param> /// <param name="assemblyName">程序集名称</param> public static void BatchAddScoped( this IServiceCollection services, string assemblyName) { var libs = DependencyContext.Default.CompileLibraries; var serviceLib = libs.Where(c => c.Name.Contains(assemblyName)).FirstOrDefault(); var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName( new AssemblyName(serviceLib.Name)); var serviceClassList = assembly.GetTypes().Where(c => c.IsClass).ToList(); foreach ( var item in serviceClassList) { var interfaceName = "I" + item.Name; var interfaceType = assembly.GetTypes().Where(c => c.IsInterface && c.Name == interfaceName).FirstOrDefault(); if (interfaceType == null ) continue ; services.AddScoped(interfaceType, item); } } } |
调用:
1 | services.BatchAddScoped( "Cloud.Service" ); |
8.日志记录:
日志分操作日志和错误日志,可以设置在数据库和文本中同时记录:
通过全局过滤器GlobalExceptionFilter和GlobalAuthorizeFilter处理
9.前端的封装(分页、弹出层、ajax等)
先放几张图吧,详细的以后再介绍
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步