c# - 如何打破存储库之间的循环依赖

标签 c# .net dependency-injection repository-pattern circular-dependency

首先,不,我没有使用 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 知道如何从持久性中创建一个 Firm,所以我希望将这些知识保留在 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 知道如何从持久性创建用户,所以我想通过 UserRepository 保留这些知识。因此,我将 IUserRepository 的引用传递给 FirmRepository:
public class FirmRepository : IFirmRepository
{
    private IUserRepository
    public FirmRepository(IUserRepository userRepository)
    {

    }
}

但是现在我们遇到了一个问题。 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;
        }
    }

}

所以,现在我有一个很好的代理对象来促进延迟加载。因此,当我从持久性中在 FirmRepository 中创建一个 Firm 时,我会返回一个 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 而不是 Firm,我需要能够在我的 FirmRepository 类中新建一个 FirmProxy。好吧,FirmProxy 采用 IUserRepository 实例 b/c UserRepository 包含如何从持久性创建 User 对象的知识。由于 FirmProxy 需要一个 IUserRepository,我的 FirmRepository 现在也需要一个 IUserRepository,我又回到了原点!

因此,鉴于这个冗长的解释/源代码,我如何才能从 FirmRepository 创建一个 User 实例和一个来自 UserRepository 的 Firm 实例,而无需:
  • 将用户创建代码放在 FirmRepository 中。我不喜欢这个。为什么 FirmRepository 应该知道有关创建用户实例的任何信息?这对我来说是违反 SoC 的。
  • 不使用服务定位器模式。如果我走这条路,我觉得这是出了名的难以测试。此外,采用显式依赖关系的对象的构造函数使这些依赖关系显而易见。
  • 属性注入(inject)而不是构造函数注入(inject)。这并不能解决问题,无论依赖项如何注入(inject) FirmProxy,在新建 FirmProxy 时我仍然需要一个 IUserRepository 实例。
  • 必须“降低”公司或用户对象并在用户上公开 FirmID,例如,而不是公司。如果我只是在做 id,那么从 UserRepository 加载 Firm 的要求就会消失,但随之而来的是能够在给定 User 实例的上下文中向 Firm 对象询问任何内容的丰富性。
  • 求助于 ORM。再说一次,我想做,但我做不到。没有 ORM。这是规则(是的,这是一条糟糕的规则)
  • 将所有可注入(inject)的依赖项保留为从应用程序的最低级别注入(inject)的依赖项,即 UI(在我的情况下,是一个 .NET Web 项目)。没有作弊并在 FirmProxy 中使用 IoC 代码来为我新建适当的依赖项。无论如何,这基本上是使用服务定位器模式。

  • 我想到了 NHiberante 和 Enitity Framework,他们似乎可以为我提供的一个简单示例找出如何生成 sql 没有问题。

    有没有其他人有任何其他想法/方法/等...可以帮助我在没有 ORM 的情况下实现我想做的事情?

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

    最佳答案

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

    您遇到的困难是向您表明您的存储库结构不正确。每当我发现我的代码变得太难时,它就会提醒我我可能做错了,我就是这样过日子的 ;)

    关于c# - 如何打破存储库之间的循环依赖,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9637465/

    相关文章:

    c# - add-in for visual studio 2010编译报错Connect类

    c# - 如何在 XNA 中使用 Ninject?

    c# - asp.net mvc 返回视频的 Action 结果

    c# - 如何在 C# 中获取从一条路径到另一条路径的相对路径

    .net - Powershell不显示XAML对话框

    java - Guice:如何避免模块冗余

    java - 从 Dagger 2 开始 - 类不注入(inject)

    c# - 如何对某些 StyleCop 规则进行异常(exception)处理?

    c# - 捕获通用的非致命异常

    C# - 强制 String.Format 使用小数,从不使用逗号