使用EF構建企業級應用(一)


本系列目錄:

使用EF構建企業級應用(一):主要講數據庫訪問基類IRepository及Repository 的實現

使用EF構建企業級應用(二):主要講動態排序擴展的實現

使用EF構建企業級應用(三):主要講靈活的構建查詢條件表達式Expression<Func<TEntity,bool>>.

使用EF構建企業級應用(四):主要講下在MVC環境中前端開發中如何郵箱的使用,及一個實例源碼包

 數據持久化
最初接觸EF是在2009年,那時因為EF還只支持DataBase-first模式,在項目中使用需要創建.edmx文件,因為覺得比較累贅,且生成的代碼不容易控制,總覺得看着不爽,於是曾一度被拋棄,也總結了自己的一些數據持久化的東東,最近不經意間聽說到了一個新詞,code-first,覺得挺新潮,最開始還以為是微軟出了個什么新技術,於是不停的Google,后得知原來所謂的code-first只是EF中的一部分,在潛心學習一段時間后,因為和Linq結合得非常好,感覺還挺喜歡這種模式的,於是,我們將這種模式應用在了我們新的項目上.在學習EF的時候,走過了很多彎路,也尋找了很多資料,現做一個小的總結,以幫助遇到類似困難的同學們.
因考慮到這個文章篇幅可能較長,故把此文章拆分成一個系列,本小結主要介紹 數據持久化(即常說的CURD)相關類容

數據持久化的簡單封裝,根據以前的經驗,以及EF的一些特殊性,我們很自然的畫出來下面的Repository類圖

    • 在上面的UML中,我們定義了兩個接口,IRepository<TEntity>及IRepository<TEntity,TPkType>
    • 上面UML中的泛型類型分別為:

      • TEntity : 數據庫操作的實體對象,

      • TPkType:TEntity中單一主鍵的數據類型

      • TContext:一個派生於DbContext對象

      下面我們來看一下IRepository 這兩個接口的具體定義
 
     /// <summary>
    ///  倉儲模型基本接口,提供基本的數據庫增刪改查接口
    /// </summary>
    /// <typeparam name="TEntity">實體類型</typeparam>
    public interface IRepository<TEntity> : IDisposable where TEntity : class
    {
        /// <summary>
        /// 獲取所有數據
        /// </summary>
        /// <returns></returns>
        IQueryable<TEntity> Get();

        /// <summary>
        /// 根據指定的查詢條件
        /// </summary>
        /// <param name="expression">查詢條件</param>
        /// <returns></returns>
        IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> expression);

        /// <summary>
        /// 根據指定條件查詢分頁數據
        /// </summary>
        /// <typeparam name="TOrderType">排序類型</typeparam>
        /// <param name="expression">查詢條件</param>
        /// <param name="orderPropertyName">排序字段</param>
        /// <param name="isAscOrder">是否是升序查詢</param>
        /// <param name="pgIndex"></param>
        /// <param name="pgSize"></param>
        /// <param name="total">總記錄數</param>
        /// <returns></returns>
        IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> expression,
            string orderPropertyName, bool isAscending,
            int pgIndex, int pgSize, out int total);

         /// <summary>
        /// 新增
         /// </summary>
        /// <param name="entity"></param>
        void Add(TEntity entity);
        /// <summary>
        /// 根據具體實體刪除
         /// </summary>
        /// <param name="entity"></param>
        void Delete(TEntity entity);

        /// <summary>
        /// 修改
         /// </summary>
        /// <param name="entity"></param>
        void Update(TEntity entity);

        /// <summary>
        /// 根據主鍵獲取
         /// </summary>
        /// <param name="id">主鍵Id值</param>
        /// <returns></returns>
        TEntity Get(params object[] ids);

        /// <summary>
        /// 根據主鍵刪除
         /// </summary>
        /// <param name="entity">主鍵Id值</param>
        void Delete(params object[] ids);

        /// <summary>
        /// 保存數據修改
         /// </summary>
        void SaveDbChange();

    }

     /// <summary>
     ///  倉儲模型基本接口,提供基本的數據庫增刪改查接口
     /// </summary>
    /// <typeparam name="TEntity">實體類型</typeparam>
    /// <typeparam name="TPkType">實體主鍵類型</typeparam>
    public interface IRepository<TEntity, TPkType> : IRepository<TEntity>
         where TEntity : class,IEntity<TPkType>
    {

        /// <summary>
        /// 根據主鍵獲取
         /// </summary>
        /// <param name="id">主鍵Id值</param>
        /// <returns></returns>
        TEntity Get(TPkType id);

        /// <summary>
        /// 根據主鍵刪除
         /// </summary>
        /// <param name="id">主鍵Id值</param>
        void Delete(TPkType id);
    }

  

 

