Welcome To Learn Dapper
This site is for developers who want to learn how to use Dapper - The micro ORM created by the people behind Stack Overflow.
QuerySingleOrDefault 查询记录集的数量
public bool IsExistXSDOrderByStockout_id(long stockout_id) { if (sqlConn.State != System.Data.ConnectionState.Open) { sqlConn.Open(); } using (SqlCommand sqlcmd = new SqlCommand()) { sqlcmd.CommandType = System.Data.CommandType.Text; sqlcmd.CommandText = string.Format(@" select count(*) from dycw{0}..tWDT_xsd_order_each where stockout_id = @stockout_id", DefaultAccID); sqlcmd.Connection = sqlConn; var resultQuery = sqlConn.QuerySingleOrDefault<int>(new CommandDefinition( sqlcmd.CommandText, new { stockout_id = stockout_id }, null, null, sqlcmd.CommandType, CommandFlags.None)); return resultQuery > 0; } }
What is Dapper?
Dapper is an open-source object-relational mapping (ORM) library for .NET and .NET Core applications. The library allows developers quickly and easily access data from databases without the need to write tedious code. Dapper allows you to execute raw SQL queries, map the results to objects, and execute stored procedures, among other things. It is available as a NuGet package.
- Dapper is lightweight and fast, making it an ideal choice for applications that require low latency and high performance.
- It is a simple yet powerful object mapping tool for any .NET language, such as C#, that enables developers to quickly and easily map query results from ADO.NET data readers to instances of business objects.
- It has excellent support for both asynchronous and synchronous database queries and batching multiple queries together into a single call.
- Additionally, dapper supports parameterized queries to help protect against SQL injection attacks.
Does Dapper support .NET Core?
Yes, Dapper has been supporting .NET Core since the release of version 1.50 in July 2016. Dapper is a cross-platform .NET library, which means that it can be used on any platform that supports .NET, including .NET Core.
Does Dapper support C#?
Yes, you can use Dapper with C# as multiple other languages, such as VB.NET and F#. In fact, Dapper was written in C# and is a popular choice for data access in C# applications because of its simplicity and efficiency.
Is Dapper an ORM?
Dapper falls into a family of tools known as micro-ORMs. These tools perform only a subset of the functionality of full-blown Object Relations Mappers, such as Entity Framework Core, but Dapper is known for its speed and simple implementation compared to others. The following table provides a general idea of the capabilities that you can expect to find in a micro ORM compared to an ORM:
Micro ORM | ORM | |
---|---|---|
Map queries to objects | ||
Caching results | ||
Change tracking | 1 | |
SQL generation | 2 | |
Identity management | ||
Association management | ||
Lazy loading | ||
Unit of work support | ||
Database migrations | ||
Dapper concentrates its efforts on the O and M of ORM - Object Mapping.
[1] Some extensions have been added to Dapper that provide the minimal change-tracking capability
[2] Dapper does generate SQL but in a limited fashion. Third-party libraries such as Dapper Plus can generate the full SQL for insert, update, and delete statements
When Should You Use Dapper?
When deciding whether to use Dapper or not, one should bear in mind the primary reason for its existence - performance. The original developers of Dapper were using Entity Framework Core's predecessor - the short-lived Linq to SQL. However, they found that query performance wasn't good enough for the increasing traffic that the site in question (Stack Overflow) was experiencing, so they wrote their own micro ORM.
Dapper is, therefore, a good choice in scenarios where read-only data changes frequently and is requested often. It is particularly good in stateless scenarios (e.g. the web) where there is no need to persist complex object graphs in memory for any duration.
Dapper does not translate queries written in .NET languages to SQL like a full-blown ORM. So you need to be comfortable writing queries in SQL or have someone write them for you.
Dapper has no real expectations about the schema of your database. It is not reliant on conventions in the same way as Entity Framework Core, so Dapper is also a good choice where the database structure isn't particularly normalized.
Dapper works with an ADO.NET IDbConnection
object, which means that it will work with any database system for which there is an ADO.NET provider.
There is no reason why you cannot use both an ORM and a micro ORM in the same project.
What does Dapper do?
Here is a standard ADO.NET C# code for retrieving data from a database and materializing it as a collection of Product
objects:
var sql = "select * from products";
var products = new List<Product>();
using (var connection = new SqlConnection(connString))
{
connection.Open();
using (var command = new SqlCommand(sql, connection))
{
using (var reader = command.ExecuteReader())
{
var product = new Product
{
ProductId = reader.GetInt32(reader.GetOrdinal("ProductId")),
ProductName = reader.GetString(reader.GetOrdinal("ProductName")),
SupplierId = reader.GetInt32(reader.GetOrdinal("SupplierId")),
CategoryId = reader.GetInt32(reader.GetOrdinal("CategoryId")),
QuantityPerUnit = reader.GetString(reader.GetOrdinal("QuantityPerUnit")),
UnitPrice = reader.GetDecimal(reader.GetOrdinal("UnitPrice")),
UnitsInStock = reader.GetInt16(reader.GetOrdinal("UnitsInStock")),
UnitsOnOrder = reader.GetInt16(reader.GetOrdinal("UnitsOnOrder")),
ReorderLevel = reader.GetInt16(reader.GetOrdinal("ReorderLevel")),
Discontinued = reader.GetBoolean(reader.GetOrdinal("Discontinued")),
DiscontinuedDate = reader.GetDateTime(reader.GetOrdinal("DiscontinuedDate"))
};
products.Add(product);
}
}
}
At its most basic level, Dapper replaces the highlighted block of assignment code in the example above with the following:
products = connection.Query<Product>(sql).ToList();
Dapper also creates the command and opens the connection if needed. If you use Dapper to manage basic assignments like this, it will save you hours. However, Dapper is capable of doing quite a bit more.
public static List<Entity_XSJL> getXSJL_ByDapper(string strConn, string accountSetsID, string accountSetsName) { using (System.Data.SqlClient.SqlConnection sqlConn = new SqlConnection(strConn)) { using (SqlCommand sqlcmd = new SqlCommand()) { sqlcmd.CommandType = System.Data.CommandType.StoredProcedure; sqlcmd.CommandText = string.Format("{0}..pGansu_Data_XSJL", accountSetsID); sqlcmd.Connection = sqlConn; using (var resultQuery = sqlConn.QueryMultiple(new CommandDefinition( sqlcmd.CommandText, new { dFromDate = Utils.BaseInfo.defaultStartDate }, null, null, sqlcmd.CommandType, CommandFlags.None))) { List<Entity_XSJL> listResult = new List<Entity_XSJL>(100); var timestamp = IoriUtilityLib.Iori.TimeUtil.GetTimeStamp(); var rowMain = resultQuery.Read<Entity_XSJL.Req>().ToList(); var rowSubAll = resultQuery.Read<Entity_XSJL.FhdInfoItem>().ToList(); foreach (var rowParent in rowMain) { rowParent.timestamp = timestamp; rowParent.dataSet.fhdInfo = rowSubAll.Where(exp => exp.cBillCode == rowParent.event_id).ToList(); Entity_XSJL entity = new Entity_XSJL() { req = rowParent, AccountSetsName = accountSetsName, AccountSetsID = accountSetsID }; listResult.Add(entity); } return listResult; } } } }
Handling Multiple Resultsets
While not a very common usage scenario, it can come handy sometimes
It may happen that you are interested in executing a batch of SQL commands whose results produce not only one resultset, but more than one. This helps to avoid doing additional roundtrips to the database. For example you can return suppliers and customers at once executing the following statement in the same command:
SELECT … FROM dbo.Suppliers;
SELECT … FROM dbo.Customers;
I’m not really fond of this approach, and if the two objects are independent from each other (like in the sample) I would rather prefer two separate asynchronous (parallel would even be better) calls to the database, but you don’t always have this option, or maybe you’re in a case where a plain and simple approach is preferred.
In any case, if you have multiple resultset, Dapper can help you, via the QueryMultiple
method:
var results = conn.QueryMultiple(@"
SELECT Id, FirstName, LastName FROM dbo.Users;
SELECT Id, CompanyName FROM dbo.Companies
");
var users = results.Read<User>();
var companies = results.Read<Company>();
QueryMultiple
returns a GridReader
object that allows you to access the available resultset in sequence, from the first to the last. All you have to do is call the Read
method, as shown in the above code, to access each results. Read
behave exactly like the Query
method we already discussed in the first article. In fact it supports all the specialized method that also Query
supports
ReadFirst
ReadFirstOrDefault
ReadSingle
ReadSingleOrDefault
all the Read
methods can also be called asynchronously via ReadAsync
.
A typical Use Case
So, even if not so common, a use case for such method exits, and is related to the necessity to create complex object with data coming from different tables.
Let’s go with another typical example here: Customers and Orders. You want to load a customer object with all related orders.
As one can guess, the Customer object has a list of Orders:
If you’re a database guy you probably immediately thought to solve the problem by joining Customers and Orders table
which will produce the following result:
Unfortunately there are a few problems with this approach from a developer perspective, even if the correct one from a database perspective.
The first problem is that we had to create column name alias, since both the tables have the Id
column. By default Dapper maps columns to properties by name matching, and thus the introduced alias will prevent this native behavior to work properly. As we’ll see in a future article, we can handle this case, but it will make our code a little bit more complex. And I don’t like making the code complex of such small thing: introducing complexity when it is not really needed is always a bad idea.
The second problem is that the resultset have has many rows as many orders the customer placed. This in turn means that customer data (Id
and Name
in the example) is returned for each order. Now, beside the fact that this will waste bandwidth, impacting on overall performance and resource usage, we also have to make sure that we actually create just one Customer object. Dapper won’t do that for us, so, again, additional complexity needs to be introduced.
As you can understand, the two aforementioned problems prevent the usage of the SELECT…JOIN
approach with Dapper. The correct solution is to query the Customer
table and create the single Customer
object and then query the Order
table, create the Order
objects and the link them to the created customer.
To avoid doing two roundtrips to the database to get customer and orders data separately, the multiple resultset feature can be used:
Of course in case you’re dealing with multiple commands like above you may also want to make sure you wrap everything in a transaction, to assure data consistency. Transaction support will be discussed in a future post but in case you want to go forward, know that Dapper supports transaction via the TransactionScope
option.
Samples
As usual an example that shows how to use the discussed feature is available here:
yorek/dapper-samples
dapper-samples - Samples that shows how to use Dapper .NET
github.com
Conclusions
The described feature helps to solve a very specific problem, keeping the performance high and the code clean, but has a big limitation: what if, in addition to the Order we also decided to get the Order Items? The discussed feature wouldn’t have helped and we would had to do a separate query to the database, for each order, to get the related items. Really not good for performance. Luckily if your database supports JSON, (SQL Server 2016 and after and Azure SQL both supports it) there are a much better and powerful way to elegantly solve the problem of mapping rows to complex objects. We’ll discuss it very soon.
What’s next
Next article will be dedicated to a feature somehow related with what discussed here: “Multiple Mapping” or automatically mapping a single row to multiple objects.
Querying Multiple Results With Dapper
You can execute multiple queries in a single SQL statement using the QueryMultiple
method (if the database provider supports it). Once executed, you can map the returned results to multiple objects using methods such as Read<T>
, ReadFirst<T>
, ReadSingle<T>
and more.
The Dapper QueryMultiple
method allows you to select multiple results from a database query. That feature is very useful for selecting multiple results sets at once, thus avoiding unnecessary round trips to the database server.
- The
QueryMultiple
method provides a simple way of fetching data stored in different tables or views with a single database query. - By using dapper's
QueryMultiple
method, you can select multiple rows from different tables in one go, saving time and resources. - Dapper's
QueryMultiple
method allows you to map results from multiple queries into strongly typed collections that can be easily consumed within your application code. - Using Dapper
QueryMultiple
is an efficient way to select multiple results from a single database query. - It saves time and resources by avoiding unnecessary round trips to the database server and eliminates the need for complex SQL queries.
Method | Description |
---|---|
QueryMultiple |
Execute multiple queries within a single database command and return a GridReader to map the results to multiple objects |
QueryMultipleAsync |
Execute multiple queries within a single database command and return a GridReader to map the results to multiple objects asynchronously |
After using the QueryMultiple
or QueryMultipleAsync
method, you can use the following methods with the results:
Method | Description |
---|---|
Read , ReadAsync |
Returns an enumerable of dynamic types |
Read<T> , ReadAsync<T> |
Returns an enumerable of the type specified by the T parameter |
ReadFirst , ReadFirstAsync |
Returns the first row as a dynamic type |
ReadFirst<T> , ReadFirstAsync<T> |
Returns the first row as an instance of the type specified by the T type parameter |
ReadFirstOrDefault , ReadFirstOrDefaultAsync |
Returns the first row as a dynamic type or null if no results are returned |
ReadFirstOrDefault<T> , ReadFirstOrDefaultAsync<T> |
Returns the first row as an instance of the type specified by the T type parameter or null if no results are returned |
ReadSingle , ReadSingleAsync |
Use when only one row is expected to be returned. Returns a dynamic type |
ReadSingle<T> , ReadSingleAsync<T> |
Use when only one row is expected to be returned. Returns an instance of the type specified by the T type parameter |
ReadSingleOrDefault , ReadSingleOrDefaultAsync |
Use when zero or one row is expected to be returned. Returns a dynamic type or null |
ReadSingleOrDefault<T> , ReadSingleOrDefaultAsync<T> |
Use when zero or one row is expected to be returned. Returns an instance of the type specified by the T type parameter or null |
Dapper QueryMultiple
The following example demonstrates how to use Dapper QueryMultiple
to execute multiple SQL statements in one query.
string sql = @"
SELECT * FROM Invoices WHERE InvoiceID = @InvoiceID;
SELECT * FROM InvoiceItems WHERE InvoiceID = @InvoiceID;
";
using (var connection = new SqlConnection(connectionString))
{
using (var multi = connection.QueryMultiple(sql, new {InvoiceID = 1}))
{
var invoice = multi.First<Invoice>();
var invoiceItems = multi.Read<InvoiceItem>().ToList();
}
}
With this, we can execute multiple SQL statements in one query and read each result set from the GridReader
object returned by the QueryMultiple
method. That can help increase the performance of your application in cases where multiple queries need to be executed and their results combined.
Using Dapper QueryMultiple
is a great way to simplify data access and ensure that your application runs quickly and efficiently. With its easy-to-use API, you can quickly set up complex queries and access the data you need.
Dapper QueryMultipleAsync
The asynchronous version of the QueryMultiple<T>
method is QueryMultipleAsync<T>
:
string sql = @"
SELECT * FROM Invoices WHERE InvoiceID = @InvoiceID;
SELECT * FROM InvoiceItems WHERE InvoiceID = @InvoiceID;
";
using (var connection = new SqlConnection(connectionString))
{
using (var multi = await connection.QueryMultipleAsync(sql, new {InvoiceID = 1}))
{
var invoice = await multi.FirstAsync<Invoice>();
var invoiceItems = await multi.ReadAsync<InvoiceItem>().ToList();
}
}
Related Articles
Working with database with C# (3 Part Series)
Learn how to read reference table from SQL-Server using a single method. What is shown provides an efficient way to either use a connection, command objects to read data via a SqlDataReader for conventional work using methods from SqlClient and also Dapper which requires two lines of code to read data and one line of code to store data into list.
Goal
To read from three reference table in a modified version of Microsoft NorthWind database, Categories, ContactType and Countries tables.
In all code samples all records are read from each table, in some cases not all records may be needed, simply change the SQL SELECT statement with a WHERE clause. Also, the same goes for columns.
Project type used
A console project is used although the code provided will work in any project type.
Creating the database
Open SSMS (SQL-Server Management Studio), create a new database named Northwind2020, run the script under the Scripts folder.
Required models
Each model has properties matching columns in the tables we will read from, not all columns are included in some tables.
The override ToString can be helpful in some cases for instance in a control or component that needs to know what to display, for example a ComboBox.
public class Categories
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public override string ToString() => CategoryName;
}
public class ContactType
{
public int ContactTypeIdentifier { get; set; }
public string ContactTitle { get; set; }
public override string ToString() => ContactTitle;
}
public class Countries
{
public int CountryIdentifier { get; set; }
public string Name { get; set; }
public override string ToString() => Name;
}
Master model
Which is used to return data from two methods which could return three classes rather than one although it makes sense to return just one instance of a class.
public class ReferenceTables
{
public List<Categories> CategoriesList { get; set; } = new List<Categories>();
public List<ContactType> ContactTypesList { get; set; } = new List<ContactType>();
public List<Countries> CountriesList { get; set; } = new List<Countries>();
}
Connection string
First off, the connection string is stored in appsettings.json and read via a NuGet package ConfigurationLibrary.
Convention method to read multiple tables
The key is that the command object can handle multiple SELECT statements as done with the following. These statements are used for the following code sample and two other code samples.
- await using var reader = await cmd.ExecuteReaderAsync(); reads the data.
- The first while statements reads from Categories table as it is the first statement from the statements in SqlStatements.ReferenceTableStatements.
- await reader.NextResultAsync(); tells the reader to work on ContactType table
- The second while reads ContactType records
- await reader.NextResultAsync(); tells the reader to work on Countries table
- The third while reads Countries table data.
- If there are no exceptions, referenceTables has all three tables data populated, otherwise the call to this method tells the caller a failure occurred and returns the Exception which can be logged.
internal class SqlStatements
{
/// <summary>
/// Statements to read reference tables for Categories, ContactType and Countries tables.
/// </summary>
public static string ReferenceTableStatements =>
"""
SELECT CategoryID,CategoryName FROM dbo.Categories;
SELECT ContactTypeIdentifier,ContactTitle FROM dbo.ContactType;
SELECT CountryIdentifier,[Name] FROM dbo.Countries;
""";
}
public static async Task<(bool success, Exception exception)> GetReferenceTables(ReferenceTables referenceTables)
{
await using SqlConnection cn = new(ConnectionString());
await using SqlCommand cmd = new()
{
Connection = cn, CommandText = SqlStatements.ReferenceTableStatements
};
try
{
await cn.OpenAsync();
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
referenceTables.CategoriesList.Add(new Categories()
{
CategoryId = reader.GetInt32(0),
CategoryName = reader.GetString(1)
});
}
await reader.NextResultAsync();
while (await reader.ReadAsync())
{
referenceTables.ContactTypesList.Add(new ContactType()
{
ContactTypeIdentifier = reader.GetInt32(0),
ContactTitle = reader.GetString(1)
});
}
await reader.NextResultAsync();
while (await reader.ReadAsync())
{
referenceTables.CountriesList.Add(new Countries()
{
CountryIdentifier = reader.GetInt32(0),
Name = reader.GetString(1)
});
}
return (true, null);
}
catch (Exception localException)
{
return (false, localException);
}
}
DataSet method to read multiple tables
In this sample, the same SQL statement is feed to the command object as done in the prior example, a SqlDataAdapter is linked to the command object which populates all three DataTables in a DataSet.
The adapter internally populates the three tables with a private method FillInternal.
public static async Task<(bool success, Exception exception, DataSet dataSet)> GetReferenceTablesDataSet()
{
DataSet ds = new();
try
{
SqlDataAdapter adapter = new();
await using SqlConnection cn = new(ConnectionString());
SqlCommand command = new(SqlStatements.ReferenceTableStatements, cn);
adapter.SelectCommand = command;
adapter.Fill(ds);
ds.Tables[0].TableName = "Categories";
ds.Tables[1].TableName = "ContactType";
ds.Tables[2].TableName = "Countries";
return (true, null, ds);
}
catch (Exception localException)
{
return (false, localException, null);
}
}
Dapper method to read multiple tables
Using Dapper is the most efficient method, no command obect required as per below, instead we use QueryMultipleAsync to read data using the same SQL statements in the last two samples.
Once QueryMultipleAsync has executed the list are populated and available as in the first sample.
public static async Task GetReferenceTablesDapper(ReferenceTables referenceTables)
{
await using SqlConnection cn = new(ConnectionString());
SqlMapper.GridReader results = await cn.QueryMultipleAsync(SqlStatements.ReferenceTableStatements);
referenceTables.CategoriesList = results.Read<Categories>().ToList();
referenceTables.ContactTypesList = results.Read<ContactType>().ToList();
referenceTables.CountriesList = results.Read<Countries>().ToList();
}
Program.cs
Each sample is broken down into separate methods but data is not displayed. Place a break point at the end of each method and view data in the local window.
using GitHubSamples.Classes;
namespace GitHubSamples;
internal partial class Program
{
static async Task Main(string[] args)
{
await StandardSample();
await DataSetSample();
await DapperSample();
AnsiConsole.MarkupLine("[yellow]Press ENTER to exit[/]");
Console.ReadLine();
}
private static async Task StandardSample()
{
ReferenceTables referenceTables = new();
var (success, exception) = await DataOperations.GetReferenceTables(referenceTables);
Console.WriteLine(success
? "Success reading to classes"
: $"Class operation failed with \n{exception.Message}");
}
private static async Task DapperSample()
{
ReferenceTables referenceTables = new();
await DataOperations.GetReferenceTablesDapper(referenceTables);
}
private static async Task DataSetSample()
{
var (success, exception, dataSet) = await DataOperations.GetReferenceTablesDataSet();
Console.WriteLine(success
? "Success reading to DataSet"
: $"DataSet operation failed with \n{exception.Message}");
}
}
Example for the Dapper sample
Stored procedure sample
Some developer rather using stored procedures, Dapper makes this easy.
Some benefits
- Execution Plan Retention and Reuse (SP are compiled and their execution plan is cached and used again to when the same SP is executed again)
- Network bandwidth conservation
- Improved security
- Sharing of application logic between applications
Some disadvantages
- Difficult to debug
- A DBA may be required to access the SQL and write a better stored procedure. This will automatically incur added cost.
- Complex stored procedures will not always port to upgraded versions of the same database. This is specially true in case of moving from one database type(Oracle) to another database type(MS SQL Server).
I have worked for several agencies/companies were the security was so tight a developer could not even see a stored procedure definition, that made it difficult to debug. Working as a contractor with pre-existing stored procedures can be a nightmare.
Here is the Stored Procedure
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
CREATE PROCEDURE dbo.usp_SelectCatCountryContactType
AS
BEGIN
SELECT CategoryID,
CategoryName
FROM dbo.Categories;
SELECT ContactTypeIdentifier,
ContactTitle
FROM dbo.ContactType;
SELECT CountryIdentifier,
Name
FROM dbo.Countries;
END;
GO
Tips on creating stored procedures for novice developer
- Find a good course on a web site offering courses on stored procedures and/or on Pluralsight.
- Purchase Redgate SQL-Prompt which will create stored procedures from an existing SQL statement in SSMS. My favorite features are intellisense for writing SQL, formatting of SQL, statement refactoring are just a few of the cool features in SQL-Prompt.
Code
public static async Task GetReferenceTablesDapperStoredProcedure(ReferenceTables referenceTables)
{
await using SqlConnection cn = new(ConnectionString());
SqlMapper.GridReader results = await cn.QueryMultipleAsync(
"usp_SelectCatCountryContactType",
null,
null,
null,
CommandType.StoredProcedure);
referenceTables.CategoriesList = results.Read<Categories>().ToList();
referenceTables.ContactTypesList = results.Read<ContactType>().ToList();
referenceTables.CountriesList = results.Read<Countries>().ToList();
}
Produces the same results as the first Dapper example above.
Summary
What has been presented can assist with reading two or more reference tables at once without the need for multiple connection and command objects using multiple data readers and best of all for some an introduction into using Dappper.
NuGet packages
- ConfigurationLibrary for reading connection strings from appsetting when using Windows forms, Consol project where a developer is not using dependency injection.
- Dapper is an open-source object-relational mapping (ORM) library for .NET and .NET Core applications. The library allows developers quickly and easily access data from databases without the need to write tedious code.
- Microsoft.Data.SqlClient namespace is the .NET Data Provider for SQL Server.
Source code
In the following project which can be cloned from the following GitHub repository.
See also
The art of Deconstructing which explains how in this article data is returned to methods in Program.cs
南来地,北往的,上班的,下岗的,走过路过不要错过!
======================个性签名=====================
之前认为Apple 的iOS 设计的要比 Android 稳定,我错了吗?
下载的许多客户端程序/游戏程序,经常会Crash,是程序写的不好(内存泄漏?刚启动也会吗?)还是iOS本身的不稳定!!!
如果在Android手机中可以简单联接到ddms,就可以查看系统log,很容易看到程序为什么出错,在iPhone中如何得知呢?试试Organizer吧,分析一下Device logs,也许有用.