Dapper简易教程(翻译自Github上StackExchange/Dapper)

本文源自:https://github.com/cnxy/Dapper-zh-cn 

本博客作者与Github上作者(cnxy)实为同一个作者。由于笔者翻译水平有限,文本中错误难免,欢迎指正!

 

执行一个查询,并将结果映射到一个强类型集合中


注意:所有扩展方法假设连接已经打开,如果连接关闭,他们将会失败。

public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)

使用例子:

public class Dog{    public int? Age { get; set; }    public Guid Id { get; set; }    public string Name { get; set; }    public float? Weight { get; set; }    public int IgnoredProperty { get { return 1; } }}            var guid = Guid.NewGuid();var dog = connection.Query<Dog>("select Age = @Age, Id = @Id", new { Age = (int?)null, Id = guid });dog.Count()    .IsEqualTo(1);dog.First().Age    .IsNull();dog.First().Id    .IsEqualTo;

超过500次迭代的SELECT映射性能 – POCO序列化

方法 执行时间 备注
手工编码 (使用 SqlDataReader) 47ms  
Dapper ExecuteMapperQuery 49ms  
ServiceStack.OrmLite (使用Id查询) 50ms  
PetaPoco 52ms 可以更快
BLToolkit 80ms  
SubSonic CodingHorror 107ms  
NHibernate SQL 104ms  
Linq 2 SQL ExecuteQuery 181ms  
Entity framework ExecuteStoreQuery 631ms  

500次查询映射的性能-POCO序列化

Method

Duration

Remarks

Hand coded (using aSqlDataReader)

47ms

Can be faster

DapperExecuteMapperQuery

49ms

ServiceStack.OrmLite(QueryById)

50ms

PetaPoco

52ms

BLToolkit

80ms

SubSonic CodingHorror

107ms

NHibernate SQL

104ms

Linq 2 SQLExecuteQuery

181ms

Entity frameworkExecuteStoreQuery

631ms

中文教程源自:中文Dapper教程.GitBook

存储过程


Dapper完全支持存储过程

var user = cnn.Query<User>("spGetUser", new {Id = 1},         commandType: CommandType.StoredProcedure).SingleOrDefault();

如果你想做的更漂亮你可以这样:

var p = new DynamicParameters();p.Add("@a", 11);p.Add("@b", dbType: DbType.Int32, direction: ParameterDirection.Output);p.Add("@c", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);cnn.Execute("spMagicProc", p, commandType: CommandType.StoredProcedure); int b = p.Get<int>("@b");int c = p.Get<int>("@c"); 

超过500次迭代的SELECT映射性能 – 动态序列化

方法 执行时间 备注
Dapper ExecuteMapperQuery (动态) 48ms  
Massive 52ms  
Simple.Data 95ms  

执行一个查询,并将结果映射到一个动态类型集合中


该方法将执行SQL和返回一个动态集合。

public static IEnumerable<dynamic> Query (this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)

使用例子:

var rows = connection.Query("select 1 A, 2 B union all select 3, 4");((int)rows[0].A)   .IsEqualTo(1);((int)rows[0].B)   .IsEqualTo(2);((int)rows[1].A)   .IsEqualTo(3);((int)rows[1].B)    .IsEqualTo(4);

多重结果

Dapper允许在单个查询中处理多个结果。

例子:

var sql =
@"
select * from Customers where CustomerId = @id
select * from Orders where CustomerId = @id
select * from Returns where CustomerId = @id";

using (var multi = connection.QueryMultiple(sql, new {id=selectedId}))
{
   var customer = multi.Read<Customer>().Single();
   var orders = multi.Read<Order>().ToList();
   var returns = multi.Read<Return>().ToList();
   ...
}

性能


Dapper的一个关键特性是性能。以下指标显示对数据库执行500次SELECT语句并返回数据映射到对象,需要多长时间。

性能测试分为三个表格:

  • POCO序列化框架支持从数据库到静态类型化对象,通过使用原始的SQL。
  • 动态序列化框架支持返回动态对象的列表。
  • 典型的框架使用。通常典型框架使用不同于最佳的使用性能明智。通常它不会涉及编写SQL。

超过500次迭代的SELECT映射性能 – 典型用法

方法 执行时间 备注
Linq 2 SQL CompiledQuery 81ms 非典型的且不涉及复杂的代码
NHibernate HQL 118ms  
Linq 2 SQL 559ms  
Entity framework 859ms  
SubSonic ActiveRecord.SingleOrDefault 3619ms  

性能基准测试信息 点击这里.

可以任意提交包含其他ORM的补丁 –
运行基准测试时,请确保在Release中编译,且不能附加调试器 (Ctrl+F5).

