在存储库模式中加载子记录

时间:2009-08-03 16:07:38

标签: c# linq-to-sql repository domain-driven-design design-patterns

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

IRepository

FindAll
FindByID
Insert
Update
Delete

然后我有扩展方法用于查询结果:

WhereSomethingEqualsTrue() ...

我的问题如下:

我的用户存储库有N个角色。我是否创建了角色存储库来管理角色?我担心,如果我走这条路,我最终会创建几十个存储库(每个表几个,除了连接表)。每个表的存储库是否通用?

4 个答案:

答案 0 :(得分:32)

如果要构建特定于一个实体(表)的存储库,以便每个实体都具有上面列出的IRepository接口中的方法列表,那么您实际所做的是{{的实现3}}模式。

绝对不每个表都有一个存储库。您需要识别域模型中的聚合以及要对其执行的操作。用户和角色通常紧密相关,通常您的应用程序将与它们一起执行操作 - 这需要一个单独的存储库,以用户及其密切相关的实体为中心。

我猜你的帖子中有Active Record。此示例的问题是所有存储库在基础级别共享相同的CRUD功能,但他没有超出此范围并实现任何域功能。该示例中的所有存储库看起来都相同 - 但实际上,真实的存储库看起来并不相同(尽管它们仍然应该是接口的),每个存储库都会有特定的域操作。

您的存储库域操作应该更像:

userRepository.FindRolesByUserId(int userID)
userRepository.AddUserToRole(int userID)
userRepository.FindAllUsers()
userRepository.FindAllRoles()
userRepository.GetUserSettings(int userID)

等...

这些是您的应用程序想要对基础数据执行的特定操作,而存储库应提供该操作。可以把它想象成Repository表示您将在域上执行的原子操作集。如果您选择通过通用存储库共享某些功能,并使用扩展方法扩展特定存储库,那么这种方法可能适用于您的应用程序。

一个好的经验法则是,您的应用程序应该罕见需要实例化多个存储库才能完成操作。这种需求确实存在,但是如果你的应用程序中的每个事件处理程序只是为了获取用户的输入而正确地实例化输入所代表的实体,那么你可能会遇到设计问题。

答案 1 :(得分:4)

  

每个表的存储库是否通用?

不,但你仍然可以有几个repositiories。您应该围绕聚合构建存储库。

此外,您可能能够从所有存储库中抽象出一些功能......而且,由于您使用的是Linq-to-Sql,因此您可以...

您可以实现基本存储库,它以通用方式实现所有这些常用功能。

以下示例仅用于证明这一点。它可能需要很多改进......

    interface IRepository<T> : IDisposable where T : class
    {
        IEnumerable<T> FindAll(Func<T, bool> predicate);
        T FindByID(Func<T, bool> predicate);
        void Insert(T e);
        void Update(T e);
        void Delete(T e);
    }

    class MyRepository<T> : IRepository<T> where T : class
    {
        public DataContext Context { get; set; }

        public MyRepository(DataContext context)
        {
            Context = Context;
        }

        public IEnumerable<T> FindAll(Func<T,bool> predicate)
        {
            return Context.GetTable<T>().Where(predicate);
        }

        public T FindByID(Func<T,bool> predicate)
        {
            return Context.GetTable<T>().SingleOrDefault(predicate);
        }

        public void Insert(T e)
        {
            Context.GetTable<T>().InsertOnSubmit(e);
        }

        public void Update(T e)
        {
            throw new NotImplementedException();
        }

        public void Delete(T e)
        {
            Context.GetTable<T>().DeleteOnSubmit(e);
        }

        public void Dispose()
        {
            Context.Dispose();
        }
    }

答案 2 :(得分:1)

对我来说,存储库模式是关于为数据访问方法设置一个瘦包装器。在你的情况下LINQ to SQL,但NHibernate,在其他人手动滚动。我发现自己正在做的是为每个表创建一个非常简单的存储库(就像bruno列表,你已经有了)。它负责查找和执行CRUD操作。

但是,正如约翰内斯所提到的那样,我的服务水平更多地涉及聚合根。我会有一个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)
    {
        // user the conn when building your datacontext
    }

    public User GetById(int id)
    {
        var context = new DataContext(_conString);
        // obviously typing this freeform but you get the idea...
        var user = // linq stuff
        return user;
    }

    public IQueryable<User> FindAll()
    {
        var context = // ... same pattern, delayed execution
    }
}

我个人会在内部确定存储库类的范围,并将UserService和其他XXXXXService类公开,因此请保持服务API的使用者诚实。因此,我再次看到存储库与与数据存储区通信的行为关联得更紧密,但您的服务层与业务流程的需求更紧密地联系在一起。

我经常发现自己真的过分思考Linq对象和所有这些东西的灵活性,并使用IQuerable et al 而不是仅仅构建吐出我实际需要的服务方法。用户LINQ在适当的地方,但不要试图让存储库做任何事情。

public IList<User> ActiveUsersInRole(Role role)
{ 
    var users = _userRep.FindAll(); // IQueryable<User>() - delayed execution;
    var activeUsersInRole = from users u where u.IsActive = true && u.Role.Contains(role);
    // I can't remember any linq and i'm type pseudocode, but
    // again the point is that the service is presenting a simple
    // interface and delegating responsibility to
    // the repository with it's simple methods.
    return activeUsersInRole;
}

所以,这有点漫无边际。不确定我是否真的帮助过,但我的建议是避免使用扩展方法过于花哨,只需添加另一层以保持每个移动部件非常简单。适合我。

答案 3 :(得分:1)

如果我们像Womp建议的那样编写我们的存储库层,我们将什么放在服务层中。我们是否必须重复相同的方法调用,这些方法调用主要包括对相应存储库方法的调用,以便在我们的控制器或代码隐藏中使用?这假设您有一个服务层,您可以在其中编写验证,缓存,工作流,身份验证/授权代码,对吗?还是我离开基地?