仓储(Repository)和工作单元模式(UnitOfWork)
仓储和工作单元模式
仓储模式
为什么要用仓储模式
通常不建议在业务逻辑层直接访问数据库。因为这样可能会导致如下结果:
- 重复的代码
- 编程错误的可能性更高
- 业务数据的弱类型
- 更难集中处理数据,比如缓存
- 无法轻松地从外部依赖项测试业务逻辑
在业务逻辑层通过仓库模式访问数据则可以实现如下特点:
- 最大化可以用自动化测试的代码量,并隔离数据层以支持单元测试。
- 对数据集中管理、提供一致的访问规则和逻辑。
- 通过将业务逻辑与数据或服务访问逻辑分隔开,从而提高代码的可维护性和可读性。
- 使用强类型的
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也可以实现。
如有不对,请多多指教。