9

Para empezar, no, no estoy usando un ORM, ni estoy autorizado. Tengo que pasar manualmente mis repositorios usando ADO.NET.Cómo romper las dependencias circulares entre repositorios

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

nota las referencias a la otra, una empresa tiene una lista de usuarios, cada usuario tiene una sola empresa.

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 ahora, 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 de la UserRepository, puedo pedir al FirmRepository para construir la empresa 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. Por lo tanto, pasa una referencia a IUserRepository a la 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 hacerlo, b/c Pruebas unitarias no deberían usar contenedores IoC), no hay manera de que yo logre lo que estoy tratando de hacer.

No hay problema, creo, simplemente 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 objeto proxy para facilitar la carga diferida. 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!

Por lo tanto, dado este código explicación de largo aliento/fuente, ¿cómo puedo ir sobre la capacidad de crear una instancia de un usuario de la FirmRepository y una instancia de la firma de la UserRepository sin:

  1. put 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 se 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 inyectables como dependencias inyectadas desde el nivel más bajo de la aplicación, que es la IU (en mi caso, un proyecto web .NET). No hacer trampa y usar el código IoC en FirmProxy para volver a crear 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 ningún problema en averiguar 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? Quiero que no quiera perder la capacidad de acceder a una Firma desde un Usuario, o para obtener una lista de Usuarios para una Firma dada

+0

Ha votado para cambiar a http://programmers.stackexchange.com/ – MattDavey

+0

@ indiecodemonkey ¿Has encontrado una solución? –

Respuesta

5

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, genera una alerta conmigo que probablemente lo estoy haciendo mal. Vivo mi vida con eso;)

+1

+1 - en este caso, parece que tanto la Firma como el Usuario son raíces agregadas. En estos escenarios, generalmente tengo una referencia suave entre las dos raíces (a través de una clave de referencia) y si necesito una estructura de datos que los una juntos, lo hago explícitamente (podría ser un objeto de consulta o un modelo de vista, etc.). – MattDavey

+0

Veo que la Firma es la raíz Agregado, y los Usuarios caen bajo esa raíz. Pero, al mismo tiempo, no veo que la Firma sea una raíz agregada evidenciada por lo que podría querer trabajar con un Usuario, y no molestar en absoluto con la Firma del Usuario. Sin embargo, cuando voy a persistir con el usuario de nuevo a la base de datos, en el ejemplo de raíz de agg, tendría que persistir toda la empresa y todos sus usuarios. Es interesante lo que MattDavey dice sobre ellos, tanto como raíces. Él me lleva de vuelta a las referencias suaves, que me gustaría poder evitar, me gusta usar referencias de objetos de cada entidad. –

+1

La forma en que generalmente pienso sobre las raíces agregadas es por la regla de 'eliminaciones en cascada'. Si elimina una raíz agregada, todo lo que se encuentra debajo debería eliminarse también. Si elimina una empresa, ¿deberían eliminarse también todos los usuarios? – MattDavey

Cuestiones relacionadas