要點 :
- 把使用EF存取資料的部分從 Web 專案中抽離出來,這表示 Web 專案只關注於資料呈現以及系統控制流程的部份
- 凡是要跟資料打交道的存取操作就不會出現在 Web 專案中,讓兩個專案有各自的職責與關注的事物
- 該專案主要就是在操作模型的CRUD
- 未來會再視情況新增方法或屬性,要有高使用率的才有可能會增加進來。此通用實作是針對Entity FrameWork
- 未來所有的ORM資料庫應該在額外建立一個專案,供資料存取曾與呼叫端做使用
- 未來會搭配ASP.NET非同步的方法
專案內容 :
資料存取層/通用型 - 介面 :
using System.Linq;
using System;
using System.Linq.Expressions;
namespace Repository_Models.Interface
{
/// <summary>
/// 代表一個Repository的interface。
/// </summary>
/// <typeparam name="T">任意model的class</typeparam>
public interface IRepository<T> where T : class
{
/// <summary>
/// 新增一筆資料。
/// </summary>
/// <param name="entity">要新增的Entity</param>
void Create(T entity);
/// <summary>
/// 刪除一筆資料內容。
/// </summary>
/// <param name="entity">要被刪除的Entity。</param>
void Delete(T entity);
/// <summary>
/// 更新一筆資料的內容。
/// </summary>
/// <param name="entity">要更新的內容</param>
void Update(T entity);
/// <summary>
/// 更新一筆資料的內容。只更新部分欄位。
/// Lambda 運算式 只需要傳遞欄位屬性 EX : x => { x.ColumnName1, x.Column2 }
/// </summary>
/// <param name="entity">要更新的內容</param>
/// <param name="updateProperties">需要更新的欄位。</param>
void Update(T entity, Expression<Func<T, object>>[] updateProperties);
/// <summary>
/// 儲存異動。
/// </summary>
//void SaveChanges(); //因為實作 IunitOfWork裡面有儲存方法 因此拿掉這一方法的規範
/// <summary>
/// 取得第一筆符合條件的內容。如果符合條件有多筆,也只取得第一筆。
/// </summary>
/// <param name="predicate">要取得的Where條件。</param>
/// <returns>取得第一筆符合條件的內容。</returns>
T Read(Expression<Func<T, bool>> predicate);
/// <summary>
/// 取得合條件的內容。可能回傳一筆或是多筆
/// </summary>
/// <param name="predicate">要取得的Where條件。</param>
/// <param name="predicates">要取得的Where條件。可新增N個條件值</param>
/// <returns>只要符合條件則回傳全部筆數的IQueryable。</returns>
IQueryable<T> Reads(Expression<Func<T, bool>> predicate,params Expression<Func<T, bool>>[] predicates);
/// <summary>
/// 取得Entity全部筆數的IQueryable。
/// </summary>
/// <returns>Entity全部筆數的IQueryable。</returns>
IQueryable<T> Reads();
}
}
資料存取層/通用型 - 實作類 :
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Data.Entity;
namespace Repository_Models.Repository
{
//TODO : 還需把判斷 entity == null 丟出例外的 工作 拋給 Service Layer
// internal
/// <summary>
/// 該類別是通用型的 Repository。使用 Entity 的機制
/// </summary>
/// <typeparam name="TEntity">請傳入Entity對應的資料類別</typeparam>
internal class EFGenericRepository<TEntity> : Interface.IRepository<TEntity>
where TEntity : class
{
private DbContext _context { get; set; }//使用 Entity Framework 的機制
//public EFGenericRepository()// : this(new FinalMvcTestDatabaseEntities())
//{
// throw new NotSupportedException(String.Format("{0} 不支援無參數的初始化",this.GetType().FullName));
//}
public EFGenericRepository(DbContext context)
{
this._context = context;
}
/// <summary>
/// 新增一筆資料到資料庫。
/// </summary>
/// <param name="entity">要新增到資料的庫的Entity</param>
public void Create(TEntity entity)
{
//指定entity物件為 TEntity 的類別 回傳對應的DbSet的物件 => 等同指定資料表
this._context.Set<TEntity>().Add(entity);
}
/// <summary>
/// 刪除一筆資料
/// </summary>
/// <param name="entity">資料對應的Entity類別</param>
public void Delete(TEntity entity)
{
this._context.Entry(entity).State = EntityState.Deleted;
}
/// <summary>
/// 修改一筆資料
/// </summary>
/// <param name="instance">資料對應的Entity類別</param>
public void Update(TEntity entity)
{
this._context.Entry(entity).State = EntityState.Modified;
}
/// <summary>
/// 更新一筆資料的內容。只更新部分欄位的。
/// Lambda 運算式 只需要傳遞欄位屬性 EX : x => x.ColumnName1, x => x.Column2....
/// </summary>
/// <param name="entity">要更新的內容</param>
/// <param name="updateProperties">需要更新的欄位。</param>
public void Update(TEntity entity, Expression<Func<TEntity, object>>[] updateProperties)
{
// 想要略過 EF 檢查 關閉自動追蹤實體的驗證
this._context.Configuration.ValidateOnSaveEnabled = false;
// 其屬性還未更新到資料庫 但先做紀錄
this._context.Entry(entity).State = EntityState.Unchanged;
if (updateProperties != null)
{
// 確認那些欄位是要修改的做上記號
foreach (var item in updateProperties)
{
this._context.Entry(entity).Property(item).IsModified = true;
}
}
}
//此方法目前內部不使用 外部也不使用 先暫時放著
/// <summary>
/// 儲存此次的資料異動
/// </summary>
public void SaveChanges()
{
this._context.SaveChanges();
//System.ComponentModel; System.ComponentModel.DataAnnotations; 依據實體的規範如 [[Required]]
//因為Update(,) 單一model需要先關掉validation,因此重新打開
if (this._context.Configuration.ValidateOnSaveEnabled == false)
this._context.Configuration.ValidateOnSaveEnabled = true;
}
/// <summary>
/// 取得第一筆符合條件的內容。如果符合條件有多筆,也只取得第一筆。
/// </summary>
/// <param name="predicate">要取得的Where條件。</param>
/// <returns>取得第一筆符合條件的內容。</returns>
public TEntity Read(Expression<Func<TEntity, bool>> predicate)
{
return this._context.Set<TEntity>().FirstOrDefault(predicate);
}
/// <summary>
/// 取得合條件的內容。可能回傳一筆或是多筆 IQueryable
/// </summary>
/// <param name="predicate">要取得的Where條件。</param>
/// <param name="predicates">要取得的Where條件。可新增N個條件值</param>
/// <returns>只要符合條件則回傳全部筆數的IQueryable。</returns>
public IQueryable<TEntity> Reads(Expression<Func<TEntity, bool>> predicate,
params Expression<Func<TEntity, bool>>[] predicates)
{
var datas = this._context.Set<TEntity>().Where(predicate);
if (predicates != null)
{
foreach (var expression in predicates)
{
datas = datas.Where(expression);
}
}
return datas;
}
/// <summary>
/// 取得Entity全部筆數的IQueryable。
/// </summary>
/// <returns>Entity全部筆數的IQueryable。</returns>
public IQueryable<TEntity> Reads()
{
return this._context.Set<TEntity>().AsQueryable();
}
#region 釋放資源
~EFGenericRepository()
{
this._context = null;
GC.SuppressFinalize(this);
}
#endregion
}
}
工作單元層 :
這個類有點像是工廠 - 功能為 :
1. 實例化 對應的 EFGenericRepository<T> 的物件給你
2. 幫忙管控並關閉實例出來的 DbContext
3. 需要你給相對應的Dbcontex才行 <T>需對應該 Dbcontext中的資料表
若不想要該類可以做以下修改 :
1. 資料存取層類別的泛型與方法中的泛型無關
2. 想辦法使用資料存取層類別的泛型產生對應的Dbcontex
工作單元層-介面
using System;
using Repository_Models.Interface;
namespace IUnitOfWork.Interface
{
/// <summary>
/// 實作Unit Of Work的interface。
/// </summary>
public interface IUnitOfWork : IDisposable
{
/// <summary>
/// 儲存所有異動。
/// </summary>
void Save();
/// <summary>
/// 取得某一個Entity的Repository。
/// 如果沒有取過,會initialise一個
/// 如果有就取得之前initialise的那個。
/// 產生出 UnitOfWork 的物件時 可循環利用該方法達到節省記憶體空間之功能
/// </summary>
/// <typeparam name="T">此Context裡面的Entity Type</typeparam>
/// <returns>Entity的Repository</returns>
IRepository<T> GetRepository<T>() where T : class;
}
}
工作單元層-實作類
using System;
using System.Collections;
using System.Data.Entity;
using Repository_Models.Interface;
using Repository_Models.Repository;
namespace IUnitOfWork
{
/// <summary>
/// EFGenercUnitOfWork 是統一產生對應的 EFGenericRepository 的類別
/// Service層 請善用該類別不應自己實例出 EFGenericRepository 的物件
/// </summary>
public class EFGenercUnitOfWork : Interface.IUnitOfWork
{
private readonly DbContext _context;
//在生命週期結束前 使用 雜湊表 儲存 泛行方法 的 類型 當KEY 值
//產生出的 instance 當 Value
private Hashtable _repositories = null;
public EFGenercUnitOfWork(DbContext context)
{
if (context == null) { throw new ArgumentNullException("context"); }
this._context = context;
}
/// <summary>
/// 取得某一個Entity的Repository。
/// 如果沒有取過,會initialise一個
/// 如果有就取得之前initialise的那個。
/// 產生出 UnitOfWork 的物件時 可循環利用該方法達到節省記憶體空間之功能
/// </summary>
/// <typeparam name="T">此Context裡面的Entity Type</typeparam>
/// <returns>Entity的Repository</returns>
public IRepository<T> GetRepository<T>() where T : class
{
if (this._repositories == null) this._repositories = new Hashtable();
// 取得泛型中的類型型態
var type = typeof(T).Name;
// 雜湊表中找不到對應的類型時 創立 Key 儲存 T 類型,Value 儲存 對應的實體
if (!this._repositories.ContainsKey(type))
{
// 取得通用的REPOSITORY類型
var repositoryType = typeof(EFGenericRepository<>);
// 使用 EFGenericRepository類型 與外部的傳遞的 泛型 建立出對應的實體,
// 最後傳遞 EFGenericRepository 建構子所需的參數
var repositoryInsetance = Activator.CreateInstance(repositoryType.MakeGenericType(
typeof(T)), this._context);
// 儲存型別名稱 與對應的實例 到雜湊表之中
this._repositories.Add(type, repositoryInsetance);
}
// 將雜湊表中的Object取出 並 轉型
return (this._repositories[type] as IRepository<T>);
}
/// <summary>
/// 儲存此次的 DbContext 資料異動
/// </summary>
public void Save()
{
//TODO: 需注意 EFGenericRepository<T> 修改方法的驗證,怕會對整體異動資料上有影響
this._context.SaveChanges();
if (this._context.Configuration.ValidateOnSaveEnabled == false)
this._context.Configuration.ValidateOnSaveEnabled = true;
}
#region IDisposable Support
private bool disposedValue = false; // 偵測多餘的呼叫
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
}
this._context.Dispose();
this._repositories.Clear();
this._repositories = null;
disposedValue = true;
}
}
~EFGenercUnitOfWork()
{
Dispose(false);
}
// 加入這個程式碼的目的在正確實作可處置的模式。
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
註記 : 未來修改的方向
- 資料存取層不應該針對<模型類別(Model class)>來產生實例
- 資料存取層應針對<DbContext>來產生實例
- 如何判斷<模型類別(Model class)>是否屬於該DbContext的呢??這應該才是關鍵
多多指教!! 歡迎交流!!
你不知道自己不知道,那你會以為你知道