2009-06-04 7 views
12

Es bastante común, especialmente en aplicaciones con un ORM, tener un mapeo bidireccional entre las clases. De esta manera:Mantenimiento de una relación bidireccional entre las clases

public class Product 
{ 
    private List<Price> HistoricPrices { get; private set;} 
} 

public class Price 
{ 
    private Product Product { get; set; } 
} 

¿Existe alguna forma aceptada de mantener esta relación en el código? ¿De modo que cuando agregue un precio a un producto, la propiedad del Producto se configura automáticamente?

Idealmente, estoy buscando una solución fácilmente reutilizable. Parece incorrecto tener que agregar algo a una colección y luego establecer las relaciones opuestas manualmente.

Tenga en cuenta que esta no es una pregunta acerca de cómo modelar productos y precios, se trata de cómo modelar una relación bidireccional. Hay muchas situaciones donde esto es perfectamente razonable.

+0

Quizás se debería elegir un ejemplo diferente, ya que este ejemplo no demuestra la necesidad de tal relación. –

Respuesta

18

En primer lugar, creo que el ejemplo de su presente es confuso - es raro que algo así como a Precio que se modelará como un objeto o que tenga referencia a las entidades que tendrían un precio. Pero creo que la pregunta es legítima: en el mundo de ORM, esto se conoce como consistencia de gráficos. Que yo sepa, no hay uno manera definitiva de abordar este problema, hay varias formas. inicio

Vamos cambiando un poco el ejemplo:

public class Product 
{ 
    private Manufacturer Manufacturer { get; private set;} 
} 

public class Manufacturer 
{ 
    private List<Product> Products { get; set; } 
} 

Así que cada producto tiene un fabricante, y cada fabricante podría tener una lista de productos. El desafío con el modelo es que si la clase Producto y la clase Fabricante mantienen referencias desconectadas entre sí, la actualización de uno puede invalidar el otro.

Hay varias maneras de abordar este problema:

  1. Eliminar la referencia circular. Esto resuelve el problema pero hace que el modelo de objeto sea menos expresivo y más difícil de usar.

  2. Cambie el código para que la referencia del fabricante en la lista de productos y productos en el fabricante sea reflexivo. En otras palabras, cambiar uno afecta al otro. En general, esto requiere algún código para que el colocador y la colección intercepten los cambios y los reflejen en uno.

  3. Gestione una propiedad en términos de la otra.Por lo tanto, en lugar de almacenar una referencia a un fabricante dentro del Producto, lo calcula buscando entre todos los Fabricantes hasta que encuentre el que le pertenece. Por el contrario, puede mantener una referencia al Fabricante en la clase de Producto y crear la lista de Productos dinámicamente. En este enfoque, generalmente harías que un lado de la relación sea de solo lectura. Por cierto, este es el enfoque estándar de la base de datos relacional: las entidades se refieren entre sí a través de una clave externa que se gestiona en un solo lugar.

  4. Externalice la relación desde ambas clases y adminístrelo en un objeto separado (a menudo llamado contexto de datos en ORM). Cuando Product quiere devolver su fabricante, le pide a DataContext. Cuando el fabricante desea devolver una lista de productos, hace lo mismo. Internamente, hay muchas maneras de implementar un contexto de datos, un conjunto de diccionarios bidireccionales no es infrecuente.

Por último, mencionaré, que usted debe considerar el uso de una herramienta ORM (como NHibernate o CSLA) que pueden ayudarle a gestionar la coherencia gráfica. En general, este no es un problema fácil de resolver correctamente, y puede complicarse fácilmente una vez que comienzas a explorar casos como relaciones de muchos a muchos, relaciones uno a uno y cargas de objetos perezosos. Es mejor utilizar una biblioteca o producto existente, en lugar de inventar un mecanismo propio.

Estos son algunos links que hablan de bidirectional associations en NHibernate que puede que sean útiles.

Aquí hay un ejemplo de código de administración de las relaciones directamente con el método # 3, que suele ser el más simple. Tenga en cuenta que solo un lado de la relación es editable (en este caso, el fabricante): los consumidores externos no pueden establecer directamente el fabricante de un producto.

public class Product 
{ 
    private Manufacturer m_manufacturer; 