在上面的定義中,細心的同學可能會發現我們定義了一個數據提交修改的方法申明"void SaveDbChange(); “,可能也許你會覺得累贅,為何不把這個方法放在每次的CURD后自動執行呢,最初我們也有同樣的想法,但當我們在實際業務中應用發現,通常每個業務都會在多個數據表上進行CURD操作,為了做到盡量減少一個業務邏輯提交數據庫的次數,我們采用了將提交數據庫這一步拆分出來,放在了單獨的方法中進行,這種一次性提交可以做到類似於事務的功能,且操作更加簡便.

下面我們來看看這個EFRepository的實現

 
     /// <summary>
    /// EF實現 數據庫增刪改查
    /// </summary>
    /// <typeparam name="TEntity">查詢實體</typeparam>
    /// <typeparam name="TContext">DbContext 類型</typeparam>
    public class EFRepository<TEntity, TContext> : IRepository<TEntity>
        where TEntity : class
        where TContext : DbContext, new()
    {
        private TContext dbContext;

        /// <summary>
        /// 獲取DbContext
        /// </summary>
        public TContext DbContext
        {
            get
            {
                if (dbContext == null)
                    dbContext = new TContext();
                return dbContext;
            }
        }

        /// <summary>
        /// 獲取DbSet 
        /// </summary>
        protected DbSet<TEntity> DbSet
        {
            get
            {
                return DbContext.Set<TEntity>();
            }
        }
        /// <summary>
        /// 關聯查詢
        /// </summary>
        /// <typeparam name="TProperty">關聯查詢的屬性類型</typeparam>
        /// <param name="path">關聯查詢屬性</param>
        /// <returns></returns>
        public IQueryable<TEntity> Include<TProperty>(Expression<Func<TEntity, TProperty>> path)
        {
            return DbSet.Include(path);
        }

        /// <summary>
        /// 獲取所有數據
        /// </summary>
        /// <returns></returns>
        public IQueryable<TEntity> Get()
        {
            return DbSet;
        }

        /// <summary>
        /// 根據指定的查詢條件
        /// </summary>
        /// <param name="expression">查詢條件</param>
        /// <returns></returns>
        public IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> expression)
        {
            IQueryable<TEntity> query = DbSet;
            if (expression != null)
                query = DbSet.Where(expression);
            return query;
        }

        /// <summary>
        /// 根據指定條件查詢分頁數據
         /// </summary>
        /// <param name="expression">查詢條件</param>
        /// <param name="orderPropertyName">排序屬性</param>
        /// <param name="isAscending">是否是升序查詢,false為降序</param>
        /// <param name="pgIndex">分頁查詢起始頁</param>
        /// <param name="pgSize">每頁顯示記錄數</param>
        /// <param name="total">總記錄數</param>
        /// <returns></returns>
        public IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> expression,
             string orderPropertyName, bool isAscending, int pgIndex, int pgSize, out int total)
        {
            total = 0;
            IQueryable<TEntity> query = Get(expression).OrderBy(orderPropertyName, isAscending);
            //IQueryable<TEntity> query = Get(expression).OrderBy(orderPropertyName);

            //分頁查詢
            if (pgSize > 0)
            {
                total = query.Count();      //記錄總數
                query = query.Skip(pgIndex * pgSize).Take(pgSize);
            }
            return query;
        }
       
        /// <summary>
        /// 新增
         /// </summary>
        /// <param name="entity"></param>
        public void Add(TEntity entity)
        {
            DbSet.Add(entity);
        }
        /// <summary>
        /// 刪除實體
        /// </summary>
        /// <param name="entity"></param>
        public void Delete(TEntity entity)
        {
            DbSet.Remove(entity);
        }

        /// <summary>
        /// 修改實體
         /// </summary>
        /// <param name="entity"></param>
        public virtual void Update(TEntity entity)
        {
            var entry = DbContext.Entry(entity);
            if (entry != null)
            {
                if (entity != null)
                {
                    if (entry.State == EntityState.Detached)
                    {
                        //以下方法在先查詢后再修改會報錯,提示dbContext已經加載了一個相同主鍵的對象
                        DbSet.Attach(entity);
                        entry.State = EntityState.Modified;
                    }
                }
            }
        }
        /// <summary>
        /// 修改數據
         /// </summary>
        /// <param name="oldEntity">原實體</param>
        /// <param name="newEntity">修改后的實體</param>
        public void Update(TEntity dbEntity, TEntity newEntity)
        {
            var entry = DbContext.Entry(dbEntity);
            if (entry != null)
            {
                //以下方法在先查詢后再修改會報錯,提示dbContext已經加載了一個相同主鍵的對象
                //DbSet.Attach(entity);
                // entry.State = EntityState.Modified;

                //改為如下方式實現 以下代碼類似於 entity=new TEntity{p1=entityToUpdate.p1,...},
                EmitMapper.ObjectMapperManager.DefaultInstance.GetMapper<TEntity, TEntity>().Map(newEntity, dbEntity);
            }
        }

        /// <summary>
        /// 根據主鍵獲取數據
         /// </summary>
        /// <param name="ids">主鍵集合</param>
        /// <returns></returns>
        public virtual TEntity Get(params object[] ids)
        {
            return DbSet.Find(ids);
        }

        /// <summary>
        /// 根據主鍵刪除數據
         /// </summary>
        /// <param name="ids">主鍵集合</param>
        public virtual void Delete(params object[] ids)
        {
            var item = Get(ids);
            if (item != null)
            {
                DbContext.Entry(item).State = EntityState.Deleted;
            }
        }

        /// <summary>
        /// 提交數據修改
        /// </summary>
        public void SaveDbChange()
        {
            dbContext.SaveChanges();
        }

        /// <summary>
        /// 釋放資源
         /// </summary>
        public void Dispose()
        {
            if (dbContext != null)
            {
                dbContext.Dispose();
                dbContext = null;
            }
        }



    }

    /// <summary>
    /// EF實現 數據庫增刪改查基本實現
     /// </summary>
    /// <typeparam name="TEntity">查詢實體</typeparam>
    /// <typeparam name="TPkType">實體主鍵類型</typeparam>
    /// <typeparam name="TContext">DbContext 類型</typeparam>
    public class EFRepository<TEntity, TPkType, TContext> : EFRepository<TEntity, TContext>, IRepository<TEntity, TPkType>
        where TEntity : class, IEntity<TPkType>
        where TContext : DbContext, new()
    {
        /// <summary>
        /// 根據主鍵獲取
        /// </summary>
        /// <param name="id">主鍵Id值</param>
        /// <returns></returns>
        public TEntity Get(TPkType id)
        {
            return DbSet.Find(id);
        }

        /// <summary>
        /// 根據主鍵刪除
        /// </summary>
        /// <param name="id"></param>
        public void Delete(TPkType id)
        {
            var info = DbSet.Find(id);
            if (info != null)
            {
                DbContext.Entry(info).State = EntityState.Deleted;
            }
        }

        /// <summary>
        /// 修改,
         /// </summary>
        /// <param name="entity"></param>
        public override void Update(TEntity entity)
        {
            var entry = DbContext.Entry(entity);
            if (entry != null)
            {
                if (entry.State == EntityState.Detached)
                {
                    //以下方法在先查詢后再修改會報錯,提示dbContext已經加載了一個相同主鍵的對象
                    //DbSet.Attach(entity);
                    // entry.State = EntityState.Modified;

                    //改為如下方式實現 以下代碼類似於 entity=new TEntity{p1=entityToUpdate.p1,...},以下語句在當實體中有虛擬方法時候會報錯
                    var entityToUpdate = DbSet.Find(entity.Id);
                    EmitMapper.ObjectMapperManager.DefaultInstance.GetMapper<TEntity, TEntity>().Map(entity, entityToUpdate);
                }
            }
        }

細心的同學可能會發現我們在實現查詢結果集的時候,返回的是IQueryable<TEntity>,而不是IList<TEntity> 或者IEnumerable<T>,這是因為,也許在實際業務邏輯中,我們並不需要返回整個集合,或者我們只需要這其中的一個最大值或者一個求和數據,為了不給寶貴的數據庫查詢帶來額外的性能犧牲,我們便決定在此並不立即執行查詢,真正的查詢交給了前段業務邏輯使用者自己去ToList().

本小結主要講到這里,在這一節中,我們實現了數據持久化Repository,細心的同學可能發現了我們在寫查詢的時候,定義的方法中支持動態排序,即類似於IQueryable<TEntity> query = Get(expression).OrderBy(orderPropertyName, isAscending);那這種擴展又是如何實現的呢?在下一節中,我們將來探討這個問題.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM