本文源自:https://github.com/cnxy/Dapper-zh-cn 本博客作者與Github上作者(cnxy)實為同一個作者。由於筆者翻譯水平有限,文本中錯誤難免,歡迎指正! 本文翻譯自:StackExchange.Dapper 原版教程源自:Dapper Tutorial 中文教程 ...
本文源自:https://github.com/cnxy/Dapper-zh-cn
本博客作者與Github上作者(cnxy)實為同一個作者。由於筆者翻譯水平有限,文本中錯誤難免,歡迎指正!
本文翻譯自:StackExchange.Dapper
原版教程源自:Dapper Tutorial
中文教程源自:中文Dapper教程.GitBook
中文教程PDF:dapper-tutorial-cn
Dapper - .Net版本的簡單對象映射器
發行說明
請見 stackexchange.github.io/Dapper
組件
Nuget穩定版:
https://www.nuget.org/packages/Dapper
Visual Studio 程式包管理器控制台:
PM> Install-Package Dappe
特點
Dapper是一個NuGet庫,您可以將其添加到項目中,以擴展您的IDbConnection
介面。
它提供了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行: "1,1", "2,2" 與 "3,3"
這適用於已經實現IEnumerable介面的集合對象T。
性能
Dapper的一個關鍵特性是性能。 以下度量標準顯示了對DB執行500個SELECT
語句並將返回的數據映射到對象所需的時間。
性能測試分為3個列表:
- 支持從資料庫中提取靜態類型對象框架的POCO序列化,使用原生SQL語句。
- 支持返回動態對象列表框架的動態序列化。
- 典型的框架用法:通常典型的框架使用與最佳使用性能明顯不同,並且它不會涉及編寫SQL語句。
超過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次迭代的SELECT映射性能 - 動態序列化
方法 | 執行時間 | 備註 |
---|---|---|
Dapper ExecuteMapperQuery (動態) |
48ms | |
Massive | 52ms | |
Simple.Data | 95ms |
超過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的RawDataAccessBencher或OrmBenchmark測試套件作為測試工具使用。
參數化查詢
可以匿名類型作為參數進行傳遞,這可以輕鬆地命名這些參數名稱,且能夠在資料庫平臺的查詢分析器中簡單地使用剪切、粘貼SQL語句並運行。
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支持布爾與數字類型的文字代替。
connection.Query("select * from User where UserId = {=Id}", new {Id = 1}));
文字替換不作為參數發送; 更好的計劃和過濾索引用法將被允許,但通常應謹慎在測試後使用。 當註入的值實際上是固定值(例如,特定於查詢的“類別ID”,“狀態代碼”或“區域”)時,此功能特別有用。 當你在思考文字live數據時,也有可能想到also並測試特定於提供程式的查詢提示,如帶有常規參數的OPTIMIZE FOR UNKNOWN
。
緩衝與未緩衝閱讀器
Dapper的預設行為是執行SQL併在返回時緩衝整個閱讀器。 在大多數情況下,這是理想的,因為它最小化了資料庫中的共用鎖並減少了資料庫網路時間。
但是,在執行大量查詢時,可能需要最小化記憶體占用並僅根據需要載入對象。 為此,將buffered:false
傳遞給Query
方法。
多重映射
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
對象,然後給它描述你想要對包含Post
和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能夠通過假設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編碼時,變得至關重要。
每行類型轉換
通常,自己希望將給定表中的所有行視為相同的數據類型。 但是,在某些情況下,能夠將不同的行解析為不同的數據類型是有用的。 這就是IDataReader.GetRowParser
派上用場的地方。
假設有一個名為“Shapes”的資料庫表,其中包含列:Id
,Type
和Data
,你想要基於Type列的值將它的行解析為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); } }
限制與警告
Dapper緩存有關它運行的每個查詢的信息,這使它能夠快速實現對象並快速處理參數。 當前實現將此信息緩存在ConcurrentDictionary
對象中。僅使用一次的語句通常會從此緩存中刷新。儘管如此,如果您在不使用參數的情況下動態生成SQL字元串,則可能會遇到記憶體問題。
Dapper的簡潔性意味著ORM附帶的許多功能都被剝離了。Dapper擔心95%的情況,併為您提供大多數時間所需的工具,並不試圖解決所有問題。
Dapper支持哪些資料庫?
Dapper沒有特定於DB的實現細節,它適用於所有.NET ADO提供程式,包括SQLite(https://www.sqlite.org/),SQL CE,Firebird,Oracle,MySQL,PostgreSQL和SQL Server。
有完整的例子清單嗎?
Dapper有一個完整位於測試工程的測試套件。
誰在用這個?
Stack Overflow正在使用Dapper。