    private Manufacturer Manufacturer 
    { 
     get { return m_manufacturer;} 
     internal set { m_manufacturer = value; } 
    } 
} 

public class Manufacturer 
{ 
    private List<Product> m_Products = new List<Product>(); 

    public IEnumerable<Product> Products { get { return m_Products.AsReadOnly(); } } 

    public void AddProduct(Product p) 
    { 
     if(!m_Products.Contains(p)) 
     { 
     m_Products.Add(p); 
     p.Manufacturer = this; 
     } 
    } 

    public void RemoveProduct(Product p) 
    { 
     m_Products.Remove(p); 
     p.Manufacturer = null; 
    } 
} 
+0

Gracias por una respuesta clara. Para responder a lo que dijiste sobre permitir que un ORM lo administre, esto es lo que ya hago.Actualmente usando nhibernate. Sin embargo, quería saber cómo administrarlo porque tengo pruebas que fallan a menos que el objeto persista y se vuelva a leer desde el repositorio. antes de guardar la relación es de una manera, después de guardarla es de 2 vías. Esto no me parece correcto. –

+0

¿No necesita establecer la propiedad del fabricante en el producto p en AddProduct (y, a la inversa, desactivarlo en RemoveProduct)? Además, la llamada al método Contains() en AddProduct es O (n); si no desea duplicados, utilice un conjunto. –

+0

Sí, Michael, eché de menos establecer la propiedad del fabricante, eso ha sido reparado. En cuanto a la llamada de O (n) a Contiene, estoy de acuerdo contigo ... pero esto fue una muestra rápida. El código tiene otros problemas, debería ser más defensivo al buscar nulos, probablemente debería anular Equals on Product para que podamos comparar valores reales de instancia y no solo referencias, etc. También se recomienda optimizar la colección, como usted señala. . – LBushkin

2

Esta no es la forma correcta de modelar este problema.

Un Product debe tener un Price, un Price no debe tener un Product:

public class Product 
{ 
    public Price CurrentPrice {get; private set; } 
    public IList<Price> HistoricPrices { get; private set;} 
} 

public class Price { } 

En su configuración particular, ¿qué significa que para un Price para tener un Product? En la clase que creé anteriormente, usted podría manejar todos los precios dentro de la clase Product.

+0

Sería correcto si el precio se llamara "PrecioProducto". De todos modos, supongo que no es el objetivo de su pregunta. –

0

Mi primer pensamiento es, en su función/propiedad utilizada para agregar precios, agregar una línea de código, así:

public void addPrice(Price p) { 
    //code to add price goes here 
    p.Product = this; 
} 
1

En el pasado he añadido métodos "link", es decir, en lugar de "ajuste" del precio de un producto, se vincula el producto y el precio.

public void linkPrice(Price toLink) { 
    this.price = toLink; 
    toLink.setProduct(this); 
} 

(si se utiliza setPrice a hacer esto, entonces setProducto también podría hacer esto, y lo harían siempre se llaman entre sí en la segunda línea, por lo tanto, se crea un método vínculo explícito en lugar de utilizar los setters y getters. de hecho, el colocador podría paquete protegido.

tu caso es distinto, su situación podría ser diferente.

0

En el pasado, he hecho algo como esto cuando lo necesitaba para mantener este tipo de relación ...

public class Owner 
{ 
    public List<Owned> OwnedList { get; set; } 
} 

public class Owned 
{ 
    private Owner _owner; 

    public Owner ParentOwner 
    { 
     get 
     { 
      if (_owner == null) 
       _owner = GetParentOwner(); // GetParentOwner() can be any method for getting the parent object 
      return _owner; 
     } 
    } 
} 
0

Ahora estoy trabajando en un problema similar a este y estoy usando el # 3 en la respuesta de LBushkin.

Tengo un objeto de almacenamiento de datos con listas para todas mis clases de datos. Las clases de datos tienen identificadores en ellas para referencias a las otras clases y llamo de nuevo a la clase de almacenamiento de datos para obtener el elemento de referencia.

Lo he encontrado realmente útil porque necesito poder filtrar los datos y eso se hace cuando solicito los datos del almacenamiento de datos.

Cuestiones relacionadas