Начнем с того, что нет, я не использую 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 знает, как создать фирму из постоянства, поэтому я хотел бы сохранить эти знания в 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 из этого уравнения (а я должен это сделать, потому что модульные тесты не должны использовать контейнеры IoC), у меня не будет возможности выполнить то, что я пытаюсь сделать.
Нет проблем, я просто создам FirmProxy для ленивой загрузки коллекции Users из Firm! Это лучшая идея, потому что я не хочу загружать ВСЕХ пользователей все время, когда я иду, чтобы получить Фирму или список Фирм.
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 из постоянства, я вместо этого возвращаю 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, мне нужно иметь возможность создать FirmProxy в моем классе FirmRepository.Что ж, FirmProxy принимает экземпляр IUserRepository, потому что UserRepository содержит информацию о том, как создать объект User из постоянства. В результате того, что FirmProxy нуждается в IUserRepository, моему FirmRepository теперь также нужен IUserRepository, и я вернулся к исходной точке!
Итак, учитывая это длинное объяснение/исходный код, как я могу создать экземпляр пользователя из FirmRepository и экземпляр фирмы из UserRepository без:
Я думаю о NHiberante и Enitity Framework, и кажется, что у них не возникло проблем с тем, как сгенерировать sql для простого примера, который я представил.
Есть ли у кого-нибудь другие идеи/методы/и т. д., которые помогут мне добиться того, чего я хочу, без ORM?
Или, может быть, есть другой/лучший способ приблизиться к этому? Хочу, чтобы я не хотел потерять возможность доступа к Фирме от Пользователя или получения списка Пользователей для данной Фирмы