Dapper .NET Tutorial I
Introduction
One of the common operations when developing a business application is to access the data from relational database. As a .NET developer, we have gone through a very interesting journey to learn how to apply the “best” approach for the data access layer.
Not long ago, “stored procedure” is still the default approach. Until .NET 3.5 was released, more and more .NET developers started to use Linq2Sql, then recently Entity Framework. Some other ALT .Net developers might like to use open source ORM like NHibernate. There is nothing wrong with any of the options there, as long as you know how to use it correctly.
But things don’t go well some time. It requires developers have good knowledge of the ORM tools they are using. It is so easy to get wrong. The well-known “Select + 1” issue is just one of them. Besides that, the sql code generated by ORM, like Linq2Sql and Entity Framework is too complicate, far from optimized. It can make DBA cry.
Both of the framework heavily utilize Linq Expression, which cause big performance overhead when translating to underneath native SQL statements. Fortunately, some smart developers have already realized that. They take a different approach to create so-called micro-ORM. Those frameworks are built directly upon ADO.NET. They are using reflection (dynamic from .NET 4.0) to generate object from the result of data reader. They are simple and perform well. Some of them include:
How to get Dapper
Today, I like to talk about Dapper .net, which is part of the building blocks used in Stackoverflow. This framework is original created by Sam Saffron who is generous to offer it as open source for the whole .NET community. Dapper perform very well because it uses dynamic method generation (MSIL) to assign column values to properties.
There have two ways to reference Dapper in your project.
- Since the core of Dapper.net is only one file. It can be easily added to the project. You can directly access its repository and grab the file.
- In the meanwhile, it is can be obtained from Nuget. If you already installed Nuget. You can install it directly downloads it.
Using Dapper
Dapper is really simple to use. In this post, I will demonstrate some common usage. In the following blogs, I will show how to use Dapper Contrib and Dapper Rain bow.
Before we start access the database, let’s build our domain objects. Using Northwind as the backend database, I mainly use Product and Supplier table to demonstrate.
First let’s create the POCO objects for Product and Supplier:
public class Product { public int Id { get; set; } public string ProductName { get; set; } public int SupplierID { get; set; } public int CategoryID { get; set; } public string QuantityPerUnit { get; set; } public decimal UnitPrice { get; set; } public short? UnitsInStock { get; set; } public short? UnitsOnOrder { get; set; } public short? ReorderLevel { get; set; } public bool Discontinued { get; set; } // reference public Supplier Supplier { get; set; } }
// Supplier public class Supplier { public int Id { get; set; } public string CompanyName { get; set; } public string ContactName { get; set; } public string ContactTitle { get; set; } public string Address { get; set; } public string City { get; set; } public string PostalCode { get; set; } public string Country { get; set; } public IEnumerable<Product> Products { get; set; } }
Select Listusing (var sqlConnection = new SqlConnection(Constant.DatabaseConnection)) { sqlConnection.Open(); IEnumerable products = sqlConnection.Query("Selectom Products"); foreach (Product product in products) { ObjectDumper.Write(product); }
sqlConnection.Close(); }
When you run the sample, you can see a list of POCO product objects returned from a ADO-like command. How simple is that! Internally, Dapper .net use MSIL to access data reader then convert to the domain object.
- Select items by applying parameters To prevent SQL injection, Dapper provide the parameters, similar as using ADO.NET stored procedure. It is convenient to take an anonymous object as the parameter
using (var sqlConnection = new SqlConnection(Constant.DatabaseConnection)) { sqlConnection.Open(); IEnumerable products = sqlConnection .Query("Select * from Products where Id = @ProductId", new {ProductID = 2}); foreach (Product product in products) { ObjectDumper.Write(product); }
sqlConnection.Close(); }
- Return dynamic object You can return a list of dynamic object (.NET 4.0), which simplified the code a lot by removing the code to define customized DTO objects if you need to build a object by using the data from different tables.
using (var sqlConnection = new SqlConnection(connectionString)) { sqlConnection.Open(); IEnumerable products = sqlConnection .Query("Select * from Products where Id = @Id", new {Id = 2}); foreach (dynamic product in products) { ObjectDumper.Write(string.Format("{0}-{1pproduct.Id, product.ProductName)); }
sqlConnection.Close(); }
-
Retrieve multiple objects with one query
With the feature as QueryMutiple, dapper can return multiple object/list in one query. It definitely improve the performance.
using (var sqlConnection = new SqlConnection(Constant.DatabaseConnection)) { sqlConnection.Open(); var query = @" SELECT * FROM dbo.Suppliers WHERE Id = @Id SELECT * FROM dbo.Products WHERE SupplierID = @Id "; // return a GridReader using (var result = sqlConnection.QueryMultiple(query, new {Id = 1})) { var supplier = result.Read().Single(); var products = result.Read().ToList(); ObjectDumper.Write(supplier); Console.WriteLine(string.Format("Total Products {0}", products.Count)); ObjectDumper.Write(products); } sqlConnection.Close(); }
-
Retrieve object with referenced object
Dapper not only can get the domain directly, but also can retrieve the reference objects together.
using (var sqlConnection = new SqlConnection(Constant.DatabaseConnection)) { sqlConnection.Open(); IEnumerable products = sqlConnection .Query( @"select Products.*, Suppliers.* from Products join Suppliers on Products.SupplierId = Suppliers.Id and suppliers.Id = 2", (a, s) => { a.Supplier = s; return a; }); // use splitOn, if the id field is not Id or ID foreach (Product product in products) { ObjectDumper.Write(product.Supplier); }
sqlConnection.Close(); }
- Select one object with collection of child object Although, by default, dapper .net does not support this feature. But you can find the code posted by Sam from Statckoverflow. It uses QueryMutiple to return multiple lines, then use a dictionary to group the related key (product.SupplierId, in the sample above) together.
private static IEnumerable QuerySupplier() { using (var sqlConnection = new SqlConnection(Constant.DatabaseConnection)) { sqlConnection.Open(); var query = @" SELECT * FROM dbo.Suppliers WHERE ContactName = 'Charlotte Cooper' SELECT * FROM dbo.Products WHERE SupplierID IN (SELECT Id FROM dbo.Suppliers WHERE ContactName = 'Charlotte Cooper') "; return sqlConnection .QueryMultiple(query) .Map(supplier => supplier.Id, product => product.SupplierID, (supplier, products) => { supplier.Products = products; }); } sqlConnection.Close(); }
public static IEnumerable Map
(this SqlMapper.GridReader reader,
Func firstKey,
Func secondKey,
Action> addChildren
)
{
var first = reader.Read().ToList();
var childMap = reader
.Read()
.GroupBy(s => secondKey(s))
.ToDictionary(g => g.Key, g => g.AsEnumerable());
foreach (var item in first)
{
IEnumerable children;
if(childMap.TryGetValue(firstKey(item), out children))
{
addChildren(item,children);
}
}
return first;
}
-
Insert using Query interface
If we like to retrieve the new Id after insertion, we can use Query interface to retrieve the new identifier directly
using (var sqlConnection = new SqlConnection(Constant.DatabaseConnection)) { sqlConnection.Open(); var supplier = new Supplier() { Address = "10 Main Street", CompanyName = "ABC Corporation" }; supplier.Id = sqlConnection.Query( @" insert Suppliers(CompanyName, Address) values (@CompanyName, @Address) select cast(scope_identity() as int) ", supplier).First(); sqlConnection.Close(); Console.WriteLine(supplier.Id); Console.WriteLine("Done. "); }
- Insert using Sql Extension . Insertion can be applied by using a simple Insert statement.
using (var sqlConnection = new SqlConnection(Constant.DatabaseConnection)) { sqlConnection.Open(); var supplier = new Supplier() { Address = "10 Main Street", CompanyName = "DEF Corporation" }; sqlConnection.Execute( @" insert Suppliers(CompanyName, Address) values (@CompanyName, @Address) ", supplier); sqlConnection.Close(); Console.WriteLine("Done. "); }
- Update Item Updating a item has similar syntax as inserting. It uses update Sql statement with an anonymous object as the parameter.
using (var sqlConnection = new SqlConnection(Constant.DatabaseConnection)) { sqlConnection.Open(); var updateStatement = @"Update Products Set UnitPrice = @UnitPrice Where Id = @ProductId "; sqlConnection.Execute(updateStatement, new { UnitPrice = 100.0m, ProductId = 50 }); sqlConnection.Close(); }
Wrap up
In this post, we show some common data access scenarios by applying the “Core” implementation of Dapper.NET. Besides that, Dapper.NET has provided some other different API to make the data operation more intuitively. In the next post, we will show how to use Dapper Contri
You can get the source code from here.
Happy Programming!
[…] the last two posts, we discuss how to use Dapper.net to simplify the CRUD operation and how the APIs are […]
Thank you dude , this was really helpful. However i think that when you are using using(… you should close connection..
i meant shouldnt
I’m choosing my ORM for my next project. Thank you for this post, it’s very clear and complete.
Hola como haces el mapeo de las tablas de datos? o como escribes tus entidades y al ejecutar la aplicación te crea las tablas en la DB, en si en que consiste este micro ORM, deberías explicar mas a detalle paso a paso, tu defrente enseñas como hacer la consulta, pero no enseñas como se hace o como e debe de comenzar.
thanks liangwu good example
Thank You very much.. Got overall clear idea..
Linq to SQl is much much better dont possibly see why one would use dapper instead of Linq to SQL
[Scroll down and look at the tables of performance data](https://github.com/StackExchange/dapper-dot-net). Link to SQL is just an embarrassment.
Following code will break:
var childMap = reader
.Read()
.GroupBy(s => secondKey(s))
.ToDictionary(g => g.Key, g => g.AsEnumerable());
if the secondKey(s) leads to a null value, which is possible for the foreign key, so grouping will be fine but Dictionary insert would break, correct version would be:
var childMap = reader
.Read()
.GroupBy(s => secondKey(s))
.Where(grp=>grp.Key != null)
.ToDictionary(g => g.Key, g => g.AsEnumerable());
I think “Selectom Products” should read “Select * from Products”
Another approach to this problem is my QueryFirst VS extension. You write your SQL directly in a .sql file (intellisense, query validation etc), then QueryFirst generates a wrapper class and a POCO for your results. Running your query is one line of code. Your query is validated against the DB, and all your data accesses are strongly typed and validated. (no invalid casts, no referencing columns that don’t exist in the result set). Changes in your DB schema directly produce compilation errors in your application.
https://visualstudiogallery.msdn.microsoft.com/eaf390af-afc1-4994-a442-ec95923dafcb