【问题标题】:implement a repository that supports lazy loaing for the entity objects?实现一个支持延迟加载实体对象的存储库?
【发布时间】:2010-11-02 00:39:46
【问题描述】:

是否有实现一个使用活动目录(或除关系数据库之外的任何其他数据源)作为数据源并且可以获得能够延迟加载其关联的实体对象的存储库?

一个例子:

两个类将在我们将要开发的各种应用程序中经常使用

class User
{
   public string DisplayName {get; set;}
   public string UserID {get; set;}
   public string Email {get; set;}
   public string WorkPhone {get; set;}
   // etc.
   public string IList<Group> Groups {get; private set;}
   public User Manager {get; set;}
}

class Group
{
   public string DisplayName {get; set;}
   public string Email {get; set;}
   // etc.
   public IList<User> Members {get; private set;}
   public User Owner {get; private set;}
}

我希望能够在我们未来的所有应用程序中重用这两个类。对于某些应用程序,UserGroup 的数据源现在是活动目录,但将来我们可能想使用一些数据库或数据库和活动目录的混合。为了代码重用,我会使用the repository pattern 设计两个存储库MySqlDBRepositoryActiveDirectoryRepository 供我检索和存储Users 和Groups。

假设我们已经有一个活动目录库,它可以用来获取以文本形式表示的用户信息,即您可以获取用户名、电子邮件、经理 ID 等字符串格式。我需要实现一个存储库,该存储库的使用方式与实体框架或 NHibernate 几乎相同,即

 var user = _activeDirectoryRepository.GetUserByUserID("bryan");

 foreach(var group in user.Groups) 

     Console.WriteLine(group.Manager.DisplayName);

最重要的是,user.groupsgroup.Manager 都应该自动延迟加载。我应该如何实现_activeDirectoryRepository.GetUserByUserID("bryan")

【问题讨论】:

  • 不确定如何在活动目录上方实现存储库...但我会说这绝对是要走的路。

标签: c# design-patterns domain-driven-design repository-pattern


【解决方案1】:

是的,这是解决问题的好方法。

定义一个接口来表示存储库的操作,例如:

public interface IAccountRepository 
{ 
   User GetUserByAccountName(string accountName);

   List<User> GetUsers(Group group); 
   List<Group> GetGroups(User user); 

   // ... etc...
} 

然后创建一个业务逻辑层将存储库映射到对象:

public class Accounts
{ 
   IAccountRepository _repository; 

   public Accounts(IAccountRepository repository) 
   { 
      _repository = repository; 
   } 

   public List<User> GetUsers(Group group) 
   { 
      return _repository.GetUsers(group); 
   } 

   public List<Group> GetGroups(User user) 
   { 
      return _repository.GetGroups(user); 
   } 

   public User GetUserByAccountName(string accountName)
   {
       return _repository.GetUserByAccountName(accountName); 
   }



   // etc...
} 

然后使用:

static void Main(string[] args) 
{ 
   // Load from the MSSQL repository. 
   Accounts accounts = new Accounts(new MSSQLAccountRepository());

   User user = accounts.GetUserByAccountName("someuser"); 

   //...

}

另见:Repository pattern tutorial in C#

【讨论】:

  • 在这种情况下我可以使用 user.Groups 吗?
  • 这里的 Accounts 类似乎没有任何价值,因为它只是完全包装了 IAccountRepositoy。事实上,它可以实现 IAccountRepository 本身。那么为什么不直接引用 IAccountRepository 并完成它呢?
  • 同意@qstarin - 回购/业务层组合没有问题,但如果您要走这条路,请使用单个 IQueryable&lt;User&gt; Find() 方法(延迟执行)和您的回购业务层对此执行查询。在你的 repo 中有 10 多个 find 方法并将它们全部包装在另一层 (IMO) 中没有意义
  • Re:Accounts 类,我已经简化到您正确的程度;它只是一个包装器。这个想法是在其中包含更复杂的业务方法。
【解决方案2】:

我希望能够在我们所有的应用程序中重用这两个类。

好的,我建议重新评估整个想法。

