1. Advanced Data Access
with Dapper
@Dave_Paquette
Microsoft MVP (ASP.NET/IIS)
contactme@davepaquette.com
http://www.davepaquette.com
2. Data Access
How did we get here?
Why is this so hard?
public class Foo
{
//…some properties
}
SQL Server
3. Data Access in .NET
• In the beginning there was System.Data
• IDbConnection
• IDbCommand
• IDataReader
• IDbTransaction
4. Sytem.Data - Reading Records
var results = new List<Employee>();
using (SqlConnection connection = new SqlConnection(Settings.ConnectionString))
{
SqlCommand command = new SqlCommand(“SELECT * FROM Employees”, connection);
connection.Open();
IDataReader reader = command.ExecuteReader();
while (reader.Read())
{
results.Add(ReadSingleRow(reader));
}
reader.Close();
}
return results;
5. The ORMs will save us
• Hide details of database
• Generate SQL based on object model and configuration
• ChangeTracking
• Complex mapping strategies
• Many-to-many relationships
• Inheritance
7. ORM Pain Points
• Black box code generation –What is going on?
• Performance Problems
• Eager Loading vs Lazy Loading
• Complex Inheritance Chains
• Dealing with disconnected entities (in a web context)
8. The Micro-ORMs will save us!
• Query --> Objects
• Usually focused on speed
• Light on features
10. Dapper
• Used in production at Stack Exchange
• Super fast and easy to use
11. Performance of SELECT mapping over 500
iterations - POCO serialization
Method Duration
Hand coded (using a SqlDataReader) 47ms
Dapper ExecuteMapperQuery 49ms
ServiceStack.OrmLite (QueryById) 50ms
PetaPoco 52ms
BLToolkit 80ms
SubSonic CodingHorror 107ms
NHibernate SQL 104ms
Linq 2 SQL ExecuteQuery 181ms
Entity framework ExecuteStoreQuery 631ms
https://github.com/StackExchange/dapper-dot-net#performance-of-select-mapping-over-500-iterations---dynamic-serialization
12. How can it be that fast?
• Dapper dynamically writes code for you
• Emits IL code for tasks like loading a data
reader into an object
• https://github.com/StackExchange/Dapper/blo
b/master/Dapper/SqlMapper.cs#L3078
13. Dapper works on Database Connections
A set of extension methods on IDbConnection
Aircraft aircraft;
using (var connection = new SqlConnection(_connectionString))
{
await connection.OpenAsync(); //Optional
var query = "SELECT * FROM Aircraft WHERE Id = @Id";
Aircraft aircraft = await connection.QuerySingleAsync<Aircraft>(query, new {Id = id});
}
15. Loading Related Objects – Multi-Mapping
• Write a single query that returns all the data in a single row
scheduledFlights =
await connection.QueryAsync<ScheduledFlight, Airport, ScheduledFlight>(query,
(flight, airport) => {
flight.Airport = airport;
return flight;
},
new{FromCode = from} );
16. Multi-Mapping Caveats
• Data duplication
• Query returns a bloated data set
• Multiple instances of an object that represent the same thing
• This is totally fine forOne-to-One relationships
• No duplication here
17. Loading Related Objects – Multiple Queries
• Get data using multiple queries and wire them up yourself
• Still executed in a single command that returns multiple results sets
using (var multi = await connection.QueryMultipleAsync(query, new{FromCode = from} ))
{
scheduledFlights = multi.Read<ScheduledFlight>();
var airports = multi.Read<Airport>().ToDictionary(a => a.Id);
foreach(var flight in scheduledFlights)
{
flight.Airport = airports[flight.AirportId];
}
}
18. Multi-Mapping vs Multiple Queries
100 ScheduledFlight Records 1,000 ScheduledFlight Records
Method Mean
MultiMapping 926.5 us
MultipleResultSets 705.9 us
Method Mean
MultiMapping 5.098 ms
MultipleResultSets 2.809 ms
21. Paging through large collections
• Use an OFFSET FETCH query
• Include a total count in the result
SELECT * FROM Flight f
INNER JOIN ScheduledFlight sf
ON f.ScheduledFlightId = sf.Id
ORDER BY Day, FlightNumber
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY
SELECT COUNT(*) FROM Flight f
INNER JOIN ScheduledFlight sf
ON f.ScheduledFlightId = sf.Id
22. Insert / Update / Delete
• Use the ExecuteAsync method
• Built-in support for batch inserts
await connection.ExecuteAsync(
@"INSERT INTO Flight(ScheduledFlightId, Day, ScheduledDeparture,
ScheduledArrival)
VALUES(@ScheduledFlightId, @Day, @ScheduledDeparture, @ScheduledArrival)",
flights);
Data access has consumed such a huge amount of our time.
- Why is that?
- What is so hard about this?
These 4 interfaces were in the original .NET framework.
They have been the basis for ALL database access since then.
Each database provider has an implementation of each of these interfaces (eg. SqlConnection)
Every ORM or Micro-ORM written in .NET is based on these 4 interfaces.
Show sample code for reading and writing data using the raw SQL
Swinging the pendulum
Demo
Start with loading simply the first level (Airport + Arrival and Departure Flights)
Using multi-mapping to load the ScheduledFlight info
Use multi queries to also load each ScheduledFlight’s Arrival and Departure airports