2008-09-24 10 views
11

¿Cuál es la forma correcta de inyectar una dependencia de acceso a datos cuando realizo una carga diferida?¿Cuál es la forma correcta de inyectar una dependencia de acceso a datos para la carga diferida?

Por ejemplo, he la siguiente estructura de clases

class CustomerDao : ICustomerDao 
    public Customer GetById(int id) {...} 

class Transaction { 
    int customer_id; //Transaction always knows this value 
    Customer _customer = null; 
    ICustomerDao _customer_dao; 
    Customer GetCustomer() { 
    if(_customer == null) 
     _customer = _customer_dao.GetById(_customer_id); 
    return _customer 
    } 

¿Cómo puedo obtener la referencia a _customer_dao en el objeto de la transacción? Requirirlo para el constructor parece que realmente no tendría sentido si quiero que la transacción parezca un POCO. ¿Está bien que el objeto Transacción haga referencia directamente a la Inversión del Contenedor de Control? Eso también parece incómodo también.

¿Cómo manejan esto marcos como NHibernate?

Respuesta

1

Normalmente hago la inyección de dependencia en el constructor como ha hecho anteriormente, pero tomo la carga perezosa un paso más allá al actuar solo cuando se llama el "obtener" como el que tengo a continuación. No estoy seguro si este es el enfoque pura que busca, pero sí elimina el "sucio" constructor de DI/carga lenta en el paso 1;)

public class Product 
{ 
    private int mProductID; 
    private Supplier mSupplier; 
    private ISupplierService mSupplierService; 

    public Product() 
    { 
     //if you want your object to remain POCO you can use dual constr 
     //this constr will be for app use, the next will be for testing 
    } 

    public Product(ISupplierService SupplierService) 
    { 
     mSupplierService = SupplierService; 
    } 

    public Supplier Supplier { 
     get { 
      if (mSupplier == null) { 
       if (mSupplierService == null) { 
        mSupplierService = new SupplierService(); 
       } 
       mSupplier = mSupplierService.GetSupplierByProductID(mProductID); 
      } 
      return mSupplier; 
     } 
     set { mSupplier = value; } 
    } 
} 
+0

podría ser más elegante, pero esto es por lo general la implementación tengo tiempo para. También deja su código abierto a alguna biblioteca de nivel superior para manejar la carga diferida por reflexión, si está interesado en ese tipo de cosas. – ojrac

+2

Mismo comentario que el anterior: los servicios de inyección (y especialmente DAO) no son una buena idea. Hace que el objeto no se pueda utilizar sin dependencia y dificulta su comprobación. Se rompe la ignorancia de mala persistencia. – thinkbeforecoding

+0

Personalmente trato de evitar el escenario donde una propiedad ('mSupplier') * podría * cargarse, pero podría no ser así. Eso me tendría preocupado cada vez que utilizo la entidad: ¿alguien lo cargó de manera incompleta, es decir, 'mSupplier' es nulo pero no debería haberlo sido? ¿Necesito verificaciones nulas en 'mSupplier'? ¿Estos controles nulos tienen la posibilidad de desencadenar una carga lenta? (Es cierto que todavía estoy buscando el Santo Grial.) – Timo

1

No estoy terriblemente familiarizado con el término POCO, pero las definiciones que he leído parecen generalmente seguir el espíritu del objeto que es independiente de un marco más amplio.

Dicho esto, no importa cómo lo cortes, si realizas una inyección de dependencia, vas a tener colaboraciones con aquellas clases cuya funcionalidad está inyectada, y algo que depende de los objetos, ya sea un marco de inyección completo o solo una clase de ensamblador.

Me parece extraño inyectar una referencia a un contenedor de IOC en una clase. Prefiero que mis inyecciones que se producen en el constructor con el código de buscar algo como esto:

public interface IDao<T> 
{ 
    public T GetById(int id); 
} 


public interface ICustomerDao : IDao<Customer> 
{ 
} 

public class CustomerDao : ICustomerDao 
{ 
    public Customer GetById(int id) 
    {...} 
} 

public class Transaction<T> where T : class 
{ 

    int _id; //Transaction always knows this value 
    T _dataObject; 
    IDao<T> _dao; 

    public Transaction(IDao<T> myDao, int id) 
    { 
     _id = id; 
     _dao = myDao; 
    } 

    public T Get() 
    { 
     if (_dataObject == null) 
      _dataObject = _dao.GetById(_id); 
     return _dataObject; 
    } 
} 
+0

Entonces, ¿básicamente estás diciendo que debería inyectarlo a través del constructor? Quizás, pero parece incómodo. –

+1

Los servicios de inyección (y especialmente DAO) no son una buena idea. Hace que el objeto no se pueda utilizar sin dependencia y dificulta su comprobación. Se rompe la ignorancia de mala persistencia. – thinkbeforecoding

7

sugiero algo diferente ... utilizar una clase de carga perezosa:

public class Lazy<T> 
{ 
    T value; 
    Func<T> loader; 

    public Lazy(T value) { this.value = value; } 
    public Lazy(Func<T> loader { this.loader = loader; } 

    T Value 
    { 
    get 
    { 
     if (loader != null) 
     { 
     value = loader(); 
     loader = null; 
     } 

     return value; 
    } 

    public static implicit operator T(Lazy<T> lazy) 
    { 
     return lazy.Value; 
    } 

    public static implicit operator Lazy<T>(T value) 
    { 
     return new Lazy<T>(value); 
    } 
} 

Una vez que lo consigue, usted no es necesario inyectar el DAO en que usted se oponga más:

public class Transaction 
{ 
    private static readonly Lazy<Customer> customer; 

    public Transaction(Lazy<Customer> customer) 
    { 
     this.customer = customer; 
    } 

    public Customer Customer 
    { 
     get { return customer; } // implicit cast happen here 
    } 
} 

al crear un objeto transcation que no está unida a la base de datos:

new Transaction(new Customer(..)) // implicite cast 
            //from Customer to Lazy<Customer>.. 

Cuando la regeneración de una transacción de la base de datos en el repositorio:

public Transaction GetTransaction(Guid id) 
{ 
    custmerId = ... // find the customer id 
    return new Transaction(() => dao.GetCustomer(customerId)); 
} 

dos cosas interesantes: - Sus objetos de dominio se pueden usar con o sin acceso a los datos, se convierte en información Area ignorante. El único pequeño giro es permitir pasar una función que da el objeto en lugar del objeto en sí. - La clase Lazy es internamente mutable pero puede usarse como un valor inmutable. La palabra clave readonly conserva su semántica, ya que su contenido no se puede cambiar de forma externa.

Cuando desee que el campo sea de escritura, simplemente elimine la palabra clave readonly. cuando se asigna un nuevo valor, se creará un nuevo Lazy con el nuevo valor debido al lanzamiento implícito.

Editar: blogged sobre él aquí:

http://www.thinkbeforecoding.com/post/2009/02/07/Lazy-load-and-persistence-ignorance

+0

En cualquier caso, siempre es mejor definir los agregados correctamente y evitar la carga diferida ... – thinkbeforecoding

Cuestiones relacionadas