或者,你可以使用Frans
Bouma的RawDataAccessBencherOrmBenchmark测试套件作为测试工具使用。

我能在我的数据库中使用吗?


Dapper没有DB具体的实现细节,它在所有工作。净ADO提供者includingSQLite、SQL
CE、火鸟、Oracle、MySQL、PostgreSQL和SQL服务器。

谁在用这个?

Stack Overflow正在使用Dapper。

500次查询映射的性能-动态类型序列化

Method

Duration

Remarks

DapperExecuteMapperQuery

48ms

Massive

52ms

Simple.Data

参数化查询

可以匿名类型作为参数进行传递,这可以轻松地命名这些参数名称,且能够在数据库平台的查询分析器中简单地使用剪切、粘贴SQL语句并运行。

new {A = 1, B = "b"} // A映射到参数@A,B映射到参数@B

缓冲和无缓冲的读者


Dapper的默认行为是执行sql和缓冲整个reader在返回。这是理想的在大多数情况下,因为它最大限度地减少共享锁在数据库和减少了数据库在网络上的时间。

然而当执行查询您可能需要减少内存占用并根据需要只加载对象。

本文翻译自:StackExchange.Dapper

特性


Dapper
是一个单独的文件,可以放进你的项目中用来扩展你的IDbConnection接口.

它提供了三个助手:

执行一个查询并将结果映射到强类型列表

public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)

示例:

public class Dog
{
    public int? Age { get; set; }
    public Guid Id { get; set; }
    public string Name { get; set; }
    public float? Weight { get; set; }

    public int IgnoredProperty { get { return 1; } }
}

var guid = Guid.NewGuid();
var dog = connection.Query<Dog>("select Age = @Age, Id = @Id", new { Age = (int?)null, Id = guid });

Assert.Equal(1,dog.Count());
Assert.Null(dog.First().Age);
Assert.Equal(guid, dog.First().Id);

多个结果


Dapper允许您处理多个结果在一个查询中.

例子:

var sql = @"select * from Customers where CustomerId = @idselect * from Orders where CustomerId = @idselect * from Returns where CustomerId = @id";using (var multi = connection.QueryMultiple(sql, new {id=selectedId})){   var customer = multi.Read<Customer>().Single();   var orders = multi.Read<Order>().ToList();   var returns = multi.Read<Return>().ToList();   ...} 

执行一个查询并将其映射到动态对象列表

public static IEnumerable<dynamic> Query (this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)

这个方法会执行SQL语句,并返回一个动态列表。

示例:

var rows = connection.Query("select 1 A, 2 B union all select 3, 4");

Assert.Equal(1, (int)rows[0].A);
Assert.Equal(2, (int)rows[0].B);
Assert.Equal(3, (int)rows[1].A);
Assert.Equal(4, (int)rows[1].B);

500次查询映射的性能-经典框架使用

Method Duration Remarks
Linq 2 SQL CompiledQuery 81ms Not super typical involves complex code
NHibernate HQL 118ms
Linq 2 SQL 559ms
Entity framework 859ms
SubSonic ActiveRecord.SingleOrDefault 3619ms

限制与警告

Dapper缓存有关它运行的每个查询的信息,这使它能够快速实现对象并快速处理参数。
当前实现将此信息缓存在ConcurrentDictionary对象中。仅使用一次的语句通常会从此缓存中刷新。尽管如此,如果您在不使用参数的情况下动态生成SQL字符串,则可能会遇到内存问题。

Dapper的简洁性意味着ORM附带的许多功能都被剥离了。Dapper担心95%的情况,并为您提供大多数时间所需的工具,并不试图解决所有问题。

多次执行一个命令


相同的占位符允许您方便有效的多次执行一个命令.

使用例子:

connection.Execute(@"insert MyTable(colA, colB) values ",    new[] { new { a=1, b=1 }, new { a=2, b=2 }, new { a=3, b=3 } }  ).IsEqualTo(3); // 3 行 被插入: "1,1", "2,2" and "3,3"

列表支持

Dapper允许将IEnumerable<int>作为传递参数,并能够自动地参数化查询

例子:

connection.Query<int>("select * from (select 1 as Id union all select 2 union all select 3) as X where Id in @Ids", new { Ids = new int[] { 1, 2, 3 } });

以上将被转换成:

select * from (select 1 as Id union all select 2 union all select 3) as X where Id in (@Ids1, @Ids2, @Ids3)" // @Ids1 = 1 , @Ids2 = 2 , @Ids2 = 3

集合参数的支持


