在仓储模式中加载子记录
在使用仓储模式时,我发现它主要是将数据访问方法进行简单的封装。在你的情况下是LINQ to SQL,但在其他情况下可能是NHibernate或手工实现。我发现自己会为每个表创建一个非常简单的仓储(就像bruno列出的那样)。它负责查找和执行CRUD操作。
然后我会有一个更多处理聚合根的服务层,就像Johannes提到的那样。我会有一个UserService,其中有一个名为GetExistingUser(int id)的方法。这个方法内部会调用UserRepository.GetById()方法来检索用户。如果你的业务流程要求GetExistingUser()返回的用户类几乎始终需要填充User.IsInRoles()属性,那么只需让UserService依赖于UserRepository和RoleRepository两者即可。伪代码如下所示:
public class UserService { public UserService(IUserRepository userRep, IRoleRepository roleRep) {...} public User GetById(int id) { User user = _userService.GetById(id); user.Roles = _roleService.FindByUser(id); return user; } }
userRep和roleRep将使用LINQ to SQL进行构造,代码如下所示:
public class UserRep : IUserRepository { public UserRep(string connectionStringName) { // 在构建数据上下文时使用连接 } public User GetById(int id) { var context = new DataContext(_conString); // 显然这是动态编写的代码,但你知道我的意思… var user = // linq查询语句 return user; } public IQueryableFindAll() { var context = // ... 同样的模式,延迟执行 } }
个人而言,我会将仓储类限定为内部作用域,而将UserService和其他XXXXXService类设为公共,以保持服务API的使用者的诚实。因此,我认为仓储与与数据存储交互更紧密相关,而服务层更与业务流程的需求相关。
我经常发现自己过于考虑Linq to Objects的灵活性,并使用IQueryable等来构建实际需要的服务方法。在适当的情况下使用LINQ,但不要试图让仓储做所有事情。
public IListActiveUsersInRole(Role role) { var users = _userRep.FindAll(); // IQueryable () - 延迟执行; var activeUsersInRole = from users u where u.IsActive = true && u.Role.Contains(role); // 我不记得任何Linq并且我是伪代码,但 // 再次强调,服务提供了一个简单的接口, // 并将责任委托给具有简单方法的仓储。 return activeUsersInRole; }
这有点啰嗦。不确定我是否真的帮到了什么,但我的建议是避免过于花哨的扩展方法,只需添加另一层来保持每个移动部分相对简单即可。这对我很有效。
从上述内容中可以整理出以下内容:
问题的出现原因:
- 是否每个表格都应该有一个repository?
- 如何在repository中加载子记录?
解决方法:
- 应该根据聚合根来构建repository,而不是每个表格一个repository。
- 可以实现一个基础repository,以通用的方式实现所有常见的功能。
- 可以通过Linq-to-Sql来抽象一些功能。
- 可以使用以下接口和类来实现repository:
interface IRepository: IDisposable where T : class { IEnumerable FindAll(Func predicate); T FindByID(Func predicate); void Insert(T e); void Update(T e); void Delete(T e); } class MyRepository : IRepository where T : class { public DataContext Context { get; set; } public MyRepository(DataContext context) { Context = Context; } public IEnumerable FindAll(Func predicate) { return Context.GetTable ().Where(predicate); } public T FindByID(Func predicate) { return Context.GetTable ().SingleOrDefault(predicate); } public void Insert(T e) { Context.GetTable ().InsertOnSubmit(e); } public void Update(T e) { throw new NotImplementedException(); } public void Delete(T e) { Context.GetTable ().DeleteOnSubmit(e); } public void Dispose() { Context.Dispose(); } }
- 每个repository应该封装一个聚合根的持久化逻辑。
以下是关于如何使用函数"FindByID"的示例代码:
MyRepositoryrepository = new MyRepository (dataContext); SomeClass entity = repository.FindByID(x => x.Id == someId);
问题的出现的原因是因为使用了错误的设计模式。将每个实体(表)都具体化为一个Repository,实际上是实现了Active Record模式。这样的设计模式是错误的,应该识别出域模型中的聚合,以及对它们进行的操作。通常,用户和角色是紧密相关的,应该建立一个围绕用户及其相关实体的单个Repository。
解决方法是重新设计Repository的域操作,使其更具体和具体化。Repository应该提供应用程序对底层数据执行的特定操作。如果选择通过通用Repository共享一些功能,并通过扩展方法扩展特定的Repository,那么这种方法可能对应用程序非常有效。
好的经验法则是,在应用程序需要实例化多个Repository才能完成一个操作的情况应该很少见。虽然这种需求确实会出现,但是如果应用程序的每个事件处理程序都需要处理六个Repository才能处理用户的输入并正确实例化输入所表示的实体,那么可能存在设计问题。
关于业务规则的评估,应该在Repository中还是在使用Repository的Service中进行评估,这取决于具体的情况。