c# - 如何在存储库中实现IDisposable继承?

标签 c# repository

我正在创建通用存储库,并且不知道实现处置功能的正确方法是什么:

我没有使用IoC/DI,但是将来我会重构代码来做到这一点,所以:

我的代码:

IUnitOfWork接口(interface):

namespace MyApplication.Data.Interfaces
{
    public interface IUnitOfWork
   {
       void Save();
   }
}

DatabaseContext类:
namespace MyApplication.Data.Infra
{
    public class DatabaseContext : DbContext, IUnitOfWork
    {
        public DatabaseContext(): base("SQLDatabaseConnectionString")
        {
            Database.SetInitializer<DatabaseContext>(null);
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // Same code.
            base.OnModelCreating(modelBuilder);
        }

        #region Entities mapping
        public DbSet<User> User { get; set; }
        // >>> A lot of tables
        #endregion

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

IGenericRepository接口(interface):
namespace MyApplication.Data.Interfaces
{
    public interface IGenericRepository<T> where T : class
    {
        IQueryable<T> GetAll();

        IQueryable<T> Get(Expression<Func<T, bool>> predicate);

        T Find(params object[] keys);

        T GetFirstOrDefault(Expression<Func<T, bool>> predicate);

        bool Any(Expression<Func<T, bool>> predicate);

        void Insert(T entity);

        void Edit(T entity);

        void Delete(Expression<Func<T, bool>> predicate);
    }
}

GenericRepository类:
namespace MyApplication.Data.Repositories
{
    public class GenericRepository<T> : IDisposable, IGenericRepository<T> where T  : class
    {
        private DatabaseContext _context;
        private DbSet<T> _entity;

        public GenericRepository(IUnitOfWork unitOfWork)
        {
            if (unitOfWork == null)
                throw new ArgumentNullException("unitofwork");

            _context = unitOfWork as DatabaseContext;
            _entity = _context.Set<T>();
        }

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

        public IQueryable<T> Get(Expression<Func<T, bool>> predicate)
        {
            return _entity.Where(predicate).AsQueryable();
        }

        // I delete some of the code to reduce the file size.

        #region Dispose
        public void Dispose()
        {
            // HERE IS MY FIRST DOUBT: MY METHOD ITS OK?!
            // I saw implementations with GC.Suppress... and dispose in destructor, etc.

            _context.Dispose();
        }
       #endregion
    }
}

IUserRepository接口(interface):
namespace MyApplication.Data.Interfaces
{
    public interface IUserRepository : IGenericRepository<User> { }
}

UserRepository类:
namespace MyApplication.Data.Repositories
{
    public class UserRepository : GenericRepository<User>, IUserRepository, IDisposable
    {
        public UserRepository(IUnitOfWork unitOfWork) : base(unitOfWork) {}
    }
}

UserController Controller 类:
namespace MyApplication.Presentation.MVCWeb.Controllers
{
    [Authorize]
    public class UserController : Controller
    {
        private IUserRepository _userRepository;
        private IProfileRepository _profileRepository;
        private IUnitOfWork _unitOfWork;

        public UserController()
        {
            this._unitOfWork = new DatabaseContext();
            this._userRepository = new UserRepository(_unitOfWork);
            this._profileRepository = new ProfileRepository(_unitOfWork);
        }

        public ActionResult List()
        {
            return View(this._userRepository.GetAll().ToList());
        }

        public ActionResult Create()
        {
            ViewBag.Profiles = new SelectList(this._profileRepository.GetAll().ToList(), "Id", "Name");

            return View(new User());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Exclude = "Id, Status, CompanyId")] User model)
        {
            ViewBag.Profiles = new SelectList(this._profileRepository.GetAll().ToList(), "Id", "Name");

            if (ModelState.IsValid)
            {
                model.EmpresaId = 1;
                model.Status = Status.Active;

                _userRepository.Insert(model);
                _unitOfWork.Save();

                return RedirectToAction("List");
            }
            else
            {
                return View();
            }
        }
    }

那么,何时以及如何处置 Controller 和/或存储库和上下文?

最佳答案

更新的答案:

我的 Controller 示例:

private IGenericRepository<Profile> _repository; 
private IUnitOfWork _unitOfWork = new DatabaseContext();
public ProfileController() 
{ 
    this._repository = new GenericRepository<Profile>(_unitOfWork); 
}

public class ProfileController : Controller 
{
    private IGenericRepository<Profile> _repository;
    private IUnitOfWork _unitOfWork = new DatabaseContext();  
    public ProfileController()  
    { 
        this._repository = new GenericRepository<Profile>(_unitOfWork);  
    }
}

使用您现在拥有的代码,最好的办法是重写Controller.Dispose(bool disposing)并在其中处置存储库。
protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        IDisposable d = _repository as IDisposable;
        if (d != null)
            d.Dispose();
        GC.SupressFinalize(this);
    }
    base.Dispose(disposing);
}

一旦开始使用IOC容器,所有这些处理代码都将消失。施工和处置应在容器一级进行。该容器将是唯一知道或关心存储库和工作单元是一次性的地方。

但是我怀疑这些类(class)中的任何一个都不需要是一次性的。您应该在using块中使用SqlConnection。它不需要是DatabaseContext中的类级字段。

