本系列目錄:
使用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對象
/// <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);那這種擴展又是如何實現的呢?在下一節中,我們將來探討這個問題.
