How to Map query results to multiple objects with Dapper in C#
By Tan Lee Published on Mar 12, 2025 373
Multi Mapping in Dapper
To set up multi-mapping, you need to define the following:
- The types to map to - Specify the classes to map the results to.
- The split column(s) - Tell Dapper how to split the columns across the different objects.
- A mapping function - This is where you link the mapped objects together after they are created.
In this article, I’ll show how multi-mapping works using an example with a Products and Categories table.
One-to-One Relationship Multi Mapping
Consider a Products table and a Categories table, where each product is linked to one category via the CategoryId
column:
Table relationship: One-to-one between Products and Categories.
SQL query:
1 2 3 4 5 |
SELECT p.ProductId, p. Name , c.CategoryId, c.CategoryName FROM Products p INNER JOIN Categories c ON p.CategoryId = c.CategoryId WHERE p.ProductId = @Id |
Sample query result:
ProductId | Name | CategoryId | CategoryName |
---|---|---|---|
12345 | "Smartphone" | 1 | "Electronics" |
Mapping the Result to Objects
We can map this result to a Product and Category object using multi-mapping.
1 2 3 4 5 6 7 8 9 10 11 |
using ( var con = new SqlConnection(ConnectionString)) { return con.Query<Product, Category, Product>(GET_SQL, map: (product, category) => { product.Category = category; return product; }, param: new { id }, splitOn: "CategoryId" ).FirstOrDefault(); } |
In this example:
Query<Product, Category, Product>
tells Dapper to map the columns to theProduct
object first, then to theCategory
object, and return anIEnumerable<Product>
.- The split column is
CategoryId
, so Dapper knows to map the columns left ofCategoryId
toProduct
and the remaining columns toCategory
.
Resulting object:
1 2 3 4 5 6 7 8 |
{ "ProductId": 12345, "Name": "Smartphone", "Category": { "CategoryId": 1, "CategoryName": "Electronics" } } |
One-to-Many Relationship Multi Mapping
Now, let’s say we have a Products table and an OrderItems table, where each product can be part of multiple order items. This forms a one-to-many relationship.
Table relationship: One-to-many between Products and OrderItems.
SQL query:
1 2 3 4 5 |
SELECT p.ProductId, p. Name , oi.OrderItemId, oi.Quantity FROM Products p INNER JOIN OrderItems oi ON p.ProductId = oi.ProductId WHERE p.ProductId IN @Ids |
Sample query result for a product:
ProductId | Name | OrderItemId | Quantity |
---|---|---|---|
12345 | "Smartphone" | 789 | 2 |
12345 | "Smartphone" | 790 | 1 |
Mapping the Result to Objects
In the one-to-many scenario, we need to ensure that multiple OrderItem
objects are added to a single Product
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
var productMap = new Dictionary< int , Product>(); using ( var con = new SqlConnection(ConnectionString)) { con.Query<Product, OrderItem, Product>(GET_ITEMS_SQL, map: (product, orderItem) => { orderItem.ProductId = product.ProductId; // Linking OrderItem to Product if (productMap.TryGetValue(product.ProductId, out Product existingProduct)) { product = existingProduct; // Use existing product } else { product.OrderItems = new List<OrderItem>(); productMap.Add(product.ProductId, product); // Store the product } product.OrderItems.Add(orderItem); return product; }, splitOn: "OrderItemId" , param: new { ids } ); } return productMap.Values; |
In this example:
Query<Product, OrderItem, Product>
tells Dapper to first map the columns to aProduct
object, then to anOrderItem
object.- The split column is
OrderItemId
, so columns to the left (likeProductId
andName
) map toProduct
, and the remaining columns (likeOrderItemId
andQuantity
) map toOrderItem
. - We use a dictionary (
productMap
) to ensure that each product is only mapped once, and its associated order items are added to theOrderItems
list.
Resulting object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{ "ProductId": 12345, "Name": "Smartphone", "OrderItems": [ { "OrderItemId": 789, "ProductId": 12345, "Quantity": 2 }, { "OrderItemId": 790, "ProductId": 12345, "Quantity": 1 } ] } |
Multi Mapping to More Than Two Objects
Finally, let's say we want to map the results of a query that pulls data from Products, Categories, and Suppliers tables. The relationships are as follows:
- One-to-one between
Products
andCategories
. - One-to-many between
Products
andSuppliers
.
Table relationship: One-to-one between Products and Categories, and one-to-many between Products and Suppliers.
SQL query:
1 2 3 4 5 6 7 |
SELECT p.ProductId, p. Name , c.CategoryId, c.CategoryName, s.SupplierId, s.SupplierName FROM Products p INNER JOIN Categories c ON p.CategoryId = c.CategoryId INNER JOIN Suppliers s ON p.SupplierId = s.SupplierId WHERE p.ProductId = @Id |
Sample query result:
ProductId | Name | CategoryId | CategoryName | SupplierId | SupplierName |
---|---|---|---|---|---|
12345 | "Smartphone" | 1 | "Electronics" | 10 | "TechCorp" |
Mapping the Result to Objects
Here’s how to map the results to Product
, Category
, and Supplier
objects:
1 2 3 4 5 6 7 8 9 10 11 12 |
using ( var con = new SqlConnection(ConnectionString)) { return con.Query<Product, Category, Supplier, Product>(GET_SQL, map: (product, category, supplier) => { product.Category = category; product.Supplier = supplier; return product; }, param: new { id }, splitOn: "CategoryId,SupplierId" ).FirstOrDefault(); } |
In this example:
Query<Product, Category, Supplier, Product>
tells Dapper to map the columns first toProduct
, then toCategory
, and finally toSupplier
.- The split columns are
CategoryId
andSupplierId
, so Dapper knows how to map the columns to the correct objects.
Resulting object:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "ProductId": 12345, "Name": "Smartphone", "Category": { "CategoryId": 1, "CategoryName": "Electronics" }, "Supplier": { "SupplierId": 10, "SupplierName": "TechCorp" } } |
This example covers how to perform multi-mapping with Dapper for various types of relationships, ranging from one-to-one to one-to-many and even multi-mapping with more than two objects.