请原谅这个答案的长度。为了使我的建议有意义,我必须建立一些基本原则。

S.O.L.I.D.

SOLID ...代表面向对象编程和设计的五项基本原则。这里需要关注的两个原则是IS

接口(interface)隔离原则(ISP)

IDisposable接口(interface)上包含IGenericRepository<T>明显违反了ISP。

这样做是因为存储库的可处置性(以及正确处置对象的必要性)与它的设计目的无关,该目的是获取和存储聚合根对象。通过将接口(interface)组合在一起,可以得到一个非隔离的接口(interface)。

除了违反某些理论原理外,为什么这很重要?我将在下面解释。但是首先,我必须介绍另一个SOLID原则:

单一可回收性原则

当我将功能代码重构为良好的OO代码时,我总是将这篇文章Taking the Single Responsibility Principle Seriously放在方便的位置。这不是一个容易的主题,并且文章非常密集。但是,它对于SRP的透彻解释是无价的。

了解SRP并忽略99.9%的所有MVC Controller 中存在许多DI构造函数参数的缺陷,这里涉及的一个缺陷是:

使 Controller 既负责使用存储库又负责存储库的处置,会跨越到不同的抽象级别,这违反了SRP。

解释:

因此,您要在存储库对象上调用至少两个公共(public)方法。一种是对对象进行Get,另一种是对存储库的Dispose。这没什么错吧?通常不,在存储库或任何对象上调用两个方法都没有错。

但是Dispose()是特殊的。处置对象的惯例是,在处置对象后它将不再有用。此约定是使用模式建立单独的代码块的原因之一:
using (var foo = new Bar())
{
    ...  // This is the code block
}

foo.DoSomething();  // <- Outside the block, this does not compile

从技术上讲这是合法的:
var foo = new Bar();
using (foo)
{
    ...  // This is the code block
}

foo.DoSomething();   // <- Outside the block, this will compile

但这会在处置对象后发出使用对象的警告。这是不合适的,这就是为什么您在MS文档中看不到这种用法的示例。

由于这种独特的约定Dispose(),与对象的构造和销毁比与对象的其他成员的使用关系更紧密,即使它作为简单的公共(public)方法公开也是如此。

构造和处置处于同一较低的抽象级别。但是因为 Controller 不是在构造存储库本身,所以它处于更高的抽象级别。处置存储库时,它超出了其抽象级别,在另一个级别上与存储库对象无关。这违反了SRP。

代码现实

好的,所有这些理论就我的代码而言到底意味着什么?

考虑一下 Controller 代码处置存储库本身时的外观:
public class CustomerController : Controller
{
    IGenericRepository<Customer> _customerRepo;
    IMapper<Customer, CustomerViewModel> _mapper;

    public CustomerController(
        IMapper<Customer, CustomerViewModel> customerRepository,
        IMapper<Customer, CustomerViewModel> customerMapper)
    {
        _customerRepo = customerRepository;
        _customerMapper = customerMapper;
    }

    public ActionResult Get(int id)
    {
        CustomerViewModel vm;
        using (_customerRepo)  // <- This looks fishy
        {
            Customer cust = _customerRepo.Get(id);
            vm = _customerMapper.MapToViewModel(cust);
        }
        return View(wm);
    }

    public ActionResult Update(CustomerViewModel vm)
    {
        Customer cust = _customerMapper.MapToModel(vm);
        CustomerViewModel updatedVm;
        using(_customerRepo)  // <- Smells like 3 week old flounder, actually
        {
            Customer updatedCustomer = _customerRepo.Store(cust);
            updatedVm = _customerMapper.MapToViewModel(updatedCustomer);
        }
        return View(updatedVm);
    }
}

Controller 在构造时必须收到一个有用的(未处置的)存储库。这是一个普遍的期望。但是不要在 Controller 中调用两个方法,否则它将中断。该 Controller 仅是一次性交易。另外,您甚至不能从另一个内部调用一个公共(public)方法。例如。在将模型存储在存储库中之后,Update方法可以调用Get以便返回更新的客户 View 。但这会炸毁。

结论

将存储库作为参数接收意味着创建存储库的其他原因。还有其他事情也应负责正确处置存储库。

当对象的生存期以及对象的可能后续使用不受直接控制时,将对象置于与使用其(其他)公共(public)成员相同的抽象级别上的另一种选择是计时炸弹。
IDisposable的规则是这样的:在另一个功能接口(interface)声明中继承IDisposable是永远不能接受的,因为IDisposable永远不是功能问题,而只是实现细节。

关于c# - 如何在存储库中实现IDisposable继承?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20358844/

相关文章:

c# - 使用 c# 桌面应用程序编写脚本

c# - 如何正确测试为非空响应返回 json 的 API Controller ?

java - 如何分离存储库和服务层

svn - 合并来自并行 Subversion 存储库的更新代码

c# - 将模型集合中的一项发布到 MVC 中的 Controller 方法

c# - c#中的字符串插入问题

c# - Unix 日期时间差异

perl - 安装 perl 模块时 cpan 不使用配置的存储库而是尝试连接到 cpan.org

SVN:将存储库主干移动到另一个分支(有历史记录)

macos - 如何使用 homebrew 获取 avidemux