To begin with, no I'm not using an ORM, nor am I allowed to. I have to hand-roll my repositories using ADO.NET.
I have two objects:
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; }
}
note the references to each other, a Firm has a list of Users, each User has only one Firm.
Now I want to design my repositories:
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);
}
So far, so good. I want to load the Firm for each User from my UserRepository. The FirmRepository knows how to create a Firm from persistence, so I'd like ot keep that knowledge with the 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;
}
}
now when I go to load any User via the UserRepository, I can ask the FirmRepository to build the User's Firm for me. So far, nothing too crazy here.
Now the problem.
I want to load a list of Users from my FirmRepository. The UserRepository knows how to create a User from persistence, so I'd like to keep that knowledge with the UserRepository. So, I pass in a reference to IUserRepository to the FirmRepository:
public class FirmRepository : IFirmRepository
{
private IUserRepository
public FirmRepository(IUserRepository userRepository)
{
}
}
But now we have a problem. The FirmRepository depends on an instance of IUserRepository, and the UserRepository now depends on an instance of IFirmRepository. So one Repository cannot be created without an instance of the other.
If I keep IoC containers OUT of this equation (and I should, b/c Unit Tests should not use IoC containers), there is no way for me to accomplish what I'm trying to do.
No problem, though, I'll just create a FirmProxy to lazy-load the Users collection from Firm! That's a better idea, b/c I don't want to load ALL the Users all the time when I go to get a Firm or a list of Firms.
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;
}
}
}
So, now I have a nice proxy object to facilitate lazy-loading. So, when I go to create a Firm in the FirmRepository from persistence, I instead return a 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;
}
But this is still not working!!!
In order to return a FirmProxy instead of a Firm, I need to be able to new up a FirmProxy in my FirmRepository class. Well, the FirmProxy takes an IUserRepository instance b/c the UserRepository contains the knowledge of how to create a User object from persistence. As a result of the FirmProxy needing an IUserRepository, my FirmRepository now needs an IUserRepository as well, and I'm right back to square one!
So, given this long winded-explanation/source code, how can I go about being able to create an instance of a User from the FirmRepository and an instance of Firm from the UserRepository without:
- put the User creation code in the FirmRepository. I don't like this. Why should the FirmRepository know anything about creating an instance of a User? This to me is a violation of SoC.
- Not using the Service Locator pattern. If I go this route, I feel this is notoriously hard to test. Besides, constructors of objects that take explicit dependencies make those dependencies obvious.
- property injection instead of constructor injection. This doesn't fix a thing, I still need an instance of IUserRepository when new'ing up a FirmProxy no matter how the dependency is injected into FirmProxy.
- Having to "dumb down" either the Firm or the User object and expose a FirmID on User, for example instead of a Firm. If I'm just doing id's, then the requirement to load a Firm from the UserRepository goes away, but with it goes the richness of being able to ask the Firm object for anything withing the context of a given User instance.
- Resorting to an ORM. Again, I want to do it, but I can't. No ORM's. That's the rule (and yes, it's a crappy rule)
- keep all my inject-able dependencies as dependencies injected from the lowest level of the application, which is the UI (in my case, a .NET web project). No cheating and using IoC code in the FirmProxy to new up the appropriate dependency for me. That's basically using the Service Locator pattern anyhow.
I think about NHiberante and Enitity Framework, and it seems they have no problem figuring out how to generate sql for a simple example that I've presented.
Does anyone else have any other ideas/methods/etc... that will help me achieve what I want to do without an ORM?
Or maybe there is a different/better way to approach this? Want I don't want to lose is the ability to access a Firm from a User, or to get a list of Users for a given Firm