它提供了3个助手:
执行查询并将结果映射到强类型列表
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);
执行查询并将结果映射到动态对象列表
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);
执行一个不返回结果的命令
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);
多次执行命令
相同的签名还允许您多次方便高效地执行命令(例如批量加载数据)
示例:
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 rows inserted: "1,1", "2,2" and "3,3"
这适用于任何实现IEnumerable<T>
的参数 T
Performance
Dapper的一个主要特点是性能。 以下度量标准显示对数据库执行500条SELECT语句需要多长时间,并将返回的数据映射到对象。
性能测试分为3个列表:
- 用于支持从数据库中提取静态类型对象的框架的POCO序列化。 使用原始的SQL.
- 支持返回动态对象列表的框架的动态序列化.
- 典型的框架使用。 通常典型的框架使用与最佳使用性能明显不同。 通常它不会涉及编写SQL.
SELECT映射超过500次迭代的性能 - POCO序列化
Method | Duration | Remarks |
---|---|---|
Hand coded (using a SqlDataReader ) | 47ms | |
Dapper ExecuteMapperQuery | 49ms | |
ServiceStack.OrmLite (QueryById) | 50ms | |
PetaPoco | 52ms | Can be faster |
BLToolkit | 80ms | |
SubSonic CodingHorror | 107ms | |
NHibernate SQL | 104ms | |
Linq 2 SQL ExecuteQuery | 181ms | |
Entity framework ExecuteStoreQuery | 631ms |
SELECT映射超过500次迭代的性能 - 动态序列化
Method | Duration | Remarks |
---|---|---|
Dapper ExecuteMapperQuery (dynamic) | 48ms | |
Massive | 52ms | |
Simple.Data | 95ms |
SELECT映射超过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 |
参数化查询
参数作为匿名类传入。 这使您可以轻松地为参数命名,并使您能够简单地剪切并粘贴SQL代码片段并在Query分析器中运行它们。
new {A = 1, B = "b"} // A将被映射到参数@A,B到参数@B
列表支持
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支持bool和数字类型的文字替换
connection.Query("select * from User where UserId = {=Id}", new {Id = 1}));
The literal replacement is not sent as a parameter; this allows better plans and filtered index usage but should usually be used sparingly and after testing. This feature is particularly useful when the value being injected
is actually a fixed value (for example, a fixed “category id”, “status code” or “region” that is specific to the query). For live data where you are considering literals, you might also want to consider and test provider-specific query hints like OPTIMIZE FOR UNKNOWN
with regular parameters.
字面替换不作为参数发送; 这允许更好的计划和过滤的索引使用,但通常应该谨慎使用和测试之后。 这个特性在被注入的值时特别有用,实际上是固定的值(例如,固定的“category id”,“status code”或“region”,特定于查询)。 对于考虑使用文本的live数据,您可能会also想要考虑并测试特定于提供者的查询提示,例如 OPTIMIZE FOR UNKNOWN
与常规参数。
有缓冲与无缓冲的读
Dapper的默认行为是执行你的sql并缓存整个读的返回。 这在大多数情况下是理想的,因为它可以最大限度地减少数据库中的共享锁,并减少数据库网络时间。
但是,当执行巨大的查询时,您可能需要最大限度地减少内存占用,并只根据需要加载对象。为此,将缓冲
:false传递给Query
方法。
多映射
Dapper允许您将单个行映射到多个对象。 如果你想避免多余的查询和负载关联,这是一个关键的功能。
示例:
考虑两个类:Post
和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; }
}
现在让我们说,我们想要映射一个连接帖子和用户表的查询。 到目前为止,如果我们需要结合2个查询的结果,我们需要一个新的对象来表达它,但是在这种情况下,把User
对象放在Post
对象内更有意义。
这是多映射的用户案例。 你告诉Dapper,查询返回一个Post和一个User对象,然后给它一个函数来描述你想对每个包含Post
和User
对象的行做什么。 在我们的例子中,我们想把user对象放到post对象中。 所以我们写这个函数:
(post, user) => { post.Owner = user; return post; }
Query
方法的3个参数指定了dapper应该使用哪些对象来反序列化行,以及返回的内容。 我们将把这两行解释为Post
和User
的组合,然后我们返回一个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 is able to split the returned row by making an assumption that your Id columns are named Id
or id
. If your primary key is different or you would like to split the row at a point other than Id
, use the optional splitOn
parameter.
Dapper能够通过假设您的Id列被命名为Id
或id
来分割返回的行。 如果您的主键不同,或者您想在Id
以外的位置拆分该行,请使用可选的splitOn
参数。
多个结果
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完全支持存储过程:
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");
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
是至关重要的。
每行切换类型
Usually you’ll want to treat all rows from a given table as the same data type. However, there are some circumstances where it’s useful to be able to parse different rows as different data types. This is where IDataReader.GetRowParser
comes in handy.
Imagine you have a database table named “Shapes” with the columns: Id
, Type
, and Data
, and you want to parse its rows into Circle
, Square
, or Triangle
objects based on the value of the Type column.
通常情况下,您会希望将给定表中的所有行视为相同的数据类型。 但是,在某些情况下,能够将不同的行解析为不同的数据类型是有用的。 这是IDataReader.GetRowParser
派上用场的地方。
想象一下,你有一个名为Shapes
的数据库表,其中包含Id
, Type
和 Data
这些列,并且你想要将它的行分析成Circle
, Square
或Triangle
对象,基于Type
列的值。
var shapes = new List<IShape>();
using (var reader = connection.ExecuteReader("select * from Shapes"))
{
//为您所期望的每种类型生成一个行解析器。
//泛型类型<IShape>是解析器将返回的内容。
//参数 (typeof(*)))是要解析的具体类型。
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);
}
}
限制和警告
Dapper缓存每个查询的信息,这使得它能够快速实现对象并快速处理参数。 当前实现将这些信息缓存在一个ConcurrentDictionary
对象中。 它存储的对象从不刷新。 如果您不使用参数即时生成SQL字符串,则可能会遇到内存问题。 我们可能会将字典转换为LRU缓存。
Dapper的简单性意味着ORM随附的许多功能都被剥离出来。 它担心95%的情况,并为您提供大部分时间需要的工具。 它不会试图解决所有问题。
Dapper会与我的数据库提供商合作吗?
Dapper没有数据库特定的实现细节,它适用于所有.NET ADO提供者,包括SQLite, SQL CE, Firebird, Oracle, MySQL, PostgreSQL 和SQL Server。