65.9K
CodeProject 正在变化。 阅读更多。
Home

MVC 中的通用存储库和 UnitOfWork 模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (16投票s)

2014年10月4日

CPOL

6分钟阅读

viewsIcon

72807

downloadIcon

5065

在 MVC 应用程序中使用通用存储库和工作单元模式,我们可以创建更灵活的应用程序。

引言

在开发应用程序时,如果我们牢记松耦合系统的优势,那么我们最终会创建出更易于维护的系统。松耦合系统是指组件对应用程序中其他组件的了解很少或没有。松耦合架构的一些优点是:

  • 不同的组件可以独立更改,而不影响其他组件,从而使应用程序更易于维护
  • 它更适合敏捷方法论,其中更改频繁发生,因此紧密耦合不允许将来的更改
  • 创建模拟测试更容易,因为实际对象可以很容易地替换为模拟对象。

相反,在强耦合系统中,应用程序组件(例如类或模块)包含对另一个组件的具体实例的直接引用。这使得系统难以维护。

在 MVC 应用程序中工作时,我们应该记住 MVC 框架基于松耦合架构。在 MVC 框架中,每个组件都基于接口或抽象类。这意味着我们可以轻松地将框架的任何组件替换为另一个组件。

如果我们以控制器和视图为例,它们实现了 IController 和 IVeiw 接口。我们可以用任何实现这些接口的组件来替换它们。

为什么要使用存储库模式?

MVC 应用程序支持创建解耦架构,而创建解耦架构的一种方法是使用存储库模式。

通常,MVC 应用程序中的模型组件负责包含业务逻辑、业务规则以及访问数据源以进行数据检索和更新。虽然我们可以将数据访问逻辑放入模型中,但它有一些缺点:

如果我们在模型中的多个位置访问相同的数据,则代码会重复。这与 DRY(不要重复自己)设计方法相悖。DRY 是一种设计原则,意味着每条信息都应该唯一地表示,没有冗余。

业务逻辑知道数据源类型,无论数据源是 SQL Server 还是 Oracle。因此,如果数据源更改,模型也需要更新以反映更改。正如我们上面讨论的松耦合设计,我们可以称之为紧耦合设计。

如果我们不分离数据访问功能,那么模型除了封装业务逻辑的主要职责之外,还具有访问数据源并将数据映射到业务实体的额外职责。这使得模型难以进行单元测试,因为要测试模型,我们还需要访问数据源。

如果我们想实现应用程序数据访问规则,则没有一个中心位置来做出更改,相反,必须在多个地方进行更改,这是一个耗时的过程,代码也难以维护。

存储库是一种设计模式,它介于域层和数据层之间。它有助于数据检索和持久化。

使用存储库,我们分离了数据访问和映射职责,因此这使得模型和数据访问逻辑松耦合。

如果我们使用存储库,那么模型层会向存储库请求它想要获取或更新的任何数据。在数据源中检索和更新数据的职责仅在于存储库。我们可以轻松地切换后端数据源,而不会影响应用程序的其余部分

假设我们的设计决策使我们更改数据源,这在敏捷方法论中很常见,在生命周期的后期,如果我们使用存储库模式,我们可以轻松地做到这一点。

业务逻辑以以下方式与存储库通信:

  1. 我们的业务逻辑以业务实体形式从存储库请求数据。
  2. 存储库从业务逻辑接收请求。它向底层数据源发出请求。
  3. 存储库从数据源接收原始数据。
  4. 存储库将数据映射到业务实体并返回实体。

因此,现在模型可以只专注于业务逻辑,而不必担心数据存储的位置和方式。

工作单元

工作单元是一种设计模式,它维护事务列表,用更改更新数据源,并提供并发问题的解决方案因此,如果应用程序正在执行多个并发操作,那么工作单元就是解决方案

 

