Для начала, нет, я не использую 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 создать для меня фирму User. Пока что здесь нет ничего сумасшедшего.
Теперь проблема.
Я хочу загрузить список пользователей из моего FirmRepository. UserRepository знает, как создать пользователя из постоянства, поэтому я хотел бы сохранить эти знания в UserRepository. Итак, я передаю ссылку на IUserRepository в FirmRepository:
public class FirmRepository : IFirmRepository
{
private IUserRepository
public FirmRepository(IUserRepository userRepository)
{
}
}
Но теперь у нас проблема. FirmRepository зависит от экземпляра IUserRepository, а UserRepository теперь зависит от экземпляра IFirmRepository. Поэтому один репозиторий нельзя создать без экземпляра другого.
Если я оставлю контейнеры IoC вне этого уравнения (и мне следует, чтобы в модульных тестах B / C не использовались контейнеры IoC), у меня не было бы возможности выполнить то, что я пытаюсь сделать.
Нет проблем, но я просто создам FirmProxy для отложенной загрузки коллекции Users из Фирмы! Это лучшая идея, потому что я не хочу загружать ВСЕХ пользователей все время, когда я иду за Фирмой или списком Фирм.
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 и экземпляр Firm из UserRepository без:
- положить код создания пользователя в FirmRepository. Мне это не нравится Почему FirmRepository должен знать что-либо о создании экземпляра пользователя? Для меня это нарушение SoC.
- Не используется шаблон Service Locator. Если я иду по этому пути, я чувствую, что это очень сложно проверить. Кроме того, конструкторы объектов, которые принимают явные зависимости, делают эти зависимости очевидными.
- внедрение свойства вместо внедрения конструктора. Это ничего не меняет, мне все еще нужен экземпляр IUserRepository при создании нового FirmProxy независимо от того, как зависимость вводится в FirmProxy.
- Необходимость «заглушить» либо объект «Фирма», либо объект «Пользователь» и указать идентификатор «Фирма» для пользователя, например, вместо «Фирма». Если я просто делаю идентификаторы, то требование загрузки Фирмы из UserRepository исчезает, но вместе с этим уходит богатство возможности запрашивать объект Фирмы для чего-либо в контексте данного экземпляра Пользователя.
- Обращение к ОРМ. Я опять хочу это сделать, но не могу. Нет ОРМ. Это правило (и да, это дерьмовое правило)
- сохранить все мои зависимости для инъекций как зависимости, внедряемые из самого нижнего уровня приложения, а именно пользовательского интерфейса (в моем случае, веб-проекта .NET). Нет мошенничества и использования кода IoC в FirmProxy, чтобы создать для меня соответствующую зависимость. Во всяком случае, это в основном использует шаблон Service Locator.
Я думаю о NHiberante и Enitity Framework, и, похоже, у них нет проблем с выяснением того, как создать sql для простого примера, который я представил.
Есть ли у кого-нибудь еще какие-либо идеи / методы /и т.д. ... это поможет мне достичь того, что я хочу сделать без ORM?
Или, может быть, есть другой / лучший способ подойти к этому?Хочу, чтобы я не хотел потерять возможность доступа к Фирме от Пользователя или получить список Пользователей для данной Фирмы