Estou tentando usar o recurso Multimapping do dapper para retornar uma lista de ProductItems e clientes associados.
[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; }
}
Meu código dapper é o seguinte
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"
);
Isso funciona bem, mas parece que tenho que adicionar a lista de colunas completa ao parâmetro splitOn para retornar todas as propriedades dos clientes. Se eu não adicionar "CustomerName", ele retornará nulo. Estou entendendo mal a funcionalidade central do recurso de multimapeamento. Não quero ter que adicionar uma lista completa de nomes de coluna todas as vezes.
Respostas:
Acabei de executar um teste que funciona bem:
O parâmetro splitOn precisa ser especificado como o ponto de divisão, o padrão é Id. Se houver vários pontos de divisão, você precisará adicioná-los em uma lista delimitada por vírgulas.
Digamos que seu conjunto de registros seja assim:
O Dapper precisa saber como dividir as colunas nesta ordem em 2 objetos. Uma olhada rápida mostra que o cliente começa na coluna
CustomerId
, portantosplitOn: CustomerId
.Há uma grande advertência aqui, se a ordem das colunas na tabela subjacente for invertida por algum motivo:
splitOn: CustomerId
resultará em um nome de cliente nulo.Se você especificar
CustomerId,CustomerName
como pontos de divisão, o dapper assume que você está tentando dividir o conjunto de resultados em 3 objetos. Primeiro começa no início, segundo começa emCustomerId
, terceiro emCustomerName
.fonte
spliton
, ou seja ,CustomerId,CustomerName
nãoCustomerId, CustomerName
, já que o Dapper nãoTrim
divide os resultados da string. Ele apenas lançará o erro spliton genérico. Um dia me deixou louco.Nossas tabelas são nomeadas de forma semelhante à sua, onde algo como "CustomerID" pode ser retornado duas vezes usando uma operação 'select *'. Portanto, Dapper está fazendo seu trabalho, mas apenas dividindo muito cedo (possivelmente), porque as colunas seriam:
Isso torna o parâmetro spliton: não tão útil, especialmente quando você não tem certeza da ordem em que as colunas são retornadas. Claro que você pode especificar colunas manualmente ... mas é 2017 e raramente fazemos isso mais para obter objetos básicos.
O que fazemos, e funcionou muito bem para milhares de consultas por muitos anos, é simplesmente usar um alias para Id e nunca especificar spliton (usando o 'Id' padrão de Dapper).
... voila! O Dapper só será dividido no Id por padrão, e esse Id ocorre antes de todas as colunas do Cliente. É claro que isso adicionará uma coluna extra ao conjunto de resultados de retorno, mas isso é uma sobrecarga extremamente mínima para a utilidade adicional de saber exatamente quais colunas pertencem a qual objeto. E você pode facilmente expandir isso. Precisa de informações de endereço e país?
O melhor de tudo é que você está mostrando claramente em uma quantidade mínima de sql quais colunas estão associadas a quais objetos. Dapper faz o resto.
fonte
Assumindo a seguinte estrutura onde '|' é o ponto de divisão e Ts são as entidades às quais o mapeamento deve ser aplicado.
A seguir está a consulta dapper que você terá que escrever.
Então, queremos que TFirst mapeie col_1 col_2 col_3, para TSegundo o col_n col_m ...
A expressão splitOn se traduz em:
Comece o mapeamento de todas as colunas no TFrist até encontrar uma coluna com o nome ou alias de 'col_3', e também inclua 'col_3' no resultado do mapeamento.
Então comece a mapear em TSegundo todas as colunas começando de 'col_n' e continue mapeando até que um novo separador seja encontrado, que neste caso é 'col_A' e marca o início do terceiro mapeamento e assim um.
As colunas da consulta sql e os props do objeto de mapeamento estão em uma relação 1: 1 (o que significa que devem ter o mesmo nome), se os nomes das colunas resultantes da consulta sql forem diferentes, você pode usar o apelido 'AS [ Expressão Some_Alias_Name] '.
fonte
Há mais uma advertência. Se o campo CustomerId for nulo (geralmente em consultas com junção à esquerda), Dapper cria ProductItem com Customer = null. No exemplo acima:
E ainda mais uma advertência / armadilha. Se você não mapear o campo especificado em splitOn e esse campo contiver nulo, o Dapper cria e preenche o objeto relacionado (Cliente, neste caso). Para demonstrar o uso desta classe com sql anterior:
fonte
Eu faço isso genericamente em meu repo, funciona bem para meu caso de uso. Eu pensei em compartilhar. Talvez alguém vá estender isso ainda mais.
Algumas desvantagens são:
O código:
fonte
Se você precisa mapear uma grande entidade, cada campo deve ser uma tarefa difícil.
Eu tentei a resposta @BlackjacketMack, mas uma das minhas tabelas tem uma coluna Id outras não (eu sei que é um problema de design de banco de dados, mas ...) então isso insere uma divisão extra no dapper, é por isso
Não funciona para mim. Então eu terminei com uma pequena mudança nisso, apenas insira um ponto de divisão com um nome que não corresponda a nenhum campo nas tabelas, no caso alterado
as Id
poras _SplitPoint_
, o script sql final fica assim:Então, no dapper, adicione apenas um splitOn como este
fonte