c# .net dependency-injection repository-pattern circular-dependency

c# - Cómo romper las dependencias circulares entre repositorios



.net dependency-injection (1)

Para empezar, no, no estoy usando un ORM, ni puedo hacerlo. Tengo que pasar manualmente mis repositorios usando ADO.NET.

Tengo dos objetos:

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; } }

observe las referencias entre sí, una Firma tiene una lista de Usuarios, cada Usuario tiene una sola Firma.

Ahora quiero diseñar mis repositorios:

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); }

Hasta aquí todo bien. Quiero cargar la Firma para cada Usuario de mi UserRepository. El FirmRepository sabe cómo crear una Firma a partir de la persistencia, así que me gustaría mantener ese conocimiento con el 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; } }

ahora cuando voy a cargar cualquier Usuario a través del UserRepository, puedo pedirle al FirmRepository que construya la Firma del Usuario para mí. Hasta ahora, nada demasiado loco aquí.

Ahora el problema.

Quiero cargar una lista de usuarios de mi FirmRepository. UserRepository sabe cómo crear un usuario desde la persistencia, por lo que me gustaría mantener ese conocimiento con UserRepository. Entonces, paso una referencia a IUserRepository al FirmRepository:

public class FirmRepository : IFirmRepository { private IUserRepository public FirmRepository(IUserRepository userRepository) { } }

Pero ahora tenemos un problema. El FirmRepository depende de una instancia de IUserRepository, y el UserRepository ahora depende de una instancia de IFirmRepository. Entonces, un Repositorio no se puede crear sin una instancia del otro.

Si mantengo los contenedores IoC FUERA de esta ecuación (y debería, b / c Pruebas unitarias no deberían usar contenedores IoC), no hay forma de que logre lo que estoy tratando de hacer.

No hay problema, creo que crearé un FirmProxy para cargar la colección de usuarios de la empresa. Esa es una mejor idea, b / c. No quiero cargar TODOS los Usuarios todo el tiempo cuando voy a obtener una Firma o una lista de Firmas.

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; } } }

Entonces, ahora tengo un buen proxy para facilitar la carga lenta. Entonces, cuando voy a crear una Firma en FirmRepository por persistencia, en cambio, devuelvo 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; }

¡Pero esto todavía no funciona!

Para devolver un FirmProxy en lugar de una Firma, necesito poder actualizar un FirmProxy en mi clase FirmRepository. Bueno, el FirmProxy toma una instancia de IUserRepository b / c el UserRepository contiene el conocimiento de cómo crear un objeto de usuario desde la persistencia. Como resultado de la FirmProxy que necesita un IUserRepository, mi FirmRepository ahora también necesita un IUserRepository, ¡y ahora vuelvo al primer puesto!

Entonces, dado este largo código explicativo / fuente, ¿cómo puedo crear una instancia de un Usuario del FirmRepository y una instancia de Firmware del UserRepository sin:

  1. poner el código de creación del usuario en el FirmRepository. No me gusta esto ¿Por qué el FirmRepository debe saber algo sobre la creación de una instancia de un usuario? Esto para mí es una violación de SoC.
  2. No usa el patrón Localizador de servicios. Si tomo esta ruta, creo que esto es muy difícil de probar. Además, los constructores de objetos que toman dependencias explícitas hacen que esas dependencias sean obvias.
  3. inyección de propiedad en lugar de inyección de constructor. Esto no soluciona nada, todavía necesito una instancia de IUserRepository al actualizar una FirmProxy sin importar cómo se inyecta la dependencia en FirmProxy.
  4. Tener que "atontar" a la Firma o al objeto Usuario y exponer un FirmID en el Usuario, por ejemplo, en lugar de una Firma. Si solo estoy haciendo id''s, entonces el requisito de cargar una Firma del UserRepository desaparece, pero con esto va la riqueza de poder pedirle al objeto Firm algo relacionado con el contexto de una instancia de Usuario dada.
  5. Recurriendo a un ORM. De nuevo, quiero hacerlo, pero no puedo. Sin ORM''s Esa es la regla (y sí, es una regla horrible)
  6. mantener todas mis dependencias de inyección como dependencias inyectadas desde el nivel más bajo de la aplicación, que es la interfaz de usuario (en mi caso, un proyecto web .NET). No hacer trampas y usar el código IoC en FirmProxy para actualizar la dependencia adecuada para mí. Eso es básicamente utilizar el patrón Localizador de servicios de todos modos.

Pienso en NHiberante y Enitity Framework, y parece que no tienen problema en descubrir cómo generar SQL para un ejemplo simple que he presentado.

¿Alguien más tiene alguna otra idea / método / etc ... que me ayude a lograr lo que quiero hacer sin un ORM?

¿O tal vez hay una forma diferente / mejor de abordar esto? Lo que no quiero perder es la capacidad de acceder a una empresa desde un usuario, o de obtener una lista de usuarios para una empresa determinada.


Necesita pensar más claramente sobre sus objetos raíz agregados, es decir, el foco de cada repositorio. No crea un repositorio para cada tabla en su base de datos, ese no es el objetivo. La intención es identificar el objeto raíz agregado con el que necesita trabajar, incluir las tablas / registros secundarios, es decir, Usuario (Empresa, Dirección, Derechos de acceso). Eso es lo que su repositorio volverá a su aplicación.

La dificultad que tiene es mostrarle que sus repositorios no están estructurados correctamente. Cada vez que encuentro que mi código se vuelve demasiado difícil, despierta una alerta conmigo de que probablemente lo estoy haciendo mal, vivo mi vida con eso;)