在仓储模式中加载子记录

22 浏览
0 Comments

在仓储模式中加载子记录

使用LINQ TO SQL作为基础的基于仓储模式的解决方案。我的实现如下:

IRepository

FindAll
FindByID
Insert
Update
Delete

然后我有一些扩展方法用于查询结果,如下所示:

WhereSomethingEqualsTrue() ...

我的问题是:

我的用户仓储有N个角色。我是否应该创建一个角色仓储来管理角色?如果按照这种方式,我担心我会创建数十个仓储(几乎每个表都有一个,除了联接表)。一个表对应一个仓储是常见的做法吗?

0
0 Comments

在使用仓储模式时,我发现它主要是将数据访问方法进行简单的封装。在你的情况下是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 IQueryable FindAll()
    {
        var context = // ... 同样的模式,延迟执行
    }
}

个人而言,我会将仓储类限定为内部作用域,而将UserService和其他XXXXXService类设为公共,以保持服务API的使用者的诚实。因此,我认为仓储与与数据存储交互更紧密相关,而服务层更与业务流程的需求相关。

我经常发现自己过于考虑Linq to Objects的灵活性,并使用IQueryable等来构建实际需要的服务方法。在适当的情况下使用LINQ,但不要试图让仓储做所有事情。

public IList ActiveUsersInRole(Role role)
{ 
    var users = _userRep.FindAll(); // IQueryable() - 延迟执行;
    var activeUsersInRole = from users u where u.IsActive = true && u.Role.Contains(role);
    // 我不记得任何Linq并且我是伪代码,但
    // 再次强调,服务提供了一个简单的接口,
    // 并将责任委托给具有简单方法的仓储。
    return activeUsersInRole;
}

这有点啰嗦。不确定我是否真的帮到了什么,但我的建议是避免过于花哨的扩展方法,只需添加另一层来保持每个移动部分相对简单即可。这对我很有效。

0
0 Comments

从上述内容中可以整理出以下内容:

问题的出现原因:

- 是否每个表格都应该有一个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"的示例代码:

MyRepository repository = new MyRepository(dataContext);
SomeClass entity = repository.FindByID(x => x.Id == someId);

0
0 Comments

问题的出现的原因是因为使用了错误的设计模式。将每个实体(表)都具体化为一个Repository,实际上是实现了Active Record模式。这样的设计模式是错误的,应该识别出域模型中的聚合,以及对它们进行的操作。通常,用户和角色是紧密相关的,应该建立一个围绕用户及其相关实体的单个Repository。

解决方法是重新设计Repository的域操作,使其更具体和具体化。Repository应该提供应用程序对底层数据执行的特定操作。如果选择通过通用Repository共享一些功能,并通过扩展方法扩展特定的Repository,那么这种方法可能对应用程序非常有效。

好的经验法则是,在应用程序需要实例化多个Repository才能完成一个操作的情况应该很少见。虽然这种需求确实会出现,但是如果应用程序的每个事件处理程序都需要处理六个Repository才能处理用户的输入并正确实例化输入所表示的实体,那么可能存在设计问题。

关于业务规则的评估,应该在Repository中还是在使用Repository的Service中进行评估,这取决于具体的情况。

0