倉儲和工作單元模式
倉儲模式
為什么要用倉儲模式
通常不建議在業務邏輯層直接訪問數據庫。因為這樣可能會導致如下結果:
- 重復的代碼
- 編程錯誤的可能性更高
- 業務數據的弱類型
- 更難集中處理數據,比如緩存
- 無法輕松地從外部依賴項測試業務邏輯
在業務邏輯層通過倉庫模式訪問數據則可以實現如下特點:
- 最大化可以用自動化測試的代碼量,並隔離數據層以支持單元測試。
- 對數據集中管理、提供一致的訪問規則和邏輯。
- 通過將業務邏輯與數據或服務訪問邏輯分隔開,從而提高代碼的可維護性和可讀性。
- 使用強類型的
Entity以便在編譯時識別問題而不是在運行時
實現倉儲模式
使用倉儲模式是為了分離業務層和數據源層,並實現業務層的Model和數據源層的Model映射。(ViewModel和Entity之間的映射)。即業務邏輯層應該和數據源層無關,業務層只關心結果,數據源層關心細節。
數據源層和業務層之間的分離有三個好處:
- 集中了數據邏輯或Web服務訪問邏輯。
- 為單元測試提供了一個替代點。
- 提供了一種靈活的體系結構,可以作為應用程序的整體設計進行調整。
一、定義倉儲接口
所有的倉儲要實現該接口。該接口定義了對數據的基本操作。
public interface IRepository<TEntity> where TEntity : class
{
#region 屬性
//IQueryable Entities { get; }
#endregion
#region 公共方法
void Insert(TEntity entity);
void Insert(IEnumerable<TEntity> entities);
void Delete(object id);
void Delete(TEntity entity);
void Delete(IEnumerable<TEntity> entities);
void Update(TEntity entity);
TEntity GetByKey(object key);
#endregion
}
二、實現泛型倉儲基類
該類為倉儲的泛型基類,實現之前定義的倉儲接口(IRepository
每個表都會對應一個實體(Entity)。每個實體(Entity)對應一個倉儲。把實體作為泛型倉儲基類的參數,來實現每個實體對應的倉儲。
(使用泛型倉儲基類可以把實體作為泛型參數來創建對應的倉儲。)
//泛型倉儲基類
public class EFBaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
//數據上下文
internal DbContext context;
//數據集
internal DbSet<TEntity> dbSet;
public EFBaseRepository(DbContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
//public IQueryable Entities => context.Set<TEntity>();
public void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public void Delete(IEnumerable<TEntity> entities)
{
dbSet.RemoveRange(entities);
}
public void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public TEntity GetByKey(object key)
{
return dbSet.Find(key);
}
public void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public void Insert(IEnumerable<TEntity> entities)
{
dbSet.AddRange(entities);
}
public void Update(TEntity entity)
{
dbSet.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "", int topNum = 0)
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
query = orderBy(query);
}
if (topNum != 0)
{
return query.Take(topNum);
}
else
{
return query.ToList();
}
}
}
三、訪問數據
可以把對Person的相關操作封裝到一個類中。在該類中實現PersonRepository(Person倉儲),操作PersonRepository來操作數據。
(數據庫有一個Person表,代碼中有一個TPerson實體)
(該類提供與業務邏輯無關的倉儲操作)
public class PersonService
{
private EFBaseRepository<TPerson> _personRepository;
public PersonService(DbContext dbContext)
{
var context = dbContext;
//實現Person倉儲,TPerson為對應的Entity
_personRepository = new EFBaseRepository<TPerson>(context);
}
public IEnumerable<TPerson> Get()
{
return _personRepository.Get();
}
public bool AddPerson(TPerson p)
{
try
{
_personRepository.Insert(p);
}
catch (Exception ex)
{
return false;
}
return true;
}
public bool EditPerson(TPerson p)
{
try
{
_personRepository.Update(p);
}
catch (Exception ex)
{
return false;
}
return true;
}
public bool DeletePerson(TPerson p)
{
try
{
_personRepository.Delete(p);
}
catch (Exception)
{
return false;
}
return true;
}
}
四、ViewModel和Entity的映射
該類是對PersonService的封裝,是為了提供同一數據上下文,和對數據上下文的釋放,及ViewModle和Entity的映射。
該類中每個方法對應一個數據上下文。如果有需要對多個表操作,將這些操作封裝到一個數據上下文中。數據上下文的釋放在每個方法中實現。
(所有與業務邏輯相關的操作在該類實現)
public class PersonManage
{
public IList<PersonVM> GetPersons()
{
using (var context = new RepositoryDemoEntities())
{
var list = new PersonService(context).Get();
var result = new List<PersonVM>();
foreach (var item in list)
{
result.Add(new PersonVM { Name = item.Name, Age = item.Age, Home = item.Home, PersonID = item.Id });
}
return result;
}
}
public bool AddPerson(PersonVM p)
{
using (var context = new RepositoryDemoEntities())
{
var result = new PersonService(context).AddPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
context.SaveChanges();
return result;
}
}
public bool DeletePerson(PersonVM p)
{
using (var context = new RepositoryDemoEntities())
{
var result = new PersonService(context).DeletePerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
context.SaveChanges();
return result;
}
}
public bool EditPerson(PersonVM p)
{
using (var context = new RepositoryDemoEntities())
{
var result = new PersonService(context).EditPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
context.SaveChanges();
return result;
}
}
}
五、在Test中測試
倉儲模式使得更容易實現單元測試
- 添加項目引用
- 設置數據庫連接字符串
- 添加
EntityFramework包即可對每個方法測試
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestShowPerson()
{
var res = new PersonManage().GetPersons();
Assert.AreNotEqual(0, res.Count);
}
[TestMethod]
public void TestAddPerson()
{
var p = new PersonVM { Home = "zhengzhou", Age = 22, Name = "Jessica", PersonID = 3 };
var res = new PersonManage().AddPerson(p);
Assert.IsTrue(res);
}
[TestMethod]
public void TestEditPerson()
{
var persons = new PersonManage().GetPersons();
var p = persons[0];
p.Name = "fixed";
var res = new PersonManage().EditPerson(p);
Assert.IsTrue(res);
}
[TestMethod]
public void TestDeletePerson()
{
var persons = new PersonManage().GetPersons();
var p = persons[0];
var res = new PersonManage().DeletePerson(p);
Assert.IsTrue(res);
}
}
小結:
倉儲模式通過對數據庫操作的封裝使數據訪問有一致性和對應用層和數據層的隔離,降低代碼的耦合性,更加容易實現單元測試。
工作單元模式
工作單元模式是“維護一個被業務事務影響的對象列表,協調變化的寫入和並發問題的解決”
比如:新入校一個同學,需要在班級,學校,學生,課程等多個表里同時操作。這些表要么都完成,要么都不完成。具有一致性。
在倉儲模式中使用工作單元模式是為了當你操作多個倉儲時,共用一個數據上下文(DbContext)使得這些倉儲具有一致性。
在Entity Framework中可以把DbContext當作是一個工作單元。在同一個DbContext對多個倉儲操作。所以工作單元模式並不是一定要自己實現,通過Entity Framework也可以實現。
上面的倉儲模式其實通過對DbContext的使用了也實現了工作單元模式。
還是簡單說下如何實現自定義的工作單元 (如果要對每個操作都產生記錄的話,可以擴展自定義工作單元來實現)
自定義工作單元
一、定義IUnitOfWork接口
/// <summary>
/// 工作單元接口
/// </summary>
public interface IUnitOfWork
{
/// <summary>
/// 保存當前單元操作的結果
/// </summary>
/// <returns></returns>
void Save();
}
二、定義UnitOfWork類
UnitOfWork包含了所有的倉儲,及一個數據上下文,該類實現IDisposable接口(該接口的方法中釋放數據上下文)。
public class UnitOfWork : IUnitOfWork, IDisposable
{
private RepositoryDemoEntities1 context = new RepositoryDemoEntities1();
private EFBaseRepository<TPerson> _personRepository;
public EFBaseRepository<TPerson> PersonRepository
{
get
{
return _personRepository ?? new EFBaseRepository<TPerson>(context);
}
}
public void Save()
{
context.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
三、實現UnitOfWork實例。通過該實例訪問倉儲。
定義一個UnitOfWork的字段,通過構造函數實例化該UnitOfWork
(該類提供與業務邏輯無關的倉儲操作)
public class PersonService
{
private UnitOfWork unit;
public PersonService(UnitOfWork unitOfWork)
{
unit = unitOfWork;
}
public IEnumerable<TPerson> Get()
{
return unit.PersonRepository.Get();
}
public bool AddPerson(TPerson p)
{
try
{
unit.PersonRepository.Insert(p);
}
catch (Exception ex)
{
return false;
}
return true;
}
public bool EditPerson(TPerson p)
{
try
{
unit.PersonRepository.Update(p);
}
catch (Exception ex)
{
return false;
}
return true;
}
public bool DeletePerson(TPerson p)
{
try
{
unit.PersonRepository.Delete(p);
}
catch (Exception)
{
return false;
}
return true;
}
}
四、通過工作單元,保持操作一致性,手動釋放數據上下文
在此將PersonService封裝,如果有對多個倉儲的操作,封裝在一個工作單元中。
(所有與業務邏輯相關的操作在該類實現)
public class PersonManage
{
public IList<PersonVM> GetPersons()
{
using (var unit = new UnitOfWork())
{
var list = new PersonService(unit).Get();
var result = new List<PersonVM>();
foreach (var item in list)
{
result.Add(new PersonVM { Name = item.Name, Age = item.Age, Home = item.Home, PersonID = item.Id });
}
return result;
}
}
public bool AddPerson(PersonVM p)
{
using (var unit = new UnitOfWork())
{
var result = new PersonService(unit).AddPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
unit.Save();
return result;
}
}
public bool DeletePerson(PersonVM p)
{
using (var unit = new UnitOfWork())
{
var result = new PersonService(unit).DeletePerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
unit.Save();
return result;
}
}
public bool EditPerson(PersonVM p)
{
using (var unit = new UnitOfWork())
{
var result = new PersonService(unit).EditPerson(new EntityFramework.TPerson { Name = p.Name, Home = p.Home, Age = p.Age, Id = p.PersonID });
unit.Save();
return result;
}
}
}
五、單元測試
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestShow()
{
var res = new PersonManage().GetPersons();
Console.WriteLine(res.Count);
Assert.AreNotEqual(0, res.Count);
}
[TestMethod]
public void TestAdd()
{
var res = new PersonManage().AddPerson(new PersonVM { Home = "meiguo", Age = 11, Name = "tidy" });
Assert.IsTrue(res);
}
[TestMethod]
public void TestEdit()
{
var pmanage = new PersonManage();
var p = pmanage.GetPersons()[0];
p.Name = "fixed";
var res = pmanage.EditPerson(p);
Assert.IsTrue(res);
}
[TestMethod]
public void TestDelete()
{
var pmanage = new PersonManage();
var p = pmanage.GetPersons()[0];
var res = pmanage.DeletePerson(p);
Assert.IsTrue(res);
}
}
小結:
工作單元模式是為了實現業務的事務功能。通過一個數據上下文對相關的倉儲操作。但是也不是必須要自己實現模式,通過ORM也可以實現。
如有不對,請多多指教。
