首先,不,我不使用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);
}
到目前为止,还不错。我想从我的用户库中为每个用户加载公司。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知道如何从持久性中创建用户,所以我想将这些知识保留在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;
}
}
}
所以,现在我有了一个很好的代理对象来帮助延迟加载。所以,当我从persistence创建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包含如何从持久性创建用户对象的知识。由于firmproxy需要一个iuserrepository,我的firmrepository现在也需要一个iuserrepository,我马上回到原点!
因此,考虑到这个冗长的解释/源代码,我如何能够从firmrepository创建一个用户实例,从userrepository创建一个firm实例,而不需要:
将用户创建代码放入firmrepository中。我不喜欢这样。为什么firmrepository应该知道有关创建用户实例的任何信息?这对我来说是违反社会责任。
不使用服务定位器模式。如果我走这条路,我觉得这很难测试。此外,具有显式依赖关系的对象的构造函数使这些依赖关系显而易见。
属性注入而不是构造函数注入。这并不能解决问题,我仍然需要一个iuserrepository实例,当我新建firmproxy时,不管依赖项如何注入firmproxy。
必须“哑”公司或用户对象,并向用户公开公司ID,例如,而不是公司。如果我只是做id,那么从userrepository加载一个公司的需求就消失了,但是随之而来的是能够在给定用户实例的上下文中向公司对象请求任何东西的丰富性。
求助于orm。再说一次,我想这么做,但我做不到。不是orm。这是规则(是的,这是个糟糕的规则)
将所有可插入的依赖项保留为从应用程序的最低级别(即ui)注入的依赖项(在我的例子中是一个.net web项目)。在firmproxy中没有欺骗和使用ioc代码来为我创建适当的依赖项。这基本上就是使用服务定位器模式。
我想到了nhiberante和enitity框架,他们似乎很容易找到如何为我介绍的一个简单示例生成sql的方法。
其他人还有其他想法/方法等吗…这将帮助我在没有orm的情况下实现我想做的事情?
或者也许有一种不同的/更好的方法来解决这个问题?我不想失去的是从一个用户那里访问一个公司,或者获取一个给定公司的用户列表的能力
最佳答案
您需要更清楚地考虑聚合根对象,即每个存储库的焦点。您不会为数据库中的每个表创建存储库,这不是目标。其目的是标识需要使用的聚合根对象,包括子表/记录,即用户(公司、地址、访问权限)。这就是存储库将返回到应用程序的内容。
您遇到的困难是向您显示存储库的结构不正确。每当我发现我的代码变得太难时,它就会提醒我,我可能做错了,我就靠它生活;)