Estoy tratando de usar la función Multimapping de dapper para devolver una lista de ProductItems 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 elegante 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<ProductItem, Customer, ProductItem>(
sql,
(productItem, customer) => {
productItem.Customer = customer;
return productItem;
},
splitOn: "CustomerId,CustomerName"
);
Esto funciona bien, pero parece que tengo que agregar la lista de columnas completa al parámetro splitOn para devolver todas las propiedades de los clientes. Si no agrego "CustomerName", devuelve nulo. ¿No entiendo bien la funcionalidad principal de la función de mapas múltiples? No quiero tener que agregar una lista completa de nombres de columna cada vez.
Respuestas:
Acabo de ejecutar una prueba que funciona bien:
El parámetro splitOn debe especificarse como el punto de división, por defecto es Id. Si hay varios puntos de división, deberá agregarlos en una lista delimitada por comas.
Digamos que su conjunto de registros se ve así:
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 lo tantosplitOn: CustomerId
.Aquí hay una gran advertencia, si el orden de las columnas en la tabla subyacente se invierte por alguna razón:
splitOn: CustomerId
dará como resultado un nombre de cliente nulo.Si especifica
CustomerId,CustomerName
como puntos de división, Dapper asume que está tratando de dividir el conjunto de resultados en 3 objetos. Primero comienza al principio, segundo comienza enCustomerId
, tercero enCustomerName
.fuente
spliton
, es decir ,CustomerId,CustomerName
noCustomerId, CustomerName
, ya que Dapper no tieneTrim
los resultados de la división de cadenas. Simplemente arrojará el error genérico de división. Me volvió loco un día.Nuestras tablas se nombran de manera similar a la suya, donde algo como "CustomerID" podría devolverse dos veces usando una operación 'select *'. Por lo tanto, Dapper está haciendo su trabajo pero simplemente dividiendo demasiado pronto (posiblemente), porque las columnas serían:
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, podría especificar columnas manualmente ... pero estamos en 2017 y rara vez lo hacemos para obtener objetos básicos.
Lo que hacemos, y ha funcionado muy bien 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).
... ¡voilá! Dapper solo se dividirá en Id de forma predeterminada, y ese Id aparece antes de todas las columnas de Cliente. Por supuesto, agregará una columna adicional a su conjunto de resultados de retorno, pero esa 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?
Lo mejor de todo es que está mostrando claramente en una cantidad mínima de sql qué columnas están asociadas con qué objeto. Dapper hace el resto.
fuente
Suponiendo la siguiente estructura donde '|' es el punto de división y Ts son las entidades a las que se debe aplicar el mapeo.
A continuación se muestra la consulta elegante que tendrá que escribir.
Entonces queremos que TFirst asigne col_1 col_2 col_3, para TSecond el col_n col_m ...
La expresión splitOn se traduce en:
Comience a mapear todas las columnas en TFrist hasta que encuentre una columna denominada o con alias como 'col_3', y también incluya 'col_3' en el resultado del mapeo.
Luego comience a mapear en TSecond todas las columnas comenzando desde 'col_n' y continúe mapeando hasta que se encuentre un nuevo separador, que en este caso es 'col_A' y marca el inicio del mapeo TThird y así uno.
Las columnas de la consulta sql y los accesorios del objeto de mapeo están en una relación 1: 1 (lo que significa que deben tener el mismo nombre), si los nombres de las columnas resultantes de la consulta sql son diferentes, puede usar un alias con 'AS [ Some_Alias_Name] 'expresión.
fuente
Hay una salvedad más. Si el campo CustomerId es nulo (normalmente en consultas con combinación izquierda), Dapper crea ProductItem con Customer = null. En el ejemplo anterior:
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 el uso de esta clase con sql anterior:
fuente
Hago esto de forma genérica en mi repositorio, funciona bien para mi caso de uso. Pensé en compartir. Quizás alguien amplíe esto más.
Algunos inconvenientes son:
El código:
fuente
Si necesita mapear una entidad grande, escriba cada campo debe ser una tarea difícil.
Probé la respuesta de @BlackjacketMack, pero una de mis tablas tiene una columna de identificación, otras no (sé que es un problema de diseño de base de datos, pero ...) luego esto inserta una división adicional en dapper, por eso
No funciona para mi. Entonces terminé con un pequeño cambio de esto, basta con insertar un punto de división con un nombre que no coincide con ninguna de campo en las mesas, en mayo de casos cambiado
as Id
poras _SplitPoint_
, las miradas finales de secuencia de comandos SQL como la siguiente:Luego, en apuesto, agregue solo una división como esta
fuente