【发布时间】:2012-03-09 16:23:04
【问题描述】:
首先,不,我没有使用 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 的用户集合!这是一个更好的主意,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 包含如何从持久性创建用户对象的知识。由于 FirmProxy 需要一个 IUserRepository,我的 FirmRepository 现在也需要一个 IUserRepository,我马上回到原点!
因此,鉴于这个冗长的解释/源代码,我如何才能在没有以下情况的情况下从 FirmRepository 创建 User 实例和从 UserRepository 创建 Firm 实例:
- 将用户创建代码放入 FirmRepository。我不喜欢这个。为什么 FirmRepository 应该知道有关创建用户实例的任何信息?对我来说,这违反了 SoC。
- 不使用服务定位器模式。如果我走这条路,我觉得这是出了名的难以测试。此外,具有显式依赖关系的对象的构造函数使这些依赖关系显而易见。
- 属性注入而不是构造函数注入。这并不能解决问题,我仍然需要一个 IUserRepository 实例来新建一个 FirmProxy,无论依赖项如何注入到 FirmProxy 中。
- 必须“简化”公司或用户对象,并在用户上公开一个公司 ID,例如,而不是公司。如果我只是在做 id,那么从 UserRepository 加载 Firm 的要求就消失了,但随之而来的是能够在给定 User 实例的上下文中向 Firm 对象询问任何内容的丰富性。
- 求助于 ORM。再一次,我想这样做,但我做不到。没有ORM。这就是规则(是的,这是一条糟糕的规则)
- 将我所有的可注入依赖项保留为从应用程序的最低级别注入的依赖项,即 UI(在我的例子中,是一个 .NET Web 项目)。没有作弊,并在 FirmProxy 中使用 IoC 代码为我新建适当的依赖项。无论如何,这基本上是使用服务定位器模式。
我想到了 NHiberante 和 Enitity Framework,对于我提供的一个简单示例,他们似乎没有问题弄清楚如何生成 sql。
有没有其他人有任何其他想法/方法/等...可以帮助我在没有 ORM 的情况下实现我想要做的事情?
或者也许有不同/更好的方法来解决这个问题?我不想失去的是从用户访问公司的能力,或者获取给定公司的用户列表
【问题讨论】:
-
@indiecodemonkey 你找到解决办法了吗?
标签: c# .net dependency-injection repository-pattern circular-dependency