C# – Mappa i risultati della query su più oggetti con Dapper

C# – Mappa i risultati della query su più oggetti con Dapper

Quando esegui query su tabelle unite, puoi mappare ogni riga su più oggetti utilizzando la funzione di mappatura multipla in Dapper.

Per la mappatura multipla, devi fornire a Dapper quanto segue:

  • A quali tipi eseguire la mappatura.
  • In quale colonna/e dividere. Questo dice a Dapper quali colonne dovrebbe provare a mappare a quale tipo.
  • Una funzione di mappatura in cui Dapper passa gli oggetti mappati e puoi collegarli insieme.

In questo articolo mostrerò esempi di mappatura multipla.

Nota:se non specifichi la colonna divisa, utilizzerà l'impostazione predefinita "Id". Consiglio di specificare sempre in modo esplicito la colonna divisa.

Mappatura multipla delle relazioni uno-a-uno

La tabella Ordini ha una relazione uno-a-uno con la tabella Clienti e sono collegati dalla colonna CustomerId:

La seguente query SQL seleziona un ordine e il cliente associato:

SELECT o.OrderId, o.[Status], c.CustomerId, c.[Name]                         
FROM Orders o
INNER JOIN Customers c
ON o.CustomerId = c.CustomerId
WHERE o.OrderId = @Id
Code language: SQL (Structured Query Language) (sql)

Ecco i risultati della query:

OrderId	Status	CustomerId	Name
43672	New	30067		Corey LuoCode language: plaintext (plaintext)

Per associare questi risultati a un oggetto Ordine e Cliente, utilizza la mappatura multipla e la suddivisione nella colonna CustomerId:

using (var con = new SqlConnection(ConnectionString))
{
	return con.Query<Order, Customer, Order>(GET_SQL, 
		map: (order, customer) =>
		{
			order.Customer = customer;
			return order;
		},
		param: new { id },
		splitOn: "CustomerId").FirstOrDefault();
}
Code language: C# (cs)

Query significa prima mappare le colonne a un oggetto Ordine, quindi a un oggetto Cliente e restituire IEnumerable.

Per ogni riga, crea un oggetto Ordine e un oggetto Cliente. Mappa le colonne agli oggetti in base alla colonna divisa (CustomerId) in questo modo:

  • Colonne dell'ordine =tutte le colonne a sinistra di CustomerId (OrderId, Status).
  • Colonne cliente =colonne rimanenti (CustomerId, Name).

Il risultato finale è un oggetto Ordine con un oggetto Cliente:

{
  "OrderId": 43659,
  "Customer": {
    "CustomerId": 29825,
    "Name": "Corey Luo"
  },
  "Status": "New"
}Code language: JSON / JSON with Comments (json)

Mappatura multipla di relazioni uno-a-molti

La tabella Orders ha una relazione uno-a-molti con la tabella OrderLines e sono collegati dalla colonna OrderId:

La seguente query SQL seleziona gli ordini e le righe ordine associate:

SELECT o.OrderId, o.Status, ol.OrderLineId, ol.Product, ol.Quantity
FROM Orders o
INNER JOIN OrderLines ol
ON o.OrderId = ol.OrderId
WHERE o.OrderId IN @Ids
Code language: SQL (Structured Query Language) (sql)

Ecco i risultati della query (per un ID ordine singolo):

OrderId	Status	OrderLineId	Product				Quantity
43672	New	126		Mountain Bike Socks, M		6
43672	New	127		Mountain-100 Black, 42		2
43672	New	128		Mountain-100 Silver, 48		1Code language: plaintext (plaintext)

Per eseguire il mapping di questi risultati agli oggetti Order/OrderLine, eseguire la mappatura multipla e dividerli nella colonna OrderLineId. La funzione mappa è più complessa nello scenario uno-a-molti.

var orderMap = new Dictionary<int, Order>();

using (var con = new SqlConnection(ConnectionString))
{
	con.Query<Order, OrderLine, Order>(GET_LINES_SQL,
		map: (order, orderLine) =>
		{
			orderLine.OrderId = order.OrderId; //non-reference back link

			//check if this order has been seen already
			if (orderMap.TryGetValue(order.OrderId, out Order existingOrder))
			{
				order = existingOrder;
			}
			else
			{
				order.Lines = new List<OrderLine>();
				orderMap.Add(order.OrderId, order);

			}

			order.Lines.Add(orderLine);
			return order;
		},
		splitOn: "OrderLineId",
		param: new { ids }
	);
}

