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

 广东蒗缦m莎 发布于 2023-02-13 10:12

我正在创建一个通用存储库,不知道实现dispose功能的正确方法是什么:

我没有使用IoC/DI,但我将来会重构我的代码,所以:

我的代码:

IUnitOfWork接口:

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(null);
        }

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

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

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

IGenericRepository接口:

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

        IQueryable Get(Expression> predicate);

        T Find(params object[] keys);

        T GetFirstOrDefault(Expression> predicate);

        bool Any(Expression> predicate);

        void Insert(T entity);

        void Edit(T entity);

        void Delete(Expression> predicate);
    }
}

GenericRepository类:

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

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

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

        public IQueryable GetAll()
        {
            return _entity;
        }

        public IQueryable Get(Expression> 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接口:

namespace MyApplication.Data.Interfaces
{
    public interface IUserRepository : IGenericRepository { }
}

UserRepository类:

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

UserController控制器类:

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

那么,我何时以及如何处置我的控制器和/或我的存储库和上下文?

1 个回答
  • 更新的答案:

    我的控制器示例:

    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容器,所有这些处理代码都将消失.施工和处置应在集装箱层面进行.容器将是唯一知道或关心存储库和工作单元是一次性的地方.

    但我怀疑这些类别中没有一个首先需要是一次性的.您应该在using块中使用SqlConnection.它不需要是一个类级别的字段DatabaseContext.


    请原谅这个答案的长度.我必须建立一些基本原则才能使我的建议有意义.

    固体

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

    接口隔离原理(ISP)

    包括IDisposableIGenericRepository<T>接口上明确违反了ISP.

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

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

    单一责任原则

    我始终保持这篇文章,认真对待单一责任原则,当我将功能代码重新编入优秀的OO代码时,我会很方便.这不是一个容易的主题,文章非常密集.但它作为对SRP的彻底解释是非常宝贵的.

    理解SRP并忽略了99.9%的MVC控制器中存在的缺陷,这些控制器采用了许多DI构造函数参数,这里涉及的一个缺陷是:

    使控制器负责使用存储库和处理存储库交叉到不同的抽象级别,这违反了SRP.

    说明:

    因此,您在存储库对象上至少调用了两个公共方法.一个到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()与构造和破坏对象的关系比与对象的其他成员的使用密切相关,即使它是作为简单的公共方法公开的.

    构造和处置处于同样低的抽象层次.但是因为控制器本身并不构建存储库,所以它存在于更高的抽象层次上.在处置存储库时,它会到达其抽象级别之外,以便在不同级别上调整存储库对象.这违反了SRP.

    代码现实

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

    考虑控制器代码在处置存储库本身时的样子:

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

    控制器在构造时必须接收有用的(非处置的)存储库.这是一种普遍的期望.但是不要在控制器中调用两个方法,否则它会中断.这个控制器只是一次性交易.另外,你甚至无法从另一个公共方法中调用一个公共方法.例如,该Update方法可以Get在将模型存储在存储库中之后调用,以便返回更新的客户视图.但这会爆炸.

    结论

    将存储库作为参数接收意味着其他东西负责创建存储库.其他东西也应该负责妥善处理存储库.

    当对象的生命周期和对象的可能的后续使用不受直接控制时,将对象置于与其(其他)公共成员相同的抽象级别的替代方案是定时炸弹.

    规则IDisposable是:在另一个功能接口声明中继承是绝对不可接受的,IDisposable因为IDisposable它永远不是一个功能性问题,而只是一个实现细节.

    2023-02-13 10:15 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有