当前位置: 首页 > news >正文

Dotnet-Dapper的用法

Dapper是一个轻量级的ORM(对象关系映射)框架,专为.NET设计。它通过扩展IDbConnection接口,使开发者能够方便地执行SQL查询,并将查询结果映射到对象模型中。类似于ADO.NET

Dapper Query - 了解如何查询数据库并返回列表

Dapper基础:CURD

查询数据

Dapper使用Query方法执行SQL查询并返回结果集。

匿名查询

只用了Query(sql),而不是Query<T>()

//查询前10条,返回第一条
string sql = "SELECT TOP 10 * FROM OrderDetails";using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{    var orderDetail = connection.Query(sql).FirstOrDefault();FiddleHelper.WriteTable(orderDetail);
}

强类型查询

使用Query<T>()

public class Student{public int Id { get; set; }public string Email { get; set; }
}using (IDbConnection db = new SqlConnection(connectionString))
{string sql = "SELECT Id, Email FROMFROM Students WHERE Email = @Email";var students = db.Query<Student>(sql,new { Email = "test@xxx.com" }).ToList();
}

查询多映射

Invoice(发票)和InvoiceDetail(发票细节)

// 发票主表实体
public class Invoice
{public int ID { get; set; } // 主键public string InvoiceNo { get; set; } // 发票编号public DateTime InvoiceDate { get; set; } // 发票日期// 其他发票字段...// 关键修正:一对多关联 → 用集合存储多条明细(原代码是单个对象,错误)public List<InvoiceDetail> InvoiceDetails { get; set; } = new List<InvoiceDetail>(); // 初始化避免空引用// 必须重写 Equals 和 GetHashCode(用于 Distinct() 去重,基于主键 InvoiceID)public override bool Equals(object obj){return obj is Invoice invoice && InvoiceID == invoice.InvoiceID;}public override int GetHashCode(){return HashCode.Combine(InvoiceID);}
}// 发票明细表实体
public class InvoiceDetail
{public int DetailID { get; set; } // 明细主键(关键!原代码可能缺少,导致 splitOn 映射错位)public int InvoiceID { get; set; } // 外键(关联发票表)public string ProductName { get; set; } // 产品名称public decimal Quantity { get; set; } // 数量public decimal UnitPrice { get; set; } // 单价// 其他明细字段...
}
string sql 
= "SELECT * FROM Invoice AS A INNER JOIN InvoiceDetail AS B ON A.InvoiceID = B.InvoiceID;";using (var connection = new SqlConnection(Connstr))
{connection.Open();//Dapper 映射:T1=Invoice(主表),T2=InvoiceDetail(从表),TReturn=Invoicevar invoices = connection.Query<Invoice, InvoiceDetail, Invoice>(sql,(invoice, invoiceDetail) =>{invoice.InvoiceDetail = invoiceDetail;return invoice;},splitOn: "DetailID").Distinct().ToList();
}

splitOn: "InvoiceID"用于分隔查询字段,赋值,从这个把查询出来的字段,左边的赋值给主表,右边的赋值给从表,例如
 

splitOn:"Aid"A.id A.name A.password   |   B.Aid B.bname B.bpasswrod .

最好不要select *,而是select 具体字段。

如果分割线选的不对可能出错例如,

SELECTA.Id, A.InvoiceNo, A.InvoiceDate,B.DetailId, -- 新增字段,放在 B.InvoiceId 前面B.InvoiceId, B.ProductName...

此时 splitOn: "InvoiceId" 会找第一个 InvoiceId,主表没有DetailId这个属性,映射直接报错。

查询多映射(1对多)

string sql = "SELECT TOP 10 * FROM Orders AS A INNER JOIN OrderDetails AS B ON A.OrderID = B.OrderID;";using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{            var orderDictionary = new Dictionary<int, Order>();var list = connection.Query<Order, OrderDetail, Order>(sql,(order, orderDetail) =>{Order orderEntry;if (!orderDictionary.TryGetValue(order.OrderID, out orderEntry)){orderEntry = order;orderEntry.OrderDetails = new List<OrderDetail>();orderDictionary.Add(orderEntry.OrderID, orderEntry);}orderEntry.OrderDetails.Add(orderDetail);return orderEntry;},splitOn: "OrderID").Distinct().ToList();Console.WriteLine(list.Count);FiddleHelper.WriteTable(list);FiddleHelper.WriteTable(list.First().OrderDetails);
}