Dapper允许您通过IEnumerable,自动化您的查询参数.

使用例子:

connection.Query<int>("select * from (select 1 as Id union all select 2 union all select 3) as X where Id in @Ids", new { Ids = new int[] { 1, 2, 3 });

将会被解析成这样:

select * from (select 1 as Id union all select 2 union all select 3) as X where Id in (@Ids1, @Ids2, @Ids3)" // @Ids1 = 1 , @Ids2 = 2 , @Ids2 = 3

Dapper – .Net版本的简单对象映射器

Ansi字符串和varchar


Dapper支持varchar
params,如果你是一个varchar列上执行一个where子句使用参数一定要通过它以这种方式:

Query<Thing>("select * from Thing where Name = @Name", new {Name = new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true });

在Sql Server上至关重要,查询时使用unicode
unicode和ansi当查询非unicode的时候。

多重映射

Dapper允许将单个行映射到多个对象。
如果想避免无关的查询和立即加载关联,这是一个很关键的特性。

例子:

思考这两个类: Post and User

class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public User Owner { get; set; }
}

class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

现在我们要把posts表单与users表单进行映射查询。到目前为止,如果我们需要结合2个查询的结果,我们需要一个新的对象来表达它,但在这种情况下将User对象放在Post对象中更有意义。

这是多重映射的用户案例。你告诉dapper查询返回一个Post和一个User对象,然后给它描述你想要对包含PostUser对象的每一行做什么的函数。
在我们的例子中,我们想要获取用户对象并将其放在post对象中。所以编写函数如下:

(post, user) => { post.Owner = user; return post; }

Query方法的3个类型参数指定dapper应该使用哪些对象及返回的内容进行反序列化行。我们将把这两行解释为PostUser的组合,然后我们返回一个Post对象。
因此类型声明变为

<Post, User, Post>

所有东西都放在一起,看起来像这样:

var sql =
@"select * from #Posts p
left join #Users u on u.Id = p.OwnerId
Order by p.Id";

var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post;});
var post = data.First();

Assert.Equal("Sams Post1", post.Content);
Assert.Equal(1, post.Id);
Assert.Equal("Sam", post.Owner.Name);
Assert.Equal(99, post.Owner.Id);

Dapper能够通过假设Id列被命名为“Id”或“id”来拆分返回的行。
如果主键不同或者希望将行拆分为“Id”以外的其他位置,请使用可选的splitOn参数。

执行一个命令,不返回结果


public static int Execute(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null)

使用例子:

connection.Execute(@"  set nocount on   create table #t   set nocount off   insert #t   select @a a union all select @b   set nocount on   drop table #t", new {a=1, b=2 })   .IsEqualTo(2);

执行不返回结果的命令

public static int Execute(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null)

示例:

var count = connection.Execute(@"
  set nocount on
  create table #t(i int)
  set nocount off
  insert #t
  select @a a union all select @b
  set nocount on
  drop table #t", new {a=1, b=2 });
Assert.Equal(2, count);

多个映射


Dapper允许单个记录映射到多个对象。这是一个关键特性,如果你想避免无关的查询和渴望加载关联的对象。

使用例子:

var sql = @"select * from #Posts p left join #Users u on u.Id = p.OwnerId Order by p.Id";var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post;});//泛型的最后一个参数是要返回的类型var post = data.First();post.Content.IsEqualTo("Sams Post1");post.Id.IsEqualTo(1);post.Owner.Name.IsEqualTo("Sam");post.Owner.Id.IsEqualTo(99);

重要注意Dapper假设您的Id列命名为“Id”或“Id”,如果你的主键不是,或你想分割宽行的“Id”,使用可选的“splitOn”参数。

每行类型转换

通常,自己希望将给定表中的所有行视为相同的数据类型。
但是,在某些情况下,能够将不同的行解析为不同的数据类型是有用的。
这就是IDataReader.GetRowParser派上用场的地方。

假设有一个名为“Shapes”的数据库表,其中包含列:IdTypeData,你想要基于Type列的值将它的行解析为CircleSquareTriangle对象。

var shapes = new List<IShape>();
using (var reader = connection.ExecuteReader("select * from Shapes"))
{
    // Generate a row parser for each type you expect.
    // The generic type <IShape> is what the parser will return.
    // The argument (typeof(*)) is the concrete type to parse.
    var circleParser = reader.GetRowParser<IShape>(typeof(Circle));
    var squareParser = reader.GetRowParser<IShape>(typeof(Square));
    var triangleParser = reader.GetRowParser<IShape>(typeof(Triangle));

    var typeColumnIndex = reader.GetOrdinal("Type");

    while (reader.Read())
    {
        IShape shape;
        var type = (ShapeType)reader.GetInt32(typeColumnIndex);
        switch (type)
        {
              case ShapeType.Circle:
                shape = circleParser(reader);
                break;
            case ShapeType.Square:
                shape = squareParser(reader);
                break;
            case ShapeType.Triangle:
                shape = triangleParser(reader);
                break;
              default:
                throw new NotImplementedException();
        }

          shapes.Add(shape);
    }
}

