Как разорвать циклические зависимости между репозиториями

Начнем с того, что нет, я не использую 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 без:

  1. поместить код создания пользователя в фирменном репозитории. Мне это не нравится. Почему FirmRepository должен знать что-то о создании экземпляра пользователя? Для меня это нарушение SoC.
  2. Шаблон Service Locator не используется. Если я пойду по этому пути, я чувствую, что это, как известно, трудно проверить. Кроме того, конструкторы объектов, которые принимают явные зависимости, делают эти зависимости очевидными.
  3. внедрение свойства вместо внедрения конструктора. Это ничего не исправляет, мне по-прежнему нужен экземпляр IUserRepository при обновлении FirmProxy, независимо от того, как зависимость внедряется в FirmProxy.
  4. Необходимость «отупить» либо объект «Фирма», либо объект «Пользователь» и предоставить идентификатор «Фирма» для пользователя, например, вместо «Фирма». Если я просто делаю идентификаторы, то требование загрузки фирмы из UserRepository исчезает, но вместе с этим исчезает богатство возможности запрашивать у объекта фирмы что-либо в контексте данного экземпляра пользователя.
  5. Использование ORM. Опять же, я хочу сделать это, но я не могу. Нет ОРМ.Это правило (и да, это дерьмовое правило)
  6. хранить все мои инъекционные зависимости как зависимости, внедряемые с самого нижнего уровня приложения, которым является пользовательский интерфейс (в моем случае, веб-проект .NET). Никакого обмана и использования кода IoC в FirmProxy, чтобы создать для меня подходящую зависимость. В любом случае это в основном использование шаблона Service Locator.

Я думаю о NHiberante и Enitity Framework, и кажется, что у них не возникло проблем с тем, как сгенерировать sql для простого примера, который я представил.

Есть ли у кого-нибудь другие идеи/методы/и т. д., которые помогут мне добиться того, чего я хочу, без ORM?

Или, может быть, есть другой/лучший способ приблизиться к этому? Хочу, чтобы я не хотел потерять возможность доступа к Фирме от Пользователя или получения списка Пользователей для данной Фирмы

9
задан Michael McCarthy 9 March 2012 в 05:23
поделиться