如何打破存储库之间的循环依赖关系

Mic*_*thy 9 .net c# dependency-injection circular-dependency repository-pattern

首先,不是我没有使用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; }
}
Run Code Online (Sandbox Code Playgroud)

注意彼此的引用,公司有一个用户列表,每个用户只有一个公司.

现在我想设计我的存储库:

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);
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.我想从我的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;
    }

}
Run Code Online (Sandbox Code Playgroud)

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

现在问题.

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

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

    }
}
Run Code Online (Sandbox Code Playgroud)

但现在我们遇到了问题.FirmRepository依赖于IUserRepository的实例,而UserRepository现在依赖于IFirmRepository的实例.因此,如果没有另一个实例,则无法创建一个存储库.

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

没问题,我只是创建一个FirmProxy来懒惰加载来自Firm的Users集合!这是一个更好的主意,b/c我不想在获得公司或公司列表时一直加载所有用户.

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;
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

所以,现在我有一个很好的代理对象来促进延迟加载.因此,当我从持久性在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;
}
Run Code Online (Sandbox Code Playgroud)

但这仍然无法正常工作!

为了返回FirmProxy而不是Firm,我需要能够在FirmRepository类中新建一个FirmProxy.好吧,FirmProxy采用IUserRepository实例b/c,UserRepository包含如何从持久性创建User对象的知识.由于FirmProxy的结果需要一个IUserRepository,我FirmRepository现在需要一个IUserRepository为好,我就回到了起点!

因此,鉴于这种长篇大论,说明/源代码,我怎么能去能创建一个从FirmRepository用户与企业的距离UserRepository没有一个实例的实例:

  1. 将用户创建代码放在FirmRepository中.我不喜欢这个.为什么FirmRepository应该知道有关创建用户实例的任何信息?这对我来说违反了SoC.
  2. 不使用服务定位器模式.如果我走这条路,我觉得这很难测试.此外,采用显式依赖关系的对象的构造函数使这些依赖关系变得明显.
  3. 属性注入而不是构造函数注入.不能解决的事,我不管new'ing了FirmProxy的依赖性是如何注入FirmProxy时仍然需要IUserRepository的一个实例.
  4. 必须"愚弄"公司或用户对象并在用户上公开FirmID,例如而不是公司.如果我只是在做id,那么从UserRepository加载一个Firm的要求就会消失,但随之而来的是能够向Firm对象询问任何具有给定User实例的上下文的内容.
  5. 求助于ORM.我想再做一次,但我做不到.没有ORM.这是规则(是的,这是一个糟糕的规则)
  6. 将所有可注入的依赖项保持为从应用程序的最低级别注入的依赖项,即UI(在我的示例中为.NET Web项目).没有作弊并在FirmProxy中使用IoC代码来为我创建适当的依赖项.无论如何,这基本上都是使用服务定位器模式.

我想一下NHiberante和Enitity Framework,看起来他们没有问题找出如何为我提出的一个简单例子生成sql.

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

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

Laz*_*rus 5

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

您遇到的困难是向您显示您的存储库结构不正确.任何时候我发现我的代码变得太难了,它会向我发出警告,我可能做错了,我过着自己的生活;)