倉儲(Repository)和工做單元模式(UnitOfWork)

倉儲和工做單元模式

倉儲模式

爲何要用倉儲模式

一般不建議在業務邏輯層直接訪問數據庫。由於這樣可能會致使以下結果:數據庫

  • 重複的代碼
  • 編程錯誤的可能性更高
  • 業務數據的弱類型
  • 更難集中處理數據,好比緩存
  • 沒法輕鬆地從外部依賴項測試業務邏輯

在業務邏輯層經過倉庫模式訪問數據則能夠實現以下特色:編程

  • 最大化能夠用自動化測試的代碼量,並隔離數據層以支持單元測試。
  • 對數據集中管理、提供一致的訪問規則和邏輯。
  • 經過將業務邏輯與數據或服務訪問邏輯分隔開,從而提升代碼的可維護性和可讀性。
  • 使用強類型的Entity以便在編譯時識別問題而不是在運行時

實現倉儲模式

使用倉儲模式是爲了分離業務層和數據源層,並實現業務層的Model和數據源層的Model映射。(ViewModel和Entity之間的映射)。即業務邏輯層應該和數據源層無關,業務層只關心結果,數據源層關心細節。緩存

數據源層和業務層之間的分離有三個好處:併發

  • 集中了數據邏輯或Web服務訪問邏輯。
  • 爲單元測試提供了一個替代點。
  • 提供了一種靈活的體系結構,能夠做爲應用程序的總體設計進行調整。

1、定義倉儲接口

全部的倉儲要實現該接口。該接口定義了對數據的基本操做。函數

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
    }

2、實現泛型倉儲基類

該類爲倉儲的泛型基類,實現以前定義的倉儲接口(IRepository ),幷包含數據上下文(DbContext),數據集(DataSet)。 單元測試

每一個表都會對應一個實體(Entity)。每一個實體(Entity)對應一個倉儲。把實體做爲泛型倉儲基類的參數,來實現每一個實體對應的倉儲。測試

(使用泛型倉儲基類能夠把實體做爲泛型參數來建立對應的倉儲。)this

//泛型倉儲基類
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();
        }
    }
}

3、訪問數據

能夠把對Person的相關操做封裝到一個類中。在該類中實現PersonRepository(Person倉儲),操做PersonRepository來操做數據。設計

(數據庫有一個Person表,代碼中有一個TPerson實體)code

(該類提供與業務邏輯無關的倉儲操做)

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;
    }
}

4、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;
        }
    }
}

5、在Test中測試

倉儲模式使得更容易實現單元測試

  1. 添加項目引用
  2. 設置數據庫鏈接字符串
  3. 添加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的使用了也實現了工做單元模式。


仍是簡單說下如何實現自定義的工做單元 (若是要對每一個操做都產生記錄的話,能夠擴展自定義工做單元來實現)

自定義工做單元

1、定義IUnitOfWork接口

/// <summary>
/// 工做單元接口
/// </summary>
public interface IUnitOfWork
{
    /// <summary>
    /// 保存當前單元操做的結果
    /// </summary>
    /// <returns></returns>
    void Save();

}

2、定義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);
    }
}

3、實現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;
    }
}

4、經過工做單元,保持操做一致性,手動釋放數據上下文

在此將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;
        }
    }
}

5、單元測試

[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也能夠實現。


代碼下載

若有不對,請多多指教。

相關文章
相關標籤/搜索