How to Map query results to multiple objects with Dapper in C#
By FoxLearn 3/12/2025 3:21:06 AM 17
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:
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.
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:
{ "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:
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
.
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:
{ "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:
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:
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:
{ "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.
- Global exception event handlers in C#
- How to Add or overwrite a value in ConcurrentDictionary in C#
- Handling Posted Form Data in an API Controller
- How to Add a custom action filter in ASP.NET Core
- How to Get all classes with a custom attribute in C#
- How to Update appsettings.json in C#
- Injecting ILogger into Dapper Polly Retry Policy in C#
- Properly Disposing HttpContent When Using HttpClient in C#