9

首先,我不使用ORM,也不允许使用ORM。我必须使用ADO.NET手动推送我的存储库。如何打破版本库之间的循环依赖关系

我有两个对象:

public class Firm 
{ 
    public Guid Id { get; set; } 
    public string Name { get; set; } 
    public virtual IEnumerable<User> Users { get; set; } 
} 

public class User 
{ 
    public Guid Id { get; set; } 
    public string Username { get; set; } 
    public Firm Firm { get; set; } 
} 

注意引用对方,一个企业拥有用户的列表,每个用户只有一个事务所。

现在我要设计我的资料库:

public interface IFirmRepository 
{ 
    IEnumerable<Firm> FindAll(); 
    Firm FindById(Guid id); 
} 

public interface IUserRepository 
{ 
    IEnumerable<User> FindAll(); 
    IEnumerable<User> FindByFirmId(Guid firmId); 
    User FindById(Guid id); 
} 

到目前为止,一切都很好。我想从UserRepository中为每个用户加载公司。 FirmRepository知道如何从持久性创建一个公司,所以我不想用FirmRepository保留这些知识。

public class UserRepository : IUserRepository 
{ 
    private IFirmRepository _firmRepository; 

    public UserRepository(IFirmRepository firmRepository) 
    { 
     _firmRepository = firmRepository; 
    } 

    public User FindById(Guid id) 
    { 
     User user = null; 
     using (SqlConnection connection = new SqlConnection(_connectionString)) 
     { 
      SqlCommand command = connection.CreateCommand(); 
      command.CommandType = CommandType.Text; 
      command.CommandText = "select id, username, firm_id from users where u.id = @ID"; 
      SqlParameter userIDParam = new SqlParameter("@ID", id); 
      command.Parameters.Add(userIDParam); 
      connection.Open(); 
      using (SqlDataReader reader = command.ExecuteReader()) 
      { 
       if (reader.HasRows) 
       { 
        user = CreateListOfUsersFrom(reader)[0]; 
       } 
      } 
     } 
     return user; 
    } 

    private IList<User> CreateListOfUsersFrom(SqlDataReader dr) 
    { 
     IList<User> users = new List<User>(); 
     while (dr.Read()) 
     { 
      User user = new User(); 
      user.Id = (Guid)dr["id"]; 
      user.Username = (string)dr["username"]; 
      //use the injected FirmRepository to create the Firm for each instance of a User being created 
      user.Firm = _firmRepository.FindById((Guid)dr["firm_id"]); 
     } 
     dr.Close(); 
     return users; 
    } 

} 

现在,当我通过UserRepository加载任何用户时,我可以要求FirmRepository为我建立用户公司。到目前为止,这里没什么太疯狂的。

现在的问题。

我想从我的FirmRepository中加载用户列表。 UserRepository知道如何从持久性创建User,所以我想用UserRepository保留这些知识。因此,我将一个对IUserRepository的引用传递给FirmRepository:

public class FirmRepository : IFirmRepository 
{ 
    private IUserRepository 
    public FirmRepository(IUserRepository userRepository) 
    { 

    } 
} 

但是现在我们遇到了问题。 FirmRepository依赖于IUserRepository的实例,UserRepository现在依赖于IFirmRepository的一个实例。所以一个Repository不能在没有其他实例的情况下创建。

如果我保持IoC容器没有这个等式(我应该,b/c单元测试不应该使用IoC容器),我无法完成我想要做的事情。

没问题,但是,我只是创建一个FirmProxy来延迟从公司加载用户集合!这是一个更好的主意,我不希望所有的用户在我去找公司或公司名单时加载所有的用户。

public class FirmProxy : Firm 
{ 
    private IUserRepository _userRepository; 
    private bool _haveLoadedUsers = false; 
    private IEnumerable<User> _users = new List<User>(); 

    public FirmProxy(IUserRepository userRepository) 
     : base() 
    { 
     _userRepository = userRepository; 
    } 

    public bool HaveLoadedUser() 
    { 
     return _haveLoadedUsers; 
    } 

    public override IEnumerable<User> Users 
    { 
     get 
     { 
      if (!HaveLoadedUser()) 
      { 
       _users = _userRepository.FindByFirmId(base.Id); 
       _haveLoadedUsers = true; 
      } 
      return _users; 
     } 
    } 

} 

所以,现在我有一个很好的代理对象来促进延迟加载。所以,当我从持久性创建FirmRepository中的公司时,我会返回一个FirmProxy。

public class FirmRepository : IFirmRepository 
{ 

