【问题标题】:Loading Subrecords in the Repository Pattern在存储库模式中加载子记录
【发布时间】:2009-08-03 16:07:38
【问题描述】:

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

IRepository

FindAll
FindByID
Insert
Update
Delete

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

WhereSomethingEqualsTrue() ...

我的问题如下:

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

【问题讨论】:

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


    【解决方案1】:

    如果您正在构建您的存储库以特定于一个实体(表),这样每个实体都有您上面列出的 IRepository 接口中的方法列表,那么您真正在做的是@987654321 的实现@ 模式。

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

    我从你的帖子猜到你是seen this example。此示例的问题在于所有存储库在基本级别共享相同的 CRUD 功能,但他没有超越这一点并实现任何域功能。该示例中的所有存储库看起来都一样 - 但实际上,真正的存储库看起来并不都相同(尽管它们仍应进行接口),每个存储库都会关联特定的域操作。

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

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

    等等……

    这些是您的应用程序想要对基础数据执行的特定操作,存储库应提供这些操作。将其视为存储库表示您将在域上执行的一组原子操作。如果您选择通过通用存储库共享某些功能,并使用扩展方法扩展特定存储库,那么这种方法可能适合您的应用。

    一个好的经验法则是很少您的应用程序需要实例化多个存储库来完成一个操作。确实需要,但如果您的应用程序中的每个事件处理程序都在处理六个存储库只是为了获取用户的输入并正确实例化输入表示的实体,那么您可能会遇到设计问题。

    【讨论】:

    • 好了。每天学习一些东西。我需要调整自己的方法:-)
    • 是的。我也从这个答案中学到了一些东西。我知道拥有多个存储库是合乎逻辑的,但不知道每个存储库都应该围绕聚合构建。完全有道理。
    • 如何为聚合的每个部分创建一个单独的存储库,然后再拥有一个组合底层存储库的聚合存储库。这样您就可以在许多聚合存储库中使用相同的存储库?这是一种糟糕的设计实践吗?
    • 您会在哪里评估业务规则?它们是存储库的一部分,还是在使用存储库的服务中进行评估。
    【解决方案2】:

    每个表一个存储库是否常见?

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

    此外,您也许可以从所有存储库中抽象出一些功能...而且,由于您使用的是 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();
            }
        }
    

    【讨论】:

    • 不,每个存储库都应该为一个聚合根封装持久性逻辑
    • 您能否告诉客户端类如何使用函数“T FindByID(Func predicate)”。我正在寻找代码演示。
    【解决方案3】:

    对我来说,存储库模式是关于在您的数据访问方法周围放置一个瘦包装器。在您的情况下使用 LINQ to SQL,但在其他情况下使用 NHibernate。我发现自己在做的是为每个表创建一个存储库,这非常简单(就像布鲁诺列表一样,你已经有了)。那是负责找东西和做 CRUD 操作的。

    但是,正如 Johannes 所提到的,我的服务级别更多地处理聚合根。我将有一个像 GetExistingUser(int id) 这样的方法的 UserService。这将在内部调用 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 to Objects 的灵活性以及所有这些东西,并使用 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;
    }
    

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-21
        • 2020-04-24
        • 2011-07-16
        • 1970-01-01
        • 2012-02-15
        相关资源
        最近更新 更多