尽管像 Entity Framework 这样的框架内部实现了工作单元模式,但在应用程序中实现工作单元有几个原因:

  • 我们正在应用程序中实现事务,并希望对数据库的插入、删除和更新进行排序。
  • 我们希望防止应用程序中出现重复更新。在一个应用程序中,我们可以有多个更新操作,其中一些可能是重复的。使用工作单元,只会有一个数据库更新,而不是多个。
  • 我们希望对应用程序中的数据库操作有更多的控制权。

例如,如果我们想同时更新学生详细信息和教师详细信息,那么我们可以通过使用工作单元设计模式来实现这种程度的控制。使用工作单元,我们可以从应用程序的中心位置完全控制数据访问操作。

使用代码

在示例应用程序中,我们将创建一个 MVC 应用程序,该应用程序维护学生列表并提供更新列表的选项。

我们将使用工作单元模式实现 CRUD 操作。我们将创建一个通用存储库类,以便可以在应用程序中针对不同类型的实体进行重用。

IRepository<T> 和 Repository<T>

我们使用接口IRepository为通用存储库类定义接口。它包含用于插入、搜索、更新和删除功能的 CRUD 操作。

  public interface IRepository<T>

    {

        T GetById(int id);

        IQueryable<T> GetAll();

        void Edit(T entity);

        void Insert(T entity);

        void Delete(T entity);

    }

现在我们已经声明了存储库的接口,我们可以声明一个实现通用接口IRepository的通用类。该类有两个字段 DbContext 和 DbSet,分别用于与数据库通信和表示数据。

下面是 Repository 类的完整定义。

public class Repository<T> : IRepository<T> where T:class
    {

        public DbContext context;
        public DbSet<T> dbset;
        public Repository(DbContext context)
        {
            this.context = context;
            dbset = context.Set<T>();
        }

        public T GetById(int id)
        {
            return dbset.Find(id);
        }


        public IQueryable<T> GetAll()
        {
            return dbset;
        }

        public void Insert(T entity)
        {
            dbset.Add(entity);
        }


        public void Edit(T entity)
        {
            context.Entry(entity).State = EntityState.Modified;
        }


        public void Delete(T entity)
        {
            context.Entry(entity).State = EntityState.Deleted;
        }

    }
UnitOfWork 和 IUnitOfWork

UnitOfWork 类维护应用程序中存储库的列表。IUnitOfWork 接口定义了 UnitOfWork 类的操作。

请注意,在 Save 方法中,我们调用了 context.SaveChanges() 方法。在上面的存储库中,我们只是设置了实体状态,但实际上并未插入数据库。只有 Save 方法才会在数据库中更新实体,因此我们的 UnitofWork 类负责数据库更新操作

如果我们使用多个存储库,并且任何更新失败,我们都不会调用 SaveChanges() 方法。由于记录未在存储库中的数据库中更新,因此无需回滚更改。

 public interface IUnitOfWork : IDisposable
    {
        IRepository<Student> StudentRepository { get; }
        void Save();
    }


public partial class UnitOfWork : IUnitOfWork
    {
        private IRepository<Student> _studentRepository;
        private Context _context;
        public IRepository<Student> StudentRepository
        {
            get {

                if (_studentRepository==null)
                    _studentRepository = new Repository<Student>(_context);
                return _studentRepository; }
        }


        public UnitOfWork()
        {
            _context=new Context();
        }

        public void Save()
        {
           _context.SaveChanges();
        }


        public void Dispose()
        {
            throw new NotImplementedException();
        }

    }

 

我们有一个单独的控制器 StudentsController,它访问 UnitofWork 类以根据用户交互执行不同的操作。

当我们执行示例应用程序时,我们可以执行不同的 CRUD 操作。

如果我们想要设计一个系统,其中 ORM 或数据库可以轻松切换,并且还希望能够对具有外部依赖项的业务逻辑进行单元测试,那么存储库模式就是解决方案。

关注点

尽管 MVC 不强制我们遵循特定的设计原则,但由于 MVC 应用程序应该灵活地适应未来的更改并可测试,因此我们应该使用有助于我们创建此类应用程序的设计原则。通过使用某些设计指南,我们可以创建更健壮和灵活的应用程序。

 

© . All rights reserved.