簡介
Dapper
是由 Stack?Overflow
團隊開發的一個簡單、高性能的微型 ORM(Object?Relational Mapper)
,僅幾千行代碼,依賴于 ADO.NET
的 IDbConnection
,通過動態生成 IL
來映射結果到實體對象。
與 EF、NHibernate
這類全功能 ORM
相比,Dapper
提供了更直接、更接近 SQL
的操作方式,性能非常接近手寫 ADO.NET
。
基本用法
安裝與配置
dotnet add package Dapper
dotnet add package Dapper.Contrib
dotnet add package System.Data.SqlClient # SQL Server
打開連接
using (var conn = new SqlConnection(connectionString))
{conn.Open();// … 執行 Dapper 操作
}
查詢單個實體
string sql = "SELECT Id, Name, Age FROM Users WHERE Id = @Id";
var user = conn.QueryFirstOrDefault<User>(sql, new { Id = 123 });
-
QueryFirstOrDefault<T>
:返回第一行并映射為T
,無數據時返回default(T)
。 -
還有
QuerySingle<T>、Query<T>
(返回IEnumerable<T>
)等方法。
查詢多個對象
using (var connection = new SqlConnection(connectionString))
{connection.Open();var users = connection.Query<User>("SELECT * FROM Users WHERE Age > @Age", new { Age = 18 });return users.ToList();
}
執行增刪改
string insert = "INSERT INTO Users (Name, Age) VALUES (@Name, @Age)";
int rows = conn.Execute(insert, new { Name = "Alice", Age = 28 });
Execute
:用于執行不返回結果集的SQL
,返回受影響行數。
基礎 CRUD 操作
using var connection = new SqlConnection(connectionString);// 查詢
var product = connection.QueryFirstOrDefault<Product>("SELECT * FROM Products WHERE Id = @Id", new { Id = 1 });// 插入
var newProduct = new Product { Name = "Laptop", Price = 999.99m };
var insertedId = connection.ExecuteScalar<int>("INSERT INTO Products (Name, Price) OUTPUT INSERTED.Id VALUES (@Name, @Price)",newProduct);// 更新
var rowsAffected = connection.Execute("UPDATE Products SET Price = @Price WHERE Id = @Id",new { Id = 1, Price = 899.99m });// 刪除
connection.Execute("DELETE FROM Products WHERE Id = @Id", new { Id = 10 });
關鍵方法詳解
方法 | 描述 | 示例 |
---|---|---|
Query<T> | 返回實體列表 | connection.Query<Product>("SELECT * FROM Products") |
QueryFirst<T> | 返回第一條結果(無結果拋異常) | connection.QueryFirst<Product>("SELECT ... WHERE Id=@id", new {id=1}) |
QueryFirstOrDefault<T> | 返回第一條或默認值(無結果返回 null) | connection.QueryFirstOrDefault<Product>(...) |
QuerySingle<T> | 返回單條結果(結果不唯一拋異常) | 適用于主鍵查詢 |
Execute | 執行非查詢操作(增刪改),返回受影響行數 | connection.Execute("DELETE FROM Products WHERE Id=@id", new {id=10}) |
ExecuteScalar<T> | 返回單個值(如 COUNT、SUM) | int count = connection.ExecuteScalar<int>("SELECT COUNT(*) FROM Products") |
高級查詢技術
多結果集處理
using var multi = connection.QueryMultiple("SELECT * FROM Products; SELECT * FROM Categories");var products = multi.Read<Product>();
var categories = multi.Read<Category>();
多表關聯映射(Multi?Mapping)
當一個查詢返回多張表的數據,需要映射到不同實體并組合起來時:
string sql = @"
SELECT o.Id, o.OrderDate,c.Id, c.Name
FROM Orders o
JOIN Customers c ON o.CustomerId = c.Id";var list = conn.Query<Order, Customer, Order>(sql,(order, cust) => { order.Customer = cust; return order; },splitOn: "Id");
splitOn
指定從哪個列開始分割下一張表的映射。
一對多關系映射
var sql = @"SELECT o.*, i.* FROM Orders oINNER JOIN OrderItems i ON o.Id = i.OrderIdWHERE o.CustomerId = @CustomerId";var orderDictionary = new Dictionary<int, Order>();var orders = connection.Query<Order, OrderItem, Order>(sql,(order, item) =>{if (!orderDictionary.TryGetValue(order.Id, out var existingOrder)){existingOrder = order;existingOrder.Items = new List<OrderItem>();orderDictionary.Add(existingOrder.Id, existingOrder);}existingOrder.Items.Add(item);return existingOrder;},new { CustomerId = 123 },splitOn: "Id");
Dapper.Contrib 擴展
自動 CRUD 操作
// 實體類標記
[Table("Products")]
public class Product
{[Key]public int Id { get; set; }public string Name { get; set; }public decimal Price { get; set; }
}// 自動操作
var product = connection.Get<Product>(1); // 查詢
connection.Insert(new Product { ... }); // 插入
connection.Update(product); // 更新
connection.Delete(product); // 刪除// 批量操作
connection.InsertAll(products); // 批量插入
自定義映射規則
SqlMapper.SetTypeMap(typeof(Product),new CustomPropertyTypeMap(typeof(Product),(type, columnName) => type.GetProperties().FirstOrDefault(prop => prop.GetCustomAttributes(false).OfType<ColumnAttribute>().Any(attr => attr.Name == columnName))));
性能優化技巧
參數化查詢最佳實踐
// 正確:參數化防止SQL注入
connection.Query("SELECT * FROM Users WHERE Name = @Name", new { Name = "Alice" });// 錯誤:拼接SQL風險
connection.Query($"SELECT * FROM Users WHERE Name = '{"Alice"}'");
批處理與事務
using var transaction = connection.BeginTransaction();try
{// 批量插入connection.Execute("INSERT INTO Logs (Message) VALUES (@Message)",logs.Select(l => new { l.Message }),transaction);// 批量更新connection.Execute("UPDATE Products SET Stock = Stock - @Quantity WHERE Id = @Id",orderItems.Select(i => new { i.Quantity, i.ProductId }),transaction);transaction.Commit();
}
catch
{transaction.Rollback();throw;
}
異步操作支持
public async Task<Product> GetProductAsync(int id)
{using var conn = new SqlConnection(connectionString);return await conn.QueryFirstOrDefaultAsync<Product>("SELECT * FROM Products WHERE Id = @Id", new { Id = id });
}
高級應用場景
動態查詢構建
public IEnumerable<Product> SearchProducts(ProductSearchCriteria criteria)
{var sql = new StringBuilder("SELECT * FROM Products WHERE 1=1");var parameters = new DynamicParameters();if (!string.IsNullOrEmpty(criteria.Name)){sql.Append(" AND Name LIKE @Name");parameters.Add("Name", $"%{criteria.Name}%");}if (criteria.MinPrice.HasValue){sql.Append(" AND Price >= @MinPrice");parameters.Add("MinPrice", criteria.MinPrice);}return connection.Query<Product>(sql.ToString(), parameters);
}
JSON 數據處理(SQL Server 2016+)
// 查詢JSON列
var orders = connection.Query<Order>(@"SELECT Id,JSON_VALUE(Details, '$.Customer.Name') AS CustomerName,JSON_VALUE(Details, '$.TotalAmount') AS TotalAmountFROM Orders");// 插入JSON數據
connection.Execute("INSERT INTO Orders (Details) VALUES (@Details)",new { Details = JsonConvert.SerializeObject(orderDetails) });
參數化查詢
Dapper
默認將匿名對象的屬性映射到 SQL
中的參數,杜絕 SQL
注入:
var parameters = new DynamicParameters();
parameters.Add("MinPrice", 100);
parameters.Add("MaxPrice", 500);
var products = conn.Query<Product>("SELECT * FROM Products WHERE Price BETWEEN @MinPrice AND @MaxPrice",parameters);
DynamicParameters
支持輸出參數、存儲過程參數等高級場景。
最佳實踐指南
與 EF Core 混合使用
// 復雜查詢用 Dapper
public List<ReportItem> GetSalesReport(DateTime start, DateTime end)
{return _dapperConnection.Query<ReportItem>(...);
}// 事務性操作用 EF Core
public void PlaceOrder(Order order)
{using var transaction = _dbContext.Database.BeginTransaction();try{_dbContext.Orders.Add(order);_dbContext.SaveChanges();// 調用庫存服務(Dapper)_dapperConnection.Execute(...);transaction.Commit();}catch{transaction.Rollback();throw;}
}
性能關鍵路徑優化
// 緩存查詢結果(高頻讀取)
private static readonly string ProductByIdSql = "SELECT * FROM Products WHERE Id = @Id";public Product GetProduct(int id)
{return _connection.QueryFirstOrDefault<Product>(ProductByIdSql, new { Id = id });
}// 使用存儲過程
var products = _connection.Query<Product>("usp_GetProductsByCategory", new { CategoryId = 5 }, commandType: CommandType.StoredProcedure);
Dapper 擴展生態
常用擴展庫
庫名 | 功能 | 安裝命令 |
---|---|---|
Dapper.Contrib | 基礎CRUD擴展 | Install-Package Dapper.Contrib |
Dapper.FluentMap | 高級映射配置 | Install-Package Dapper.FluentMap |
Dapper.SimpleCRUD | 自動化CRUD操作 | Install-Package Dapper.SimpleCRUD |
Dapper.SqlBuilder | 動態SQL構建 | Install-Package Dapper.SqlBuilder |
數據庫提供程序支持
// PostgreSQL
using var conn = new NpgsqlConnection(pgConnectionString);// MySQL
using var conn = new MySqlConnection(mysqlConnectionString);// SQLite
using var conn = new SQLiteConnection(sqlliteConnectionString);
Dapper 適用場景分析
推薦使用場景
-
高性能報表查詢:大數據量復雜查詢
-
批量數據處理:
ETL
管道操作 -
微服務架構:輕量級數據訪問層
-
遺留系統集成:替代原生
ADO.NET
-
讀寫分離架構:讀操作專用
不推薦場景
-
復雜領域模型:需要變更跟蹤
-
數據庫遷移需求:缺乏遷移工具
-
自動
SQL
生成:需手寫SQL
-
簡單
CRUD
應用:EF Core
更高效
總結:Dapper 核心價值
-
極致性能:接近原生
ADO.NET
的執行效率 -
精細控制:完全掌控
SQL
語句 -
輕量靈活:最小化
ORM
開銷 -
易于集成:與現有項目無縫結合
-
豐富擴展:強大的社區生態支持
存儲過程調用
var users = conn.Query<User>("sp_GetActiveUsers",new { IsActive = true },commandType: CommandType.StoredProcedure);
- 也可用
Execute、QueryMultiple
(一次取多組結果)等。
性能優勢
-
由于
Dapper
直接在運行時生成IL
來做對象映射,并且復用ADO.NET
連接與命令,對大量并發、批量查詢場景性能非常優秀。 -
對比
EF Core
,Dapper
的查詢速度常常有 2–10 倍優勢,尤其是返回扁平對象時。
與 EF 的對比
Dapper | EF Core | |
---|---|---|
編寫 SQL | 手寫、完全自定義 | LINQ 語句 |
配置映射 | 自動映射或手動配置(少量) | 屬性與 Fluent API |
性能 | 極高,接近原生 ADO.NET | 較高,但 LINQ 翻譯與變更追蹤有額外開銷 |
靈活度 | 完全掌控 SQL | 高,受限于 LINQ 表達式能力 |
學習成本 | 低 | 相對高(遷移、關系、導航屬性等) |
常見問題與最佳實踐
-
連接管理:建議每次操作單獨
using
打開連接,避免長連接導致資源占用。 -
參數重用:對于頻繁相同參數的查詢,可重用
DynamicParameters
對象,提升性能。 -
大型結果集:如果結果集很大,使用
Query<T>
時可結合AsList()
分批處理,或使用IDataReader
手動讀取。 -
分頁:可在
SQL
中直接使用OFFSET…FETCH
,或結合動態參數快速實現。 -
緩存:
Dapper
自帶簡單的SQL
緩存(IL
生成后會緩存映射),但不緩存查詢結果;如需緩存結果,可加分布式/內存緩存。