return orderMap.Values;
Code language: C# (cs)

Query significa prima mappare le colonne a un oggetto Order, quindi a un oggetto OrderLine e restituire IEnumerable.

Per ogni riga, crea un oggetto Order e un oggetto OrderLine e mappa le colonne in base alla colonna divisa (OrderLineId) in questo modo:

  • Colonne dell'ordine =tutte le colonne a sinistra di OrderLineId (OrderId, Status).
  • Colonne OrderLine =colonne rimanenti (OrderLineId, Prodotto, Quantità).

Passa gli oggetti mappati alla funzione mappa. Dapper associa le colonne dell'ordine a un nuovo oggetto Ordine per ogni riga, motivo per cui è necessario deduplicare e tenere traccia di oggetti Ordine univoci con un Dizionario.

Ciò si traduce nel seguente oggetto Order con una matrice di oggetti OrderLine:

{
  "OrderId": 43672,
  "Lines": [
    {
      "OrderLineId": 126,
      "OrderId": 43672,
      "Product": "Mountain Bike Socks, M",
      "Quantity": 6
    },
    {
      "OrderLineId": 127,
      "OrderId": 43672,
      "Product": "Mountain-100 Black, 42",
      "Quantity": 2
    },
    {
      "OrderLineId": 128,
      "OrderId": 43672,
      "Product": "Mountain-100 Silver, 48",
      "Quantity": 1
    }
  ],
  "Status": "New"
}Code language: JSON / JSON with Comments (json)

Nota:sembra inefficiente che Dapper stia mappando le colonne dell'ordine su nuovi oggetti Order per ogni riga. L'alternativa è eseguire più query, una per Orders e una per OrderLines, quindi scorrere i risultati e collegarli. Sulla base dei miei test, ha all'incirca le stesse prestazioni della mappatura multipla.

Mappatura multipla su più di due oggetti

La tabella Ordini ha una relazione uno-a-uno con la tabella Clienti e la tabella Negozi:

La seguente query SQL seleziona un ordine e il cliente e il negozio associati:

SELECT o.OrderId, o.[Status], c.CustomerId, c.[Name], s.StoreId, s.[Location]
FROM Orders o
INNER JOIN Customers c
ON o.CustomerId = c.CustomerId
INNER JOIN Stores s
ON o.StoreId = s.StoreId
WHERE o.OrderId = @Id
Code language: SQL (Structured Query Language) (sql)

Ecco i risultati:

OrderId	Status	CustomerId	Name		StoreId	Location
43672	New	30067		Corey Luo	1	Main StCode language: plaintext (plaintext)

Ecco come mappare in modo multiplo questi risultati su un oggetto Ordine/Cliente/Negozio:

using (var con = new SqlConnection(ConnectionString))
{
	return con.Query<Order, Customer, Store, Order>(GET_SQL,
		map: (order, customer, store) =>
		{
			order.Customer = customer;
			order.Store = store;
			return order;
		},
	param: new { id },
	splitOn: "CustomerId,StoreId").FirstOrDefault();
}
Code language: C# (cs)

Query significa prima mappare le colonne a un oggetto Order, quindi a un oggetto Customer, quindi a un oggetto Store e infine restituire IEnumerable.

Quando esegui il mapping a più di due oggetti, dovrai specificare più colonne divise con una stringa delimitata da virgole ("CustomerId,StoreId"). Mappa le colonne ai tre oggetti in base a queste colonne divise (CustomerId e StoreId) in questo modo:

  • Colonne dell'ordine =tutte le colonne a sinistra di CustomerId (OrderId, Status).
  • Colonne cliente =colonne rimanenti a sinistra di StoreId (CustomerId, Name).
  • Colonne negozio =colonne rimanenti (StoreId, Posizione).

Ecco l'oggetto Ordine risultante con oggetti Cliente/Negozio collegati:

{
  "OrderId": 43659,
  "Customer": {
    "CustomerId": 29825,
    "Name": "Corey Luo"
  },
  "Status": "New",
  "Store": {
    "StoreId": 1,
    "Location": "Main St"
  }
}Code language: JSON / JSON with Comments (json)