1. 程式人生 > >基於.net core 2.0+mysql+AceAdmin搭建一套快速開發框架

基於.net core 2.0+mysql+AceAdmin搭建一套快速開發框架

前言

.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

技術要點

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更新的處理

 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;
        }

  使用

 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

 public class EntityTypeConfiguration<T> : IEntityTypeConfiguration<T> where T : class
    {
        public void Configure(EntityTypeBuilder<T> builder)
        {
            RelyConfigure(builder);
        }
        public virtual void RelyConfigure(EntityTypeBuilder<T> builder)
        {

        }
    }
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);
        }

    }
  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

   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的動態註冊

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);
            }
        }
    } 

呼叫:

 services.BatchAddScoped("Cloud.Service");

8.日誌記錄:

日誌分操作日誌和錯誤日誌,可以設定在資料庫和文字中同時記錄:

通過全域性過濾器GlobalExceptionFilter和GlobalAuthorizeFilter處理

9.前端的封裝(分頁、彈出層、ajax等)

先放幾張圖吧,詳細的以後再介紹