Ala*_*tts 14 .net asp.net-mvc database-abstraction
我正在设计我的ASP.NET MVC应用程序,我遇到了一些有趣的想法.
我见过的很多样本都描述并使用了Repository模式(IRepository),所以这就是我学习MVC时的方式.
现在我知道它正在做什么,我开始看看我目前的设计并想知道它是否是最好的方式.
目前,我有一个基本的IUserRepository,它定义方法,如FindById(),SaveChanges()等.
目前,每当我想在DB中加载/查询用户表时,我都会按照以下方式执行操作:
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");
}
Run Code Online (Sandbox Code Playgroud)
现在我还不完全理解MVC在用户创建链接时如何工作(不确定每个用户是否有1个控制器或每个应用程序有1个控制器),所以我并不认为最好的方法是行动.
我发现关于使用一个通用存储库接口的一个很大的问题IRepository<T> 在这里和也显得静态的想法RepositoryFactory在许多博客.基本上只保留一个存储库实例,它是通过这个工厂获得的
所以我的问题围绕着人们如何在应用程序中做到这一点,以及什么是好的做法.
人们是否有基于每个表(IUserRepository)的个人存储库?
他们使用通用IRepository<T>吗?
他们使用静态存储库工厂吗?
或完全不同的东西?
编辑:我刚才意识到我应该问:
IRepository在每个控制器上有一个私人的好方法吗?或者我应该IRepository每次想要使用它时实例化一个新的?
BOUNTY编辑:我开始获得更多观点的赏金(不是蒂姆没有帮助).
我更想知道人们在他们的MVC应用程序中做了什么,或者他们认为什么是好主意.
Aar*_*ght 24
泛型的一些非常明显的问题IRepository<T>:
它假设每个实体使用相同类型的密钥,这在几乎任何非平凡的系统中都不是这样.一些实体将使用GUID,其他实体可能具有某种自然和/或复合键.NHibernate可以很好地支持这一点,但Linq to SQL非常糟糕 - 你必须编写大量的hackish代码来进行自动键映射.
这意味着每个存储库只能处理一种实体类型,并且只支持最简单的操作.当存储库被降级为这样一个简单的CRUD包装器时,它几乎没有用处.你可以把客户端交给IQueryable<T>或Table<T>.
它假定您对每个实体执行完全相同的操作.实际上,这与事实相去甚远.当然,也许你想Order通过它的ID 得到它,但更有可能你想获得Order特定客户的对象列表,并在某个日期范围内.完全通用的概念IRepository<T>不允许您几乎肯定想要对不同类型的实体执行不同类型的查询.
存储库模式的重点是创建对常见数据访问模式的抽象.我认为一些程序员对创建存储库感到厌倦,所以他们说"嘿,我知道,我会创建一个可以处理任何实体类型的über-repository!" 除了存储库对于您尝试执行的操作的80%毫无用处之外,这是很棒的.它作为一个基类/接口很好,但如果这是你所做的工作的全部范围,那么你只是懒惰(并保证未来的头痛).
理想情况下,我可能会从一个看起来像这样的通用存储库开始:
public interface IRepository<TKey, TEntity>
{
TEntity Get(TKey id);
void Save(TEntity entity);
}
Run Code Online (Sandbox Code Playgroud)
你会发现,这不具有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);
}
Run Code Online (Sandbox Code Playgroud)
等等 - 你明白了.这样我们就拥有了"通用"存储库,如果我们真的需要非常简单的check-by-id语义,但总的来说我们实际上永远不会传递它,当然也不会传递给控制器类.
现在对于控制器来说,你必须做到这一点,否则你几乎否定了你在整理所有存储库时所做的所有工作.
控制器需要从外部世界获取其存储库.您创建这些存储库的原因是您可以执行某种控制反转.这里的最终目标是能够将一个存储库换成另一个存储库 - 例如,进行单元测试,或者如果您决定在将来的某个时刻从Linq切换到SQL到Entity Framework.
这个原则的一个例子是:
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; }
}
Run Code Online (Sandbox Code Playgroud)
换句话说,Controller 不知道如何创建存储库,也不应该.如果你在那里有任何存储库构造,它就会创建你真正不想要的耦合.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(...));
}
Run Code Online (Sandbox Code Playgroud)
如果您OrderController要开始创建自己的存储库,则无法执行此操作.此测试方法不应该进行任何数据访问,它只是确保控制器根据操作调用正确的存储库方法.
这还不是DI,请注意,这只是假装/嘲笑.当你认为Linq to SQL对你做得不够而且你真的想要在NHibernate中使用HQL时,DI会进入画面,但是你需要3个月的时间来移植所有东西,并且你希望能够一次做这个存储库.所以,例如,使用像Ninject这样的DI框架,你所要做的就是改变这个:
Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<LinqToSqlOrderRepository>();
Bind<IProductRepository>().To<LinqToSqlProductRepository>();
Run Code Online (Sandbox Code Playgroud)
至:
Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<NHibernateOrderRepository>();
Bind<IProductRepository>().To<NHibernateProductRepository>();
Run Code Online (Sandbox Code Playgroud)
而且你现在所依赖的一切IOrderRepository都是使用NHibernate版本,你只需要更改一行代码而不是可能有数百行代码.我们并排运行Linq to SQL和NHibernate版本,逐个移植功能,而不会破坏中间的任何东西.
总结一下我所做的所有要点:
不要严格依赖通用IRepository<T>接口.您希望从存储库获得的大多数功能都是特定的,而不是通用的.如果你想IRepository<T>在类/接口层次结构的较高层包含一个,那很好,但是控制器应该依赖于特定的存储库,所以当你发现通用存储库时,你最终不必在5个不同的地方更改你的代码缺少重要的方法.
控制器应该从外部接受存储库,而不是创建自己的存储库.这是消除耦合和提高可测试性的重要步骤.
通常,您需要使用依赖注入框架连接控制器,其中许多可以与ASP.NET MVC无缝集成.如果这对您来说太过分了,那么至少您应该使用某种静态服务提供程序,这样您就可以集中所有存储库创建逻辑.(从长远来看,你可能会发现学习和使用DI框架更容易).