我看到的很多样本都是使用Repository模式(IRepository)来实现的,所以我在学习MVC时是这样做的.
现在我知道它在做什么,我开始看我目前的设计,想知道它是否是最好的方法.
目前我有一个基本的IUserRepository,它定义了FindById(),SaveChanges()等方法.
目前,每当我想加载/查询数据库中的用户表时,我都会按照以下几点做一些事情:
private IUserRepository Repository; public UserController() : this(new UserRepository()) { } [RequiresAuthentication] [AcceptVerbs(HttpVerbs.Get)] public ActionResult Edit(string ReturnUrl,string FirstRun) { var user = Repository.FindById(User.Identity.Name); var viewmodel = Mapper.Map<User,UserEditviewmodel>(user); viewmodel.FirstRun = FirstRun == "1" ? true : false; return View("Edit",viewmodel); } [AcceptVerbs(HttpVerbs.Post),ValidateAntiForgeryToken(Salt = "SaltAndPepper")] public ActionResult Edit(UserEditviewmodel viewmodel,string ReturnUrl) { //Map the viewmodel to the Model var user = Repository.FindById(User.Identity.Name); //Map changes to the user Mapper.Map<UserEditviewmodel,User>(viewmodel,user); //Save the DB changes Repository.SaveChanges(); if (!string.IsNullOrEmpty(ReturnUrl)) return Redirect(ReturnUrl); else return RedirectToAction("Index","User"); }
现在我完全不了解MVC如何在用户创建链接时创建控制器(不知道每个用户是否有1个控制器或每个应用程序有1个控制器),所以我不是最好的过程行动.
我发现一个关于使用通用存储库接口IRepository< T> here也似乎有一个静态RepositoryFactory在许多博客上的想法.基本上只有一个存储库的实例永远保存下来,它通过这个工厂获得
所以我的问题围绕人们在应用程序中的做法,以及什么是好的做法.
人们是否有基于每个表(IUserRepository)的单个存储库?
他们是否使用通用的IRepository?
他们使用静态存储库工厂吗?
还是别的什么呢?
编辑:
我只是意识到我也应该问:
每个控制器上都有一个私有的IRepository吗?或者我每次要使用它时应该实例化一个新的IRepository?
BOUNTY编辑:
我开始一个赏金来获得更多的观点(不是蒂姆没有帮助).
知道人们在他们的MVC应用程序中做什么,或者他们认为是一个好主意,我更好奇.
解决方法
>它假定每个实体使用相同类型的密钥,这在几乎任何非平凡的系统中都不是真的.一些实体将使用GUID,其他实体可能具有某种天然和/或复合密钥. NHibernate可以很好地支持这一点,但是Linq对于sql来说还是很糟糕的 – 你必须编写大量的hackish代码来进行自动键映射.
>这意味着每个存储库只能处理一个实体类型,只支持最简单的操作.当存储库被降级到这样一个简单的CRUD包装器时,它根本没有用.您也可以将客户端交给IQueryable< T>或表T.
它假定您对每个实体执行完全相同的操作.在现实中,这将远离真相.当然,也许您想要通过其ID获得该订单,但更有可能您想要获取特定客户和某些日期范围内的Order对象列表.完全一般的IRepository< T>的概念不允许你几乎肯定希望对不同类型的实体执行不同类型的查询.
存储库模式的全部要点是创建对通用数据访问模式的抽象.我认为有些程序员无聊创建存储库,所以他们说“嘿,我知道,我将创建一个可以处理任何实体类型的über仓库!”这是伟大的,除了存储库几乎没有用的80%的你想要做的.它是一个基类/接口,但如果这是你所做的全部工作,那么你只是懒惰(并保证未来的头痛).
理想情况下,我可以从一个通用存储库开始,看起来像这样:
public interface IRepository<TKey,TEntity> { TEntity Get(TKey id); void Save(TEntity entity); }
你会注意到这没有List或GetAll函数 – 这是因为认为可以在代码中的任何地方从整个表中检索数据是可以接受的,这是荒谬的.当您需要开始进入特定的存储库时:
public interface IOrderRepository : IRepository<int,Order> { IEnumerable<Order> GetOrdersByCustomer(Guid customerID); IPager<Order> GetOrdersByDate(DateTime fromDate,DateTime toDate); IPager<Order> GetOrdersByProduct(int productID); }
等等 – 你得到这个想法.这样我们有一个“通用”存储库,如果我们实际上需要一个令人难以置信的简单的逐个检索语义,但是一般来说,我们永远不会传递给它,当然不是一个控制器类.
现在对于控制器,你必须做到这一点,否则你们几乎都否定了你刚刚把所有的存储库放在一起的所有工作.
控制器需要从外界获取其存储库.您创建这些存储库的原因是您可以进行某种反转控制.您的最终目标是能够将另一个存储库交换出来,例如进行单元测试,或者如果您决定在将来的某个时刻从Linq切换到sql到实体框架.
这个原则的一个例子是:
public class OrderController : Controller { public OrderController(IOrderRepository orderRepository) { if (orderRepository == null) throw new ArgumentNullException("orderRepository"); this.OrderRepository = orderRepository; } public ActionResult List(DateTime fromDate,DateTime toDate) { ... } // More actions public IOrderRepository OrderRepository { get; set; } }
换句话说,控制器不知道如何创建存储库,也不应该.如果您有任何存储库构建,那么它将创建您真正不想要的耦合. ASP.NET MVC示例控制器具有无参数构造函数创建具体存储库的原因是,站点需要能够编译和运行,而不强制您设置一个完整的依赖注入框架.
但是在生产站点中,如果您没有通过构造函数或公共属性传递存储库依赖关系,那么您根本无法使用存储库,因为控制器仍然紧密耦合到数据库层.您需要能够编写如下测试代码:
[TestMethod] public void Can_add_order() { OrderController controller = new OrderController(); FakeOrderRepository fakeRepository = new FakeOrderRepository(); controller.OrderRepository = fakeRepository; //<-- Important! controller.SubmitOrder(...); Assert.That(fakeRepository.ContainsOrder(...)); }
如果您的OrderController正在关闭并创建自己的存储库,则无法执行此操作.该测试方法不应该进行任何数据访问,只是确保控制器基于该操作调用正确的存储库方法.
这不是DI,但请记住,这只是假冒/嘲笑. DI在哪里可以看出,当你决定Linq to sql不足以满足你的需求,而且你真的希望在NHibernate中使用HQL,但是要花费3个月的时间来完成所有的操作,你希望能够一次做这个一个存储库.所以,例如,使用像Ninject这样的DI框架,你所要做的就是改变这一点:
Bind<ICustomerRepository>().To<LinqTosqlCustomerRepository>(); Bind<IOrderRepository>().To<LinqTosqlOrderRepository>(); Bind<IProductRepository>().To<LinqTosqlProductRepository>();
至:
Bind<ICustomerRepository>().To<LinqTosqlCustomerRepository>(); Bind<IOrderRepository>().To<NHibernateOrderRepository>(); Bind<IProductRepository>().To<NHibernateProductRepository>();
而在那里,现在,依赖于IOrderRepository的所有内容都使用NHibernate版本,您只需要更改一行代码,而不是数百行.而且我们正在运行Linq到sql和NHibernate版本,一个接一个地移植功能,而不会中断任何东西.
所以总结一下我所做的一切:
>不要严格依赖一般的IRepository< T>接口.来自存储库的大多数功能都是特定的,而不是通用的.如果要包括IRepository< T>在类/接口层次结构的上层,这很好,但控制器应该依赖于特定的存储库,所以当您发现通用存储库缺少重要方法时,最终不必在5个不同的地方更改代码.>控制器应该从外部接受存储库,而不是创建自己的存储库.这是消除耦合和提高可测性的重要一步.>通常,您将使用依赖注入框架连接控制器,其中许多可以与ASP.NET MVC无缝集成.如果对你来说太多了,那么至少应该使用某种静态服务提供者,以便集中所有的存储库创建逻辑. (从长远来看,您可能会发现只需学习和使用DI框架就更容易).