返回的[0x123,0x123,0x134].Distinct()==[0x123,0x134].

查询单个项

QueryFirst

QuerySingle

QueryFirstOrDefault,

QuerySingleOrDefault

查询到无值,一个值,多个值的结果:

他们也支持QueryFirst(匿名),QueryFirst<T>(强类型)两种方式。

批处理 SQL 返回多个结果

QueryMultiple

以下示例演示如何在一次往返数据库执行两个 SQL 语句。在第一个查询中,我们从表中获取所有发票,在第二个查询中,我们从表中获取所有发票项目

string sql = "SELECT * FROM Invoices WHERE InvoiceID = @InvoiceID; SELECT * FROM InvoiceItems WHERE InvoiceID = @InvoiceID;";using (var connection = My.ConnectionFactory())
{connection.Open();using (var multi = connection.QueryMultiple(sql, new {InvoiceID = 1})){var invoice = multi.Read<Invoice>().First();var invoiceItems = multi.Read<InvoiceItem>().ToList();}
}

一次查询两个多结果的结果集

{List<Order> orders = new List<Order>();List<OrderDetail> allDetails = new List<OrderDetail>();// 需求:查询所有状态为“已付款”的订单 + 对应的所有明细(两个都是多结果集)string sql = @"-- 结果集1:多结果(所有已付款的订单)SELECT OrderID, OrderNo, OrderDate, Status FROM Orders WHERE Status = @Status; -- 条件:已付款-- 结果集2:多结果(这些订单对应的所有明细)SELECT DetailID, OrderID, ProductName, Quantity, UnitPrice FROM OrderDetails WHERE OrderID IN (SELECT OrderID FROM Orders WHERE Status = @Status); -- 关联主表条件";try{using (var connection = CreateConnection()){await connection.OpenAsync();// 执行多结果集查询(两个结果集都是多数据)using (var multi = await connection.QueryMultipleAsync(sql, new { Status = "已付款" })){// 1. 读取第一个多结果集:所有已付款的订单(转为列表)orders = (await multi.ReadAsync<Order>()).ToList();// 2. 读取第二个多结果集:所有关联的明细(转为列表)allDetails = (await multi.ReadAsync<OrderDetail>()).ToList();}}// 3. 核心:在内存中关联主表和从表(按 OrderID 分组匹配)// 先将明细按 OrderID 分组(key=OrderID,value=该订单的所有明细)var detailsGrouped = allDetails.GroupBy(d => d.OrderID).ToDictionary(g => g.Key, g => g.ToList());// 遍历每个订单,将对应的明细赋值给 OrderDetails 集合foreach (var order in orders){// 若该订单有对应的明细,赋值;否则保持空集合(初始化时已 new List<>)if (detailsGrouped.TryGetValue(order.OrderID, out var orderDetails)){order.OrderDetails = orderDetails;}}// 输出结果验证Console.WriteLine($"已付款订单总数:{orders.Count}");foreach (var order in orders){Console.WriteLine($"订单编号:{order.OrderNo},明细数量:{order.OrderDetails.Count}");foreach (var detail in order.OrderDetails){Console.WriteLine($"  - 商品:{detail.ProductName},数量:{detail.Quantity}");}}}catch (SqlException ex){Console.WriteLine($"数据库错误:{ex.Message}");}catch (Exception ex){Console.WriteLine($"系统错误:{ex.Message}");}}

QueryUnbufferedAsync()

异步流式查询,防止一次性读入大数据占据内存过大,该函数可以一条一条,将内存占用从1G减到1K。底层应该使用了数据库游标

支持匿名,强类型,一对一,一对多

public class OrderDetail
{public int OrderDetailID { get; set; }public int OrderID { get; set; }public int ProductID { get; set; }public int Quantity { get; set; }
}string sql = "SELECT TOP 10 * FROM OrderDetails";using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{await foreach (var orderDetail in connection.QueryUnbufferedAsync<OrderDetail>(sql)){Console.WriteLine($"{orderDetail.OrderDetailID} - {orderDetail.Quantity}");}
}

一对一映射

string sql = "SELECT * FROM Invoice AS A INNER JOIN InvoiceDetail AS B ON A.InvoiceID = B.InvoiceID;";using (var connection = My.ConnectionFactory())
{var invoices = new List<Invoice>();await foreach (var invoice in connection.QueryUnbufferedAsync<Invoice, InvoiceDetail, Invoice>(sql,(inv, detail) =>{inv.InvoiceDetail = detail;return inv;},splitOn: "InvoiceID")){invoices.Add(invoice);}
}

Execute

它可以执行一次或多次命令并返回受影响的行数。此方法通常用于执行:

存储过程
INSERT 语句
UPDATE 语句
DELETE 语句

执行存储过程
var sql = "usp_UpdateTable";using (var connection = new SqlConnection(connectionString))
{var rowsAffected = connection.Execute(sql, new { id = 1, value1 = "ABC", value2 = "DEF" }, commandType: CommandType.StoredProcedure);
}

多次执行存储过程

string sql = "Invoice_Insert";using (var connection = My.ConnectionFactory())
{var affectedRows = connection.Execute(sql,new[]{new {Kind = InvoiceKind.WebInvoice, Code = "Many_Insert_1"},new {Kind = InvoiceKind.WebInvoice, Code = "Many_Insert_2"},new {Kind = InvoiceKind.StoreInvoice, Code = "Many_Insert_3"}},commandType: CommandType.StoredProcedure);My.Result.Show(affectedRows);
}

在上面的示例中,名为“Invoice_Insert”的存储过程将被调用三次。

插入

多条记录插入

string sql = "INSERT INTO Customers (CustomerName) Values (@CustomerName);";using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{connection.Open();var affectedRows = connection.Execute(sql,new[]{new {CustomerName = "John"},new {CustomerName = "Andy"},new {CustomerName = "Allan"}}
);Console.WriteLine(affectedRows);
更新

在下面的示例中,它将更新两条等于“1”和“4”的记录

string sql = "UPDATE Categories SET Description = @Description WHERE CategoryID = @CategoryID;";using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{    var affectedRows = connection.Execute(sql,new[]{new {CategoryID = 1, Description = "Soft drinks, coffees, teas, beers, mixed drinks, and ales"},new {CategoryID = 4, Description = "Cheeses and butters etc."}}
);Console.WriteLine(affectedRows);
删除
string sql = "DELETE FROM OrderDetails WHERE OrderDetailID = @OrderDetailID";using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{            var affectedRows = connection.Execute(sql, new[]{new {OrderDetailID = 1},new {OrderDetailID = 2},new {OrderDetailID = 3}}
);Console.WriteLine(affectedRows);

ExecuteReader

using(var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServer()))
{var reader = connection.ExecuteReader("SELECT * FROM Customers WHERE CreatedDate > @CreatedDate;", new { createdDate} );DataTable table = new DataTable();table.Load(reader);FiddleHelper.WriteTable(table);
}

ExcuteScalar

它允许您执行 SQL 语句或存储过程,并在结果集中第一行的第一列返回标量值。
如果结果集包含多个列或行,则它仅采用第一行的第一列,所有其他值将被忽略。
如果结果集为空,它将返回引用。Null
与 or 等聚合函数一起使用非常有用。Count(*),Sum()

//得到客户总数
using(var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServer()))
{int rowCount = connection.ExecuteScalar<int>("SELECT COUNT(*) FROM Customers");Console.WriteLine(rowCount);
}
//得到客户id为1的人的名字
using(var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServer()))
{var name = connection.ExecuteScalar<string>("SELECT Name FROM Customers WHERE CustomerID = @CustomerID;", new { CustomerID = 1});Console.WriteLine(name);
}

多类型结果

string sql = "SELECT * FROM Invoice;";using (var connection = My.ConnectionFactory())
{connection.Open();var invoices = new List<Invoice>();using (var reader = connection.ExecuteReader(sql)){var storeInvoiceParser = reader.GetRowParser<StoreInvoice>();得到转化器var webInvoiceParser = reader.GetRowParser<WebInvoice>();while (reader.Read()){Invoice invoice;switch ((InvoiceKind) reader.GetInt32(reader.GetOrdinal("Kind")))int转成枚举{case InvoiceKind.StoreInvoice:invoice = storeInvoiceParser(reader);转化break;case InvoiceKind.WebInvoice:invoice = webInvoiceParser(reader);break;default:throw new Exception(ExceptionMessage.GeneralException);}invoices.Add(invoice);}}
}

Dapper缓冲,就是提前ToList()

事务

string sql = "INSERT INTO Customers (CustomerName) Values (@CustomerName);";using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{connection.Open();using (var transaction = connection.BeginTransaction()){try{var affectedRows = connection.Execute(sql, new { CustomerName = "Mark" }, transaction: transaction);transaction.Commit(); // ✅ Commit when everything is fineConsole.WriteLine(affectedRows);}catch{transaction.Rollback(); // ✅ Rollback if something goes wrongthrow;}}
}

Dapper Plus

PM> Install-Package Z.Dapper.Plus

1实体映射配置(DapperPlusManager)

// 配置 Supplier 实体:映射到数据库表 Suppliers,主键是 SupplierID(Identity 表示“身份字段”,通常是自增主键)
DapperPlusManager.Entity<Supplier>().Table("Suppliers").Identity(x => x.SupplierID);

目录

Dapper基础:CURD

查询数据

匿名查询

强类型查询

查询多映射

查询多映射(1对多)

查询单个项

批处理 SQL 返回多个结果

QueryUnbufferedAsync()

Execute

执行存储过程

插入

更新

删除

ExecuteReader

ExcuteScalar

事务

Dapper Plus

批量插入主从表

一对多

实体具有标识属性,但您希望强制它插入特定值

插入而不返回标识值


using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{    // 核心链式操作:批量插入供应商 → 关联外键 → 批量插入产品connection.BulkInsert(suppliers) // 第一步:批量插入所有供应商.ThenForEach(x => x.Products.ForEach(p => p.SupplierID = x.SupplierID)) // 第二步:给产品设置外键(关联供应商ID).ThenBulkInsert(x => x.Products); // 第三步:批量插入所有产品
}

插入一对一

// @nuget: Z.Dapper.Plus
using Z.Dapper.Plus;DapperPlusManager.Entity<Supplier>().Table("Suppliers").Identity(x => x.SupplierID);
DapperPlusManager.Entity<Product>().Table("Products").Identity(x => x.ProductID);using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{    connection.BulkInsert(suppliers).ThenForEach(x => x.Product.SupplierID = x.SupplierID).ThenBulkInsert(x => x.Product);
}

一对多

// @nuget: Z.Dapper.Plus
using Z.Dapper.Plus;DapperPlusManager.Entity<Supplier>().Table("Suppliers").Identity(x => x.SupplierID); 
DapperPlusManager.Entity<Product>().Table("Products").Identity(x => x.ProductID);     using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{    connection.BulkInsert(suppliers).ThenForEach(x => x.Products.ForEach(y => y.SupplierID =  x.SupplierID)).ThenBulkInsert(x => x.Products);
}

实体具有标识属性,但您希望强制它插入特定值

  • 场景 1:数据库表主键是「非自增列」(如 UUID、业务自定义主键),需要手动指定 CustomerID 值插入;
  • 场景 2:数据库表主键是「自增列」,但需要批量导入历史数据(历史数据已有固定主键值,不能重新生成);
  • 场景 3:数据同步(从其他系统同步客户数据,主键值需保持一致,不能由当前数据库生成)。
 底层原理(以 SQL Server 为例)
Dapper Plus 执行时的底层 SQL 逻辑:sql
-- 1. 开启手动插入标识列(允许手动传入 CustomerID)
SET IDENTITY_INSERT Customers ON;-- 2. 批量插入数据(包含 CustomerID 字段)
BULK INSERT Customers (CustomerID, CustomerName, Email, CreateTime)
FROM ...; -- 数据来源(Dapper Plus 封装的内存数据)-- 3. 关闭手动插入标识列(恢复默认行为)
SET IDENTITY_INSERT Customers OFF;

用法:

connection.UseBulkOptions(options => options.InsertKeepIdentity = true).BulkInsert(Entity);

例子:

// @nuget: Z.Dapper.Plus
using Z.Dapper.Plus;DapperPlusManager.Entity<Customer>().Table("Customers"); using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{var validCustomers = customers.Where(c => c.CustomerID > 0).ToList(); // 过滤无效主键connection.UseBulkOptions(options => options.InsertKeepIdentity = true).BulkInsert(validCustomers);
}

字段映射必须正确(避免主键值插入失败)

DapperPlusManager.Entity<Customer>().Table("Customers").Identity(x => x.CustID) // 实体主键.Map(x => x.CustID, "CustomerID"); // 映射到表主键字段.Map(x => x.CustomerName, "CustomerName").Map(x => x.Email, "Email").Map(x => x.CreateTime, "CreateTime");也可以用[Column] 特性(数据注解) 
直接在实体类的属性上标记数据库字段名,无需在 DapperPlusManager 中通过 .Map() 显式配置
数据库主键需支持手动插入
坑 1:若数据库表主键是「自增列」且未开启 IDENTITY_INSERT(Dapper Plus 会自动处理,但需确保当前用户有该权限),会抛 “不能为标识列插入显式值” 的错误;
坑 2:若实体中的 CustomerID 值重复(如两个客户的 CustomerID 都是 1001),插入时会抛 “主键约束冲突” 的错误;
解决:
确保实体中的 CustomerID 唯一(批量插入前校验去重);
确保数据库用户有 ALTER TABLE 或 IDENTITY_INSERT 权限(否则无法切换标识列开关)。

插入而不返回标识值

意思就是数据库生成的主键id回填到你实体类中。

配置 options.AutoMapOutputDirection = false,可以提升性能。

// @nuget: Z.Dapper.Plus
using Z.Dapper.Plus;DapperPlusManager.Entity<Customer>().Table("Customers"); using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{connection.UseBulkOptions(options => options.AutoMapOutputDirection = false).BulkInsert(customers);FiddleHelper.WriteTable("1 - Customers (from list)", customers);
}

http://www.dtcms.com/a/577555.html

相关文章:

  • 深入理解 Spring Boot 中的数据库迁移:Flyway 与 Liquibase 实战指南
  • 使用visa进行仪器控制
  • 百度网站验证创意交易平台官网
  • Node.js异步编程的多种实现方式:从回调地狱到优雅的async/await
  • 全面评测 | Photoshop 2026 新特性深度解析与实测体验
  • FastAPI深度解析
  • wordpress会员数据共同盐城网络优化
  • 学校招聘教师网站建设网站建站前期准备工作
  • springboot系列--自动配置原理
  • Spring Aop实现
  • 在 VSCode 中:修改快捷键
  • 网站推广软件免费下载安装wordpress这个博客
  • React 18.x 学习计划 - 第七天:React性能优化
  • 网站建设费是几个点的税远程访问群晖wordpress
  • 2.9 超参数自动调优(Optuna / Hyperopt)
  • 【大模型训练】 roll 权重更新 过程
  • QAbstractListModel 详细解析
  • 2025自动化运维厂商选型指南:数字化转型下,自动化运维平台为何成为“必选项”?
  • 如何把宏观战略转化为可执行的产品计划
  • 店铺设计素材针对网站做搜索引擎做优化
  • 温州网站排名优化公司哪家好网站推广服务合同模板
  • vscode-python学习-启动
  • STM32 串口线A-B
  • 使用 dnsmasq 搭建本地 DNS 服务器完整指南
  • 水墨画风格网站wordpress大气摄影主题
  • 详细介绍一下“集中同步+分布式入库”方案的具体实现步骤
  • 网站建设需要上传数据库吗双创网站建设
  • 轻量级Kafka集群管理工具
  • 嵌入式计算架构变革:ARM 浪潮下的替代革命与杰和科技产品布局
  • HarmonyOs鸿蒙开发,日期滑动选择器