位于https://github.com/dapperlib/dapper/releases
myget预释放提要:https://www.myget.org/gallery/dapper
包裹 | nuget稳定 | Nuget预发行 | 下载 | 迈特 |
---|---|---|---|---|
Dapper | ||||
dapper.entityframework | ||||
dapper.entityframework.strongname | ||||
dapper.rainbow | ||||
dapper.sqlbuilder | ||||
dapper.strongname |
包装目的:
Dapper最初是为堆栈溢出而开发的,但是f/oss。欢迎和邀请赞助 - 请参阅页面顶部的赞助商链接。非常感谢所有赞助Dapper的人(个人或组织),但特别感谢:
Dapper是一个Nuget库,您可以将其添加到项目中,可以通过DbConnection
实例上的扩展方法来增强您的ado.net连接。这为调用SQL提供了简单有效的API,并支持同步和异步数据访问,并允许缓冲和非缓冲查询。
它提供了多个助手,但是关键的API是:
// insert/update/delete etc
var count = connection . Execute ( sql [ , args ] ) ;
// multi-row query
IEnumerable < T > rows = connection . Query < T > ( sql [ , args ] ) ;
// single-row query ({Single|First}[OrDefault])
T row = connection . QuerySingle < T > ( sql [ , args ] ) ;
args
可以在哪里(除其他事项):
Dictionary<string,object>
DynamicParameters
实例 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 ) ;
此方法将执行SQL并返回动态列表。
示例用法:
var rows = connection . Query ( " select 1 A, 2 B union all select 3, 4 " ) . AsList ( ) ;
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 ) ;
示例用法:
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"
当您已经有现有集合时,另一个示例用法:
var foos = new List < Foo >
{
{ new Foo { A = 1 , B = 1 } }
{ new Foo { A = 2 , B = 2 } }
{ new Foo { A = 3 , B = 3 } }
} ;
var count = connection . Execute ( @"insert MyTable(colA, colB) values (@a, @b)" , foos ) ;
Assert . Equal ( foos . Count , count ) ;
这适用于某些T的任何IEnumerable<T>
。
Dapper的一个关键功能是性能。以下指标表明,针对DB执行SELECT
语句需要多长时间(以各种配置,每个标记为标记),并将返回的数据映射到对象。
基准可以在dapper.tests.smormance中找到(欢迎贡献!),可以通过:
dotnet run --project . b enchmarks D apper.Tests.Performance -c Release -f net8.0 -- -f * --join
最新运行的输出是:
BenchmarkDotNet v0.13.7, Windows 10 (10.0.19045.3693/22H2/2022Update)
Intel Core i7-3630QM CPU 2.40GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores
.NET SDK 8.0.100
[Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX
ShortRun : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX
ORM | 方法 | 返回 | 意思是 | stddev | 错误 | Gen0 | Gen1 | Gen2 | 分配 |
---|---|---|---|---|---|---|---|---|---|
Dapper Cache撞击 | 执行eparameters_cache | 空白 | 96.75美国 | 0.668美国 | 1.010美国 | 0.6250 | - | - | 2184 b |
Dapper Cache撞击 | QUERYFIRSTPARAMETERS_CACHE | 空白 | 96.86美国 | 0.493美国 | 0.746美国 | 0.8750 | - | - | 2824 b |
手工编码 | SQLCommand | 邮政 | 119.70美国 | 0.706美国 | 1.067美国 | 1.3750 | 1.0000 | 0.1250 | 7584 b |
手工编码 | 数据表 | 动态的 | 126.64美国 | 1.239美国 | 1.873美国 | 3.0000 | - | - | 9576 b |
sqlmarshal | SQLCommand | 邮政 | 132.36美国 | 1.008美国 | 1.523美国 | 2.0000 | 1.0000 | 0.2500 | 11529 b |
Dapper | QUERYFIRSTORDEFAULT | 邮政 | 133.73美国 | 1.301美国 | 2.186美国 | 1.7500 | 1.5000 | - | 11608 b |
强大 | 询问 | 动态的 | 133.92我们 | 1.075美国 | 1.806美国 | 2.0000 | 1.7500 | - | 12710 b |
linq到DB | 询问 | 邮政 | 134.24美国 | 1.068美国 | 1.614美国 | 1.7500 | 1.2500 | - | 10904 b |
repodb | ExecuteQuery | 邮政 | 135.83美国 | 1.839美国 | 3.091我们 | 1.7500 | 1.5000 | - | 11649 b |
Dapper | “查询(缓冲)” | 邮政 | 136.14美国 | 1.755美国 | 2.653美国 | 2.0000 | 1.5000 | - | 11888 b |
强大 | 询问 | 邮政 | 137.96美国 | 1.485美国 | 2.244美国 | 2.2500 | 1.2500 | - | 12201 b |
Dapper | QUERYFIRSTORDEFAULT | 动态的 | 139.04美国 | 1.507我们 | 2.279美国 | 3.5000 | - | - | 11648 b |
强大 | 单fromquery | 动态的 | 139.74美国 | 2.521我们 | 3.811我们 | 2.0000 | 1.7500 | - | 12710 b |
Dapper | “查询(缓冲)” | 动态的 | 140.13美国 | 1.382我们 | 2.090美国 | 2.0000 | 1.5000 | - | 11968 b |
ServiceStack | 单侧 | 邮政 | 140.76美国 | 1.147美国 | 2.192我们 | 2.5000 | 1.2500 | 0.2500 | 15248 b |
Dapper | “贡献” | 邮政 | 141.09美国 | 1.394美国 | 2.108美国 | 2.0000 | 1.5000 | - | 12440 b |
强大 | 单fromquery | 邮政 | 141.17美国 | 1.941美国 | 2.935美国 | 1.7500 | 1.5000 | - | 12201 b |
大量的 | “查询(动态)” | 动态的 | 142.01美国 | 4.957我们 | 7.494美国 | 2.0000 | 1.5000 | - | 12342 b |
linq到DB | “第一(编译)” | 邮政 | 144.59美国 | 1.295美国 | 1.958美国 | 1.7500 | 1.5000 | - | 12128 b |
repodb | Queryfield | 邮政 | 148.31美国 | 1.742我们 | 2.633美国 | 2.0000 | 1.5000 | 0.5000 | 13938 b |
规范 | '阅读<>(元组)' | valuetuple`8 | 148.58美国 | 2.172我们 | 3.283美国 | 2.0000 | 1.7500 | - | 12745 b |
规范 | 'read <()>(命名元组)' | valuetuple`8 | 150.60美国 | 0.658美国 | 1.106我们 | 2.2500 | 2.0000 | 1.2500 | 14562 b |
repodb | 询问 | 邮政 | 152.34美国 | 2.164美国 | 3.271我们 | 2.2500 | 1.5000 | 0.2500 | 14106 b |
repodb | Querydynamic | 邮政 | 154.15美国 | 4.108我们 | 6.210我们 | 2.2500 | 1.7500 | 0.5000 | 13930 b |
repodb | 查询某个地方 | 邮政 | 155.90美国 | 1.953美国 | 3.282我们 | 2.5000 | 0.5000 | - | 14858 b |
Dapper Cache撞击 | 执行原核心_nocache | 空白 | 162.35美国 | 1.584美国 | 2.394美国 | - | - | - | 760 b |
Dapper Cache撞击 | 执行原始核心_Cache | 空白 | 162.42我们 | 2.740美国 | 4.142我们 | - | - | - | 760 b |
Dapper Cache撞击 | QUERYFIRSTNOPARAMETERS_CACHE | 空白 | 164.35美国 | 1.206美国 | 1.824美国 | 0.2500 | - | - | 1520 b |
devexpress.xpo | FindObject | 邮政 | 165.87美国 | 1.012我们 | 1.934美国 | 8.5000 | - | - | 28099 b |
Dapper Cache撞击 | QUERYFIRSTNOPARAMETERS_NOCACHE | 空白 | 173.87美国 | 1.178美国 | 1.781美国 | 0.5000 | - | - | 1576 b |
linq到DB | 第一的 | 邮政 | 175.21我们 | 2.292我们 | 3.851美国 | 2.0000 | 0.5000 | - | 14041 b |
EF 6 | sqlquery | 邮政 | 175.36美国 | 2.259美国 | 3.415美国 | 4.0000 | 0.7500 | - | 24209 b |
规范 | '读<>(class)' | 邮政 | 186.37美国 | 1.305美国 | 2.496我们 | 3.0000 | 0.5000 | - | 17579 b |
devexpress.xpo | getObjectByKey | 邮政 | 186.78美国 | 3.407我们 | 5.151我们 | 4.5000 | 1.0000 | - | 30114 b |
Dapper | “查询(未掩盖)” | 动态的 | 194.62美国 | 1.335美国 | 2.019我们 | 1.7500 | 1.5000 | - | 12048 b |
Dapper | “查询(未掩盖)” | 邮政 | 195.01美国 | 0.888美国 | 1.343美国 | 2.0000 | 1.5000 | - | 12008 b |
devexpress.xpo | 询问 | 邮政 | 199.46美国 | 5.500美国 | 9.243美国 | 10.0000 | - | - | 32083 b |
贝尔格莱德 | firstordEfault | 任务`1 | 228.70美国 | 2.181我们 | 3.665美国 | 4.5000 | 0.5000 | - | 20555 b |
EF核心 | “第一(编译)” | 邮政 | 265.45美国 | 17.745美国 | 26.828美国 | 2.0000 | - | - | 7521 b |
NHIBERNATE | 得到 | 邮政 | 276.02美国 | 8.029我们 | 12.139美国 | 6.5000 | 1.0000 | - | 29885 b |
NHIBERNATE | HQL | 邮政 | 277.74美国 | 13.032美国 | 19.703美国 | 8.0000 | 1.0000 | - | 31886 b |
NHIBERNATE | 标准 | 邮政 | 300.22美国 | 14.908美国 | 28.504美国 | 13.0000 | 1.0000 | - | 57562 b |
EF 6 | 第一的 | 邮政 | 310.55美国 | 27.254美国 | 45.799美国 | 13.0000 | - | - | 43309 b |
EF核心 | 第一的 | 邮政 | 317.12美国 | 1.354美国 | 2.046我们 | 3.5000 | - | - | 11306 b |
EF核心 | sqlquery | 邮政 | 322.34美国 | 23.990美国 | 40.314美国 | 5.0000 | - | - | 18195 b |
NHIBERNATE | SQL | 邮政 | 325.54美国 | 3.937我们 | 7.527美国 | 22.0000 | 1.0000 | - | 80007 b |
EF 6 | “第一个(无跟踪)” | 邮政 | 331.14美国 | 27.760美国 | 46.649美国 | 12.0000 | 1.0000 | - | 50237 b |
EF核心 | “第一个(无跟踪)” | 邮政 | 337.82美国 | 27.814美国 | 46.740美国 | 3.0000 | 1.0000 | - | 17986 b |
NHIBERNATE | linq | 邮政 | 604.74美国 | 5.549我们 | 10.610美国 | 10.0000 | - | - | 46061 b |
Dapper Cache撞击 | 执行eparameters_nocache | 空白 | 623.42美国 | 3.978美国 | 6.684美国 | 3.0000 | 2.0000 | - | 10001 b |
Dapper Cache撞击 | QUERYFIRSTPARAMETERS_NOCACHE | 空白 | 630.77美国 | 3.027我们 | 4.576我们 | 3.0000 | 2.0000 | - | 10640 b |
请随时提交包含其他ORM的补丁 - 运行基准测试时,请确保在发行版中进行编译,而不要附加调试器( CTRL + F5 )。
另外,您可能更喜欢Frans Bouma的RawDataAccessbencher测试套件或Ormbenchmark。
参数通常作为匿名类传递。这使您可以轻松地命名参数,并使您能够简单地剪切和剪切SQL摘要并将其运行到DB平台的查询分析仪中。
new { A = 1 , B = " b " } // A will be mapped to the param @A, B to the param @B
参数也可以使用DynamicParameters类动态构建。这允许构建动态SQL语句,同时仍使用参数以进行安全性和性能。
var sqlPredicates = new List < string > ( ) ;
var queryParams = new DynamicParameters ( ) ;
if ( boolExpression )
{
sqlPredicates . Add ( " column1 = @param1 " ) ;
queryParams . Add ( " param1 " , dynamicValue1 , System . Data . DbType . Guid ) ;
} else {
sqlPredicates . Add ( " column2 = @param2 " ) ;
queryParams . Add ( " param2 " , dynamicValue2 , System . Data . DbType . String ) ;
}
DynamicParameters还支持从不同类型的现有对象复制多个参数。
var queryParams = new DynamicParameters ( objectOfType1 ) ;
queryParams . AddDynamicParams ( objectOfType2 ) ;
当实现传递到Execute
或Query
函数的IDynamicParameters
接口的对象时,将通过此接口提取参数值。显然,最有可能用于此目的的对象类是内置的DynamicParameters
类。
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支持布尔和数字类型的字面替代。
connection . Query ( " select * from User where UserTypeId = {=Admin} " , new { UserTypeId . Admin } ) ;
文字替换不是作为参数发送的;这允许更好的计划和过滤索引使用情况,但通常应在测试后谨慎使用。当注入的值实际上是一个固定值时(例如,固定的“类别ID”,“状态代码”或“区域”特定于查询)时,此功能特别有用。对于您正在考虑文字的实时数据,您可能还需要考虑和测试特定于特定于提供商的查询提示,例如使用常规参数OPTIMIZE FOR UNKNOWN
。
Dapper的默认行为是在返回时执行您的SQL并缓冲整个读者。在大多数情况下,这是理想的选择,因为它可以最大程度地减少数据库中的共享锁,并在DB网络时间缩短。
但是,执行大量查询时,您可能需要最大程度地减少内存足迹,而仅根据需要加载对象。为此, buffered: false
到Query
方法中。
Dapper允许您将一行映射到多个对象。如果您想避免其他查询和急切的负载关联,这是一个关键功能。
例子:
考虑2类: 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
对象的每个行要做什么。在我们的情况下,我们希望将用户对象放在邮政对象中。因此,我们编写功能:
( 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可以通过假设您的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完全支持存储的Procs:
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支持varchar params,如果您使用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至关重要。
通常,您需要将来自给定表的所有行视为相同的数据类型。但是,在某些情况下,能够将不同的行分解为不同的数据类型很有用。这就是IDataReader.GetRowParser
派上用场的地方。
想象一下,您的数据库表名为“形状”,带有列: Id
, Type
和Data
,并且要根据类型列的值将其行分解为Circle
, Square
或Triangle
对象。
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变量与MySQL连接器一起使用,您必须在连接字符串中添加以下选项:
Allow User Variables=True
确保您不会为Dapper提供用于映射的属性。
Dapper缓存了有关其运行的每个查询的信息,这使其可以快速实现对象并快速处理参数。当前的实现将此信息缓存在ConcurrentDictionary
对象中。通常将一次使用的语句从此缓存中通常冲洗掉。不过,如果您在不使用参数的情况下即时生成SQL字符串,则可能会遇到内存问题。
Dapper的简单性意味着Orms发货的许多功能都被剥离了。它担心95%的情况,并为您提供大部分时间所需的工具。它不会试图解决所有问题。
DAPPER没有DB特定的实现详细信息,它在所有.NET ADO提供商中都起作用,包括SQLITE,SQL CE,Firebird,Oracle,MariaDB,MySQL,MySQL,PostgreSQL和SQL Server。
Dapper在测试项目中具有全面的测试套件。
Dapper在堆栈溢出处的生产使用。