跳槽了,新公司的數據庫層,準確地說,數據庫層和持久層使用 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 });
dog.Count()
.IsEqualTo(1);
dog.First().Age
.IsNull();
dog.First().Id
.IsEqualTo(guid);
public static IEnumerable<dynamic> Query (this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)
該方法將執行 SQL,并返回一個動態 list,即 var 變量。
用法:
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);
rows[0] 這種訪問方式會出錯,不知道示例是怎么給的~
public static int Execute(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null)
用法:
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 })
.IsEqualTo(2);
相同的簽名也可以讓你方便高效地對一個命令執行多次,例如批量加載數據(bulk-load data)。
用法:
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 } }
).IsEqualTo(3); // 3 rows inserted: "1,1", "2,2" and "3,3"
對任何類型實現 T 為 IEnumerable 的參數都可以執行。
Dapper 的主要特點是性能。以下數據顯示對一個數據庫執行 SELECT 出 500 條,并把數據映射到對象中需要多長時間。
性能測試分為三個方面:
方法 | 持續時間(毫秒) | 備注 |
Hand coded (using a SqlDataReader) | 47 | Can be faster |
Dapper ExecuteMapperQuery | 49 | 同上 |
ServiceStack.OrmLite (QueryById) | 50 | 同上 |
PetaPoco | 52 | 同上 |
BLToolkit | 80 | 同上 |
SubSonic CodingHorror | 107 | 同上 |
NHibernate SQL | 104 | 同上 |
Linq 2 SQL ExecuteQuery | 181 | 同上 |
Entity framework ExecuteStoreQuery | 631 | 同上 |
方法 | 持續時間(毫秒) | 備注 |
Dapper ExecuteMapperQuery (dynamic) | 48 | |
Massive | 52 | |
Simple.Data | 95 |
方法 | 持續時間(毫秒) | 備注 |
Linq 2 SQL CompiledQuery | 81 | Not super typical involves complex code |
NHibernate HQL | 118 | |
Linq 2 SQL | 559 | |
Entity framework | 859 | |
SubSonic ActiveRecord.SingleOrDefault | 3619 |
參數可以作為匿名類來傳遞。這使你可以輕松地給命名參數,只要簡單地剪切和粘貼 SQL 片斷,并在查詢分析器中執行即可。
new {A = 1, B = "b"} // A will be mapped to the param @A, B to the param @B
Dapper 運行讓你傳遞 IEnumerable,自動地參數化的查詢。
例如下面 SQL 的 in 查詢:
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 的默認動作是執行 SQL 并在返回時緩沖整個 reader。在大多數情況下,這是理想的,因為它能最大限度地減少數據庫中的共享鎖,以及減少數據庫的網絡時間。
但是,在執行龐大查詢時,你可能為了減少內存的占用,只加載需要的對象。要做到這點,通過緩沖到查詢方法中。
/// <summary>
/// NonBuffered
/// 將會拋出異常,Invalid attempt to Read when reader is closed.
/// </summary>
public static void TestBasicStringUsageAsyncNonBuffered()
{
var query = DbHelp.QueryAsync<string>(new CommandDefinition("select 'abc' as Value union all select @txt", new { txt = "def" }, flags: CommandFlags.None));
var arr = query.Result.ToArray();
arr.IsSequenceEqualTo(new[] { "abc", "def" });
}
/// <summary>
/// Buffered
/// 不會拋出異常
/// </summary>
public static void TestBasicStringUsageAsyncBuffered()
{
var query = DbHelp.QueryAsync<string>(new CommandDefinition("select 'abc' as Value union all select @txt", new { txt = "def" }, flags: CommandFlags.Buffered));
var arr = query.Result.ToArray();
arr.IsSequenceEqualTo(new[] { "abc", "def" });
}
/// <summary>
/// Pipelined
/// 將會拋出異常,Invalid attempt to Read when reader is closed.
/// </summary>
public static void TestBasicStringUsageAsyncPipelined()
{
var query = DbHelp.QueryAsync<string>(new CommandDefinition("select 'abc' as Value union all select @txt", new { txt = "def" }, flags: CommandFlags.Pipelined));
var arr = query.Result.ToArray();
arr.IsSequenceEqualTo(new[] { "abc", "def" });
}
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'參數。
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).First();}}}
如果你想要更靈活的操作,可以這樣做:
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 Strings 和 varchar
Dapper 支持 varchar 參數,如果你在一個 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 執行的每個查詢的緩存信息,使得它能夠快速地物化對象和處理參數。當前的實現把信息緩存在一個 ConcurrentDictionary 對象中。它存儲的對象永遠不會被刷新。如果你生成的 SQL 字符串沒有使用參數,那么可能會出現命中內存問題。我們把字典轉換成 LRU(Least Recently Used)緩存。
ORM 的很多特點都被 Dapper 去掉了,沒有身份地圖(Identity Map),沒有更新/選擇的助手等。
Dapper 不會管理你連接的生命周期,它假定它得到的連接是打開的,并且不存在 DataReader 枚舉(除非啟用 MARS)。
什么是 Mars?它是在創建數據庫連接時指定的,下面是 Dapper 中連接 SQL Server 的示例:
public static SqlConnection GetOpenConnection(bool mars = false)
{
var cs = connectionString;
if (mars)
{
SqlConnectionStringBuilder scsb = new SqlConnectionStringBuilder(cs);
scsb.MultipleActiveResultSets = true;
cs = scsb.ConnectionString;
}
var connection = new SqlConnection(cs);
connection.Open();
return connection;
}
如果指定了 Mars,那么還會創建 SqlConnectionStringBuilder,并指定其 MultipleActiveResultSets 屬性為 true。不過,看 Dapper 的例子,貌似 SQL Server 是有 Mars 的,但 MySQL 沒有。
Dapper 能運行在所有 .net ado 提供者上,包括 sqlite,sqlce,firebird,Oracle,MySQL,PostgreSQL 和 SQL Server。
Dapper 在測試項目中有完整的測試套件。
目前使用 Dapper 的有 Stack Overflow 和 helpdesk。
(if you would like to be listed here let me know)
下載 Demo
新聞熱點
疑難解答