数据库抽象层设计 - 使用 IRepository 的正确方法?
-
23-09-2019 - |
题
我正在设计 ASP.NET MVC 应用程序,并且遇到了一些有趣的想法。
我见过的许多示例都描述并使用了存储库模式(IRepository
)所以这就是我在学习 MVC 时所做的方式。
现在我知道它在做什么,我开始审视我当前的设计并想知道这是否是最好的方法。
目前我有一个基本的 IUserRepository
, ,它定义了诸如 FindById()
, SaveChanges()
, , ETC。
目前,每当我想加载/查询数据库中的用户表时,我都会执行以下操作:
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>
这里 并且似乎也有静态的想法 RepositoryFactory
在许多博客上。基本上只保留 1 个存储库实例,并且是通过此工厂获取的
所以我的问题围绕人们如何在这些应用程序中做到这一点,以及什么被认为是好的做法。
人们是否有基于每个表的单独存储库(IUserRepository
)?
他们使用通用的吗 IRepository<T>
?
他们使用静态存储库工厂吗?
或者完全是别的什么?
编辑:我刚刚意识到我也应该问:
拥有私人 IRepository
在每个控制器上有一个好的方法吗?或者我应该实例化一个新的 IRepository
每次我想用它?
赏金编辑:我正在开始悬赏以获得更多观点(并不是说蒂姆的没有帮助)。
我更想知道人们在他们的 MVC 应用程序中做了什么或者他们认为什么是好主意。
解决方案
通用概念的一些非常明显的问题 IRepository<T>
:
它假设每个实体都使用相同类型的密钥,但这在几乎所有重要的系统中都是不正确的。一些实体将使用 GUID,其他实体可能具有某种自然密钥和/或复合密钥。NHibernate 可以很好地支持这一点,但 Linq to SQL 在这方面表现得相当糟糕 - 您必须编写大量的 hackish 代码来进行自动键映射。
这意味着每个存储库只能处理一种实体类型,并且只支持最琐碎的操作。当存储库被降级为这样一个简单的 CRUD 包装器时,它根本没有什么用处。您不妨直接交给客户一个
IQueryable<T>
或者Table<T>
.它假设您对每个实体执行完全相同的操作。事实上,这与事实相去甚远。当然, 或许 你想得到那个
Order
通过其 ID,但您更有可能想要获取以下列表Order
特定客户和某个日期范围内的对象。完全通用的概念IRepository<T>
不允许您几乎肯定希望对不同类型的实体执行不同类型的查询。
存储库模式的重点是创建一个 抽象 超过常见的数据访问模式。我认为有些程序员对创建存储库感到无聊,因此他们说:“嘿,我知道,我会创建一个可以处理任何实体类型的über-Repository!”这是很棒的,除了存储库对您要做的80%的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);
}
等等——你明白了。这样,如果我们确实需要极其简单的按 id 检索语义,我们就拥有了“通用”存储库,但一般来说,我们永远不会真正将其传递给控制器类,当然也不会传递给控制器类。
现在对于控制器来说,您必须正确执行此操作,否则您几乎就否定了刚刚在整理所有存储库时所做的所有工作。
控制器需要 从外部世界获取其存储库. 。您创建这些存储库的原因是您可以进行某种控制反转。您的最终目标是能够将一个存储库替换为另一个存储库 - 例如,进行单元测试,或者如果您决定在将来的某个时候从 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,这只是伪造/嘲笑。当您认为 Linq to SQL 的功能还不够,并且您确实想要 NHibernate 中的 HQL,但需要 3 个月的时间来移植所有内容,并且您希望能够一次执行一个存储库。例如,使用 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 to SQL 和 NHibernate 版本,一点一点地移植功能,而不会破坏中间的任何内容。
总结一下我提出的所有观点:
不要严格依赖泛型
IRepository<T>
界面。您希望从存储库获得的大部分功能是 具体的, , 不是 通用的. 。如果你想包括一个IRepository<T>
在类/接口层次结构的上层,这很好,但控制器应该依赖于 具体的 因此,当您发现通用存储库缺少重要方法时,您不必在 5 个不同的位置更改代码。控制器应该接受来自外部的存储库,而不是创建自己的存储库。这是消除耦合和提高可测试性的重要一步。
通常,您需要使用依赖注入框架来连接控制器,其中许多控制器可以与 ASP.NET MVC 无缝集成。如果这对您来说太多了,那么至少您应该使用某种静态服务提供程序,以便可以集中所有存储库创建逻辑。(从长远来看,您可能会发现学习和使用 DI 框架更容易)。
其他提示
确实让人有基于每个表(IUserRepository)个人repositorys?的 我倾向于具有用于每个聚集,而不是每个表的存储库。
他们是否使用一个通用的IRepository?的 如果可能的话,是
他们是否使用静态库的工厂?的 我喜欢库实例的注射通过IOC容器
下面是我如何使用它。我使用的IRepository所有通用于所有的存储库的操作。
public interface IRepository<T> where T : PersistentObject
{
T GetById(object id);
T[] GetAll();
void Save(T entity);
}
和我也使用用于每个骨料专用的ITRepository用于操作该是不同的这个仓库。例如,对于用户我将使用IUserRepository添加方法,该方法是不同的,以UserRepository:
public interface IUserRepository : IRepository<User>
{
User GetByUserName(string username);
}
实施将看起来像这样:
public class UserRepository : RepositoryBase<User>, IUserRepository
{
public User GetByUserName(string username)
{
ISession session = GetSession();
IQuery query = session.CreateQuery("from User u where u.Username = :username");
query.SetString("username", username);
var matchingUser = query.UniqueResult<User>();
return matchingUser;
}
}
public class RepositoryBase<T> : IRepository<T> where T : PersistentObject
{
public virtual T GetById(object id)
{
ISession session = GetSession();
return session.Get<T>(id);
}
public virtual T[] GetAll()
{
ISession session = GetSession();
ICriteria criteria = session.CreateCriteria(typeof (T));
return criteria.List<T>().ToArray();
}
protected ISession GetSession()
{
return new SessionBuilder().GetSession();
}
public virtual void Save(T entity)
{
GetSession().SaveOrUpdate(entity);
}
}
比在UserController中将如下:
public class UserController : ConventionController
{
private readonly IUserRepository _repository;
private readonly ISecurityContext _securityContext;
private readonly IUserSession _userSession;
public UserController(IUserRepository repository, ISecurityContext securityContext, IUserSession userSession)
{
_repository = repository;
_securityContext = securityContext;
_userSession = userSession;
}
}
比存储库被使用利用定制控制器工厂依赖注入模式实例化。我使用 StructureMap 作为我的依赖注入层。
在数据库层是NHibernate的。的ISession是通往此会话中的数据库。
我建议你把眼光放在 CodeCampServer 的结构,你可以学到很多东西从它
这是你可以学习另一个项目回回是谁能帮我。这我不还不够深入呢。
人们是否拥有基于每个表的单独存储库(IUserRepository)?
是的,这是更好的选择,原因有两个:
- 我的 DAL 基于 Linq 到 Sql (但我的 DTO 是基于 LTS 实体的接口)
- 执行的操作是 原子 (添加是一个原子操作,保存是另一个原子操作,等等)
他们使用通用的 IRepository 吗?
是的, 只, ,受 DDD Entity/Value 方案的启发,我创建了 IRepositoryEntity / IRepositoryValue 和一个用于其他内容的通用 IRepository。
他们使用静态存储库工厂吗?
是和否:我用一个 国际奥委会集装箱 通过静态类调用。出色地...我们可以说它是一种工厂。
笔记 :我自己设计了这个架构,我的一位同事发现它非常棒,因此我们目前正在这个模型上创建整个公司的框架(是的,这是一家年轻的公司)。这绝对是值得尝试的事情,即使我觉得这种框架将由主要参与者发布。
你可以找到一个优秀的 通用存储库 编写的库是为了使其能够用作 codeplex 上的 WebForms asp:ObjectDataSource: 多层LinqToSql
我的每个控制器都有私有存储库来存储它们需要支持的操作。