Uso correcto de Multimapping en Dapper

Estoy intentando utilizar la función Multimapping de dapper para devolver una lista de artículos de productos y clientes asociados.

[Table("Product")] public class ProductItem { public decimal ProductID { get; set; } public string ProductName { get; set; } public string AccountOpened { get; set; } public Customer Customer { get; set; } } public class Customer { public decimal CustomerId { get; set; } public string CustomerName { get; set; } } 

Mi código apuesto es el siguiente

 var sql = @"select * from Product p inner join Customer c on p.CustomerId = c.CustomerId order by p.ProductName"; var data = con.Query( sql, (productItem, customer) => { productItem.Customer = customer; return productItem; }, splitOn: "CustomerId,CustomerName" ); 

Esto funciona bien, pero parece que tengo que agregar la lista completa de columnas al parámetro splitOn para devolver todas las propiedades de los clientes. Si no agrego “CustomerName”, devuelve null. ¿No entiendo mal la funcionalidad central de la función de multimapping? No quiero tener que agregar una lista completa de nombres de columna cada vez.

Acabo de ejecutar una prueba que funciona bien:

 var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(1 as decimal) CustomerId, 'name' CustomerName"; var item = connection.Query(sql, (p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First(); item.Customer.CustomerId.IsEqualTo(1); 

El parámetro splitOn debe especificarse como el punto de división, por defecto es Id. Si hay múltiples puntos de división, deberá agregarlos en una lista delimitada por comas.

Digamos que su conjunto de registros se ve así:

 ProductID |  ProductName |  Cuenta abierta |  CustomerId |  Nombre del cliente 
 --------------------------------------- ----------- --------------

Dapper necesita saber cómo dividir las columnas en este orden en 2 objetos. Una mirada superficial muestra que el Cliente comienza en la columna CustomerId , por splitOn: CustomerId tanto, splitOn: CustomerId .

Hay una gran advertencia aquí, si el orden de la columna en la tabla subyacente se voltea por alguna razón:

 ProductID |  ProductName |  Cuenta abierta |  CustomerName |  Identificación del cliente  
 --------------------------------------- ----------- --------------

splitOn: CustomerId dará como resultado un nombre de cliente nulo.

Si especifica CustomerId,CustomerName como puntos de división, dapper supone que está intentando dividir el conjunto de resultados en 3 objetos. Primero comienza al principio, el segundo comienza en CustomerId , el tercero en CustomerName .

Nuestras tablas se nombran de manera similar a la suya, donde algo como “CustomerID” puede ser devuelto dos veces usando una operación ‘select *’. Por lo tanto, Dapper está haciendo su trabajo, pero solo se divide demasiado pronto (posiblemente), porque las columnas serían:

 (select * might return): ProductID, ProductName, CustomerID, --first CustomerID AccountOpened, CustomerID, --second CustomerID, CustomerName. 

Esto hace que el parámetro spliton: no sea tan útil, especialmente cuando no está seguro en qué orden se devuelven las columnas. Por supuesto, puede especificar columnas manualmente … pero es 2017 y raramente lo hacemos más para los objetos básicos.

Lo que hacemos, y ha funcionado de maravilla para miles de consultas durante muchos años, es simplemente usar un alias para Id, y nunca especificar spliton (usando el ‘Id’ predeterminado de Dapper).

 select p.*, c.CustomerID AS Id, c.* 

… voila! Dapper solo se dividirá en Id de manera predeterminada, y esa Id se produce antes de todas las columnas de Clientes. Por supuesto, agregará una columna adicional a su resultado de retorno, pero es una sobrecarga extremadamente mínima para la utilidad adicional de saber exactamente qué columnas pertenecen a qué objeto. Y puedes expandir esto fácilmente. ¿Necesita información sobre la dirección y el país?

 select p.*, c.CustomerID AS Id, c.*, address.AddressID AS Id, address.*, country.CountryID AS Id, country.* 

Lo mejor de todo es que muestra claramente en una cantidad mínima de sql qué columnas están asociadas a cada objeto. Dapper hace el rest.

Hay una advertencia más. Si el campo CustomerId es nulo (normalmente en las consultas con join left), Dapper crea ProductItem con Customer = null. En el ejemplo de arriba:

 var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(null as decimal) CustomerId, 'n' CustomerName"; var item = connection.Query(sql, (p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First(); Debug.Assert(item.Customer == null); 

E incluso una advertencia / trampa más. Si no asigna el campo especificado en splitOn y ese campo contiene nulo, Dapper crea y llena el objeto relacionado (Cliente en este caso). Para demostrar, use esta clase con sql anterior:

 public class Customer { //public decimal CustomerId { get; set; } public string CustomerName { get; set; } } ... Debug.Assert(item.Customer != null); Debug.Assert(item.Customer.CustomerName == "n"); 

Lo hago genéricamente en mi repository, funciona bien para mi caso de uso. Pensé que lo compartiría. Tal vez alguien extenderá esto más.

Algunos inconvenientes son:

  • Esto supone que las propiedades de la clave externa son el nombre de su objeto secundario + “Id”, por ejemplo, UnitId.
  • Lo tengo solo mapeando 1 objeto hijo para el padre.

El código:

  public IEnumerable GetParentChild() { var sql = string.Format(@"select * from {0} p inner join {1} c on p.{1}Id = c.Id", typeof(TParent).Name, typeof(TChild).Name); Debug.WriteLine(sql); var data = _con.Query( sql, (p, c) => { p.GetType().GetProperty(typeof (TChild).Name).SetValue(p, c); return p; }, splitOn: typeof(TChild).Name + "Id"); return data; } 
    Intereting Posts