Reuse is [often] a fallacy.

将这些类移动到链接到多个应用程序的可重用库中究竟有什么收益

将这些类移动到链接到多个应用程序的可重用库中究竟会遇到什么痛苦

我非常有信心,如果你走这条路,你会体验更多的痛苦而不是收获。

我不想将用户、组类定义(源代码)从一个应用程序复制到另一个应用程序。

为什么?这对你有什么真正的好处?

您真的希望通过单击“添加引用”与复制和粘贴代码文件目录并单击“添加现有项”来节省多少时间?

您认为第一次更改此库并在某个应用程序中创建错误而不是您进行更改的应用程序时会浪费多少时间?您将花费多少时间来管理链接到多个不同应用程序的共享库的多个版本?你认为你会浪费多少时间过度概括这个库的概念,而不是只做你手头的应用程序需要的事情?当您在需要的实体比您放入库中的更多的域中使用它时,扩展用户和组需要多长时间?

如果你继续这样做,我愿意打赌你会输得比得到的多。

【讨论】:

  • +1 除了假设,这里还有很多重要的考虑因素。
  • 我不想复制和粘贴相同的代码,不是因为我想节省复制和粘贴的时间。这是因为我不想在收到更改请求时在多个地方修改相同的代码。
  • 假设我们不需要更改现有应用程序。我将有五个新应用程序,它们将使用基本相同的用户类,其数据源现在是活动目录,以后可能会切换到数据库。
  • 更改请求仅适用于 一些 应用程序,具体取决于此通用组件,可能会咬到你,如果真的发生了,你可能会输比你得到的任何好处都多。处理更少的依赖项通常比处理更多的依赖项更容易,而且这种考虑通常比对类似代码的多个副本进行重复修复更重要。
【解决方案3】:

经过一番思考,我发现这个解决方案可能很好。

ActiveDirectoryUserRepository 一起,我可以创建一个继承自User 的内部类ProxyUser,它引用ActiveDirectoryUserRepositoryProxyUser 应该在第一次调用时返回 Groups 属性。


// User.cs
public class User
{
    public virtual string Name { get; set; }
    public virtual string DisplayName {get; set;}
    public virtual string Email { get; set; }

    public virtual IList<Group> Groups { get; set; }
}

// Group.cs
public class Group
{
    public virtual string Name { get; set; }
    public virtual string Email { get; set; }

    public virtual IList<User> Members { get; set; }
}

//  ProxyUser.cs. It should be in the same assembly as ActiveDirectoryUserRepository 

internal class ProxyUser : User
{

   private ActiveDirectoryUserRepository _repository ;
   internal ProxyUser(ActiveDirectoryUserRepository repository)
   {
       _repository = repository;
   }

   internal string IList<string> GroupNames { get; set; }

   private IList<Group> _groups;
   public override IList<Group> Groups 
   { 
      get
      {
         if (_groups == null)
         {
            if (GroupNames != null && GroupNames.Count > 0)
            {
                _groups = new List<Group>();
                foreach(string groupName in GroupNames)
                   _groups.Add(_repository.FindGroupByName(groupName);
            }
         }
         return _groups;
       }

       set
       {
           _groups = value;
       }
    }
}

// ProxyGroup.cs

internal class ProxyGroup : Group
{
    // This class should be similar to ProxyUser
}

// ActiveDirectoryUserRepository.cs

public class ActiveDirectoryUserRepository
{
    public User FindUserByName(string name)
    {
         DirectorySearcher searcher = new DirectorySearcher();

         // ...
         // set up the filter and properties of the searcher
         // ...

         Result result = searcher.FindOne();
         User user = new ProxyUser(this)
         {
             Name = result.Properties["displayName"][0],
             Email = result.Properties["email"][0],
             GroupNames = new List<string>()
         };

         foreach(string groupName in result.Properties["memberOf"])
         {
             user.GroupNames.Add(groupName);
         }

         return user;
    }

    public Group FindGroupByName(string name)
    {
        // Find the Group, which should be similar to FindUserByName
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-27
    • 2021-02-05
    • 2011-08-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多