参数化查询


参数传递匿名类。这允许您命名参数容易和使您能够简单剪切和粘贴SQL代码片段在查询分析器和运行它们。

new {A = 1, B = "b"} // A 会被映射出参数 @A, B 对应 @B 

原版教程源自:Dapper Tutorial

限制和警告


Dapper缓存信息每个查询在它运行时,这使它迅速实现对象和工艺参数。当前的实现在ConcurrentDictionary最后对象缓存这些信息。它存储的对象不会被刷新。如果你是动态生成SQL字符串不使用参数有可能你将遇到内存问题。我们可以把词典转换为一个LRU缓存。

文字代替

Dapper支持布尔与数字类型的文字代替。

connection.Query("select * from User where UserId = {=Id}", new {Id = 1}));

文字替换不作为参数发送;
更好的计划和过滤索引用法将被允许,但通常应谨慎在测试后使用。
当注入的值实际上是固定值(例如,特定于查询的“类别ID”,“状态代码”或“区域”)时,此功能特别有用。
当你在思考文字live数据时,也有可能想到also并测试特定于提供程序的查询提示,如带有常规参数的OPTIMIZE FOR UNKNOWN

Ansi字符串和varchar

Dapper支持varchar参数,如果使用param在varchar列上执行where子句,请确保以这种方式传递它:

Query<Thing>("select * from Thing where Name = @Name", new {Name = new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true });

在SQL
Server中,使用unicode编码查询unicode与ANSI编码或查询非unicode编码时,变得至关重要。

组件

Nuget稳定版:

https://www.nuget.org/packages/Dapper

Visual Studio 程序包管理器控制台:

PM> Install-Package Dappe

特点

Dapper是一个NuGet库,您可以将其添加到项目中,以扩展您的IDbConnection接口。

它提供了3个使用方法:

中文教程PDF:dapper-tutorial-cn

Dapper支持哪些数据库?

Dapper没有特定于DB的实现细节,它适用于所有.NET
ADO提供程序,包括SQLite(https://www.sqlite.org/),SQL),SQL CE,Firebird,Oracle,MySQL,PostgreSQL和SQL
Server。

多次执行命令

还允许使用相同的参数签名方便有效地多次执行命令(例如批量加载数据)

示例:

var count = connection.Execute(@"insert MyTable(colA, colB) values (@a, @b)",
    new[] { new { a=1, b=1 }, new { a=2, b=2 }, new { a=3, b=3 } }
  );
Assert.Equal(3, count); // 插入3行: "1,1", "2,2" 与 "3,3"

这适用于已经实现IEnumerable接口的集合对象T。

发行说明

请见 stackexchange.github.io/Dapper

存储过程

Dapper完全支持存储过程:

var user = cnn.Query<User>("spGetUser", new {Id = 1},
        commandType: CommandType.StoredProcedure).SingleOrDefault();

如果你想要更有趣的东西,你可以这样做:

var p = new DynamicParameters();
p.Add("@a", 11);
p.Add("@b", dbType: DbType.Int32, direction: ParameterDirection.Output);
p.Add("@c", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);

cnn.Execute("spMagicProc", p, commandType: CommandType.StoredProcedure);

int b = p.Get<int>("@b");
int c = p.Get<int>("@c");

性能

Dapper的一个关键特性是性能。
以下度量标准显示了对DB执行500个SELECT语句并将返回的数据映射到对象所需的时间。

性能测试分为3个列表:

  • 支持从数据库中提取静态类型对象框架的POCO序列化,使用原生SQL语句。
  • 支持返回动态对象列表框架的动态序列化。
  • 典型的框架用法:通常典型的框架使用与最佳使用性能明显不同,并且它不会涉及编写SQL语句。

有完整的例子清单吗?

Dapper有一个完整位于测试工程的测试套件。

缓冲与未缓冲阅读器

Dapper的默认行为是执行SQL并在返回时缓冲整个阅读器。
在大多数情况下,这是理想的,因为它最小化了数据库中的共享锁并减少了数据库网络时间。

但是,在执行大量查询时,可能需要最小化内存占用并仅根据需要加载对象。
为此,将buffered:false传递给Query方法。