    public Firm FindById(Guid id) 
    { 
     Firm firm = null; 
     using (SqlConnection connection = new SqlConnection(_connectionString)) 
     { 
      SqlCommand command = connection.CreateCommand(); 
      command.CommandType = CommandType.Text; 
      command.CommandText = "select id, name from firm where id = @ID"; 
      SqlParameter firmIDParam = new SqlParameter("@ID", id); 
      command.Parameters.Add(firmIDParam); 
      connection.Open(); 
      using (SqlDataReader reader = command.ExecuteReader()) 
      { 
       if (reader.HasRows) 
       { 
        firm = CreateListOfFirmsFrom(reader)[0]; 
       } 
      } 
     } 
     return firm; 
    } 

private IList<Firm> CreateListOfFirmsFrom(SqlDataReader dr) 
{ 
    IList<FirmProxy> firms = new List<FirmProxy>([need an IUserRepository instance here!!!!]); 
    while (dr.Read()) 
    { 

    } 
    dr.Close(); 
    return firms; 
} 

但是,这仍然是行不通的!

为了返回一个FirmProxy而不是一个公司,我需要能够在我的FirmRepository类中新建一个FirmProxy。那么,FirmProxy需要一个IUserRepository实例b/c UserRepository包含了如何从持久性创建一个User对象的知识。由于FirmProxy需要一个IUserRepository,我的FirmRepository现在也需要一个IUserRepository,我马上就回到原点!

因此,鉴于此长篇大论,说明/源代码,我怎么能去能创建一个从FirmRepository用户的实例,并从UserRepository没有事务所的一个实例:

  1. 放FirmRepository中的用户创建代码。我不喜欢这个。为什么FirmRepository知道关于创建用户实例的任何信息?这对我来说是违反SoC的。
  2. 不使用服务定位器模式。如果我走这条路,我觉得这是非常难以测试的。此外,具有显式依赖性的对象的构造函数使这些依赖性显而易见。
  3. 属性注入而不是构造函数注入。这并不能解决问题,无论在FirmProxy中如何注入依赖关系,在创建FirmProxy时仍需要IUserRepository的一个实例。
  4. 必须“贬低”公司或用户对象并在用户身上公开FirmID,例如,而不是公司。如果我只是在做id,那么从UserRepository中加载公司的要求就会消失,但随着它能够向Firm对象请求任何具有给定User实例上下文的内容的丰富性。
  5. 使用ORM。再次,我想要这样做,但我做不到。没有ORM的。这是规则(是的,这是一个糟糕的规则)
  6. 保留所有我的注入能力的依赖关系注入从最低级别的应用程序,这是UI(在我的情况下,一个.NET Web项目)注入的依赖项。在FirmProxy中没有作弊和使用IoC代码来为我提供适当的依赖关系。无论如何,这基本上是使用服务定位器模式。

我想到NHiberante和Enitity Framework,看来他们没有问题想出如何生成一个简单的例子,我已经提出了SQL。

其他任何人有任何其他的想法/方法/等......这将帮助我实现我想要做的事情没有ORM?

或者也许有不同的/更好的方法来解决这个问题?希望我不想失去的是能够从用户访问公司,或获得给定公司的用户列表

+0

投票移动到http://programmers.stackexchange.com/ – MattDavey 2012-03-09 16:35:21

+0

@ indiecodemonkey你找到了解决方案吗? – 2015-07-13 09:45:38

回答

5

您需要更清楚地了解您的聚合根对象,即焦点每个存储库。您不会为数据库中的每个表创建存储库,但这不是目标。其目的是识别需要使用的聚合根对象,包括子表/记录,即用户(公司,地址,访问权限)。这就是您的存储库将返回到您的应用程序。

您遇到的困难是向您显示您的存储库结构不正确。任何时候我发现我的代码变得太难了,它会引起我的警觉,说我可能做错了,我以此为生;)

+1

+1 - 在这种情况下,公司和用户都是聚合根。在这些场景中,我通常在两个根之间有一个软引用(通过引用键),如果我需要一个将它们连接在一起的数据结构,我会明确地做它(可以是查询对象或视图模型等)。 – MattDavey 2012-03-09 16:33:05

+0

我看到公司是聚合根,并且该用户属于该根。但与此同时,我并不认为公司是一个可以证明我可以与用户合作,并且完全不与用户公司打交道的证据。然而,当我坚持用户回到数据库,在agg根的例子中,我必须坚持所有的企业,所有的用户。 MattDavey说他们都是根源,这很有趣。他把我引回到软引用,我希望我可以远离,我喜欢使用来自每个实体的对象引用。 – 2012-03-09 16:58:38

+1

我通常关于聚合根的方式是通过'级联删除'的规则。如果你删除了一个聚合根,那么下面的所有东西都应该被删除。如果你删除一家公司,是否所有的用户都被删除? – MattDavey 2012-03-09 17:42:45