How do I write one to many query in Dapper.Net?
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
In Dapper, a one-to-many query usually means running a join and then rebuilding the object graph yourself. Dapper can map rows quickly, but it will not automatically understand that repeated parent data from a joined result set should collapse into one parent object with a child collection.
Example Shape
Suppose you have orders and items:
And a SQL query like:
This returns one row per order-item combination, which means the order columns repeat across rows.
Use a Lookup Dictionary
The standard Dapper pattern is to keep a dictionary keyed by the parent ID, then add children as rows are read:
This is the idiomatic one-to-many Dapper approach because it handles repeated parent rows without duplicating the parent object in memory.
Why splitOn Matters
splitOn tells Dapper where the columns for the second mapped object begin. If it is wrong, Dapper will map columns into the wrong object or fail in confusing ways.
In the example above, splitOn: "ItemId" means:
- columns before
ItemIdbelong toOrder - '
ItemIdand the columns after it belong toOrderItem'
That makes column order in the SQL query important.
Handle Missing Children Correctly
With a LEFT JOIN, orders with no items still appear, but the child columns are null. Your mapping logic should avoid adding empty child objects for those rows.
That is why the null or sentinel check in the mapping delegate matters. Without it, you may end up with one fake OrderItem in every otherwise-empty order.
Sometimes Two Queries Are Simpler
A single join query is common, but it is not always the best design. For some workloads, querying parents and children separately and composing them in memory is clearer:
- one query for orders
- one query for items by order IDs
That can be easier to reason about when the object graph gets larger than one parent and one child type.
Common Pitfalls
- Expecting Dapper to build one-to-many object graphs automatically with no lookup dictionary.
- Using the wrong
splitOncolumn. - Forgetting that joined parent rows repeat and then getting duplicate parent objects.
- Adding empty child objects for
LEFT JOINrows with no child data. - Writing overly wide joins when two simpler queries would be clearer.
Summary
- Dapper one-to-many mapping usually means join rows plus a parent lookup dictionary.
- Use
Query<TParent, TChild, TParent>with a mapping delegate. - Set
splitOnto the first column of the child object in the result set. - Guard against empty child rows when using
LEFT JOIN. - Choose between one join and multiple simpler queries based on clarity and data shape.
Related reading
- How do MySQL indexes work?
- How do parameterized queries help against SQL injection?
- How do reconnecting nodes in a database synchronize with majority cluster?
- How do search engines merge results from an inverted index?
- How do the semantics of AsyncLocal differ from the logical call context?
- How do the Sho dll's from Microsoft Research compare to the open-source Math.NET numerics project
- How do synchronized static methods work in Java and can I use it for loading Hibernate entities?
- How do synchronized static methods work in Java and can I use it for loading Hibernate entities?

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.