使用 Dapper 处理多个结果集和多个映射的教程






4.89/5 (2投票s)
如何在单个数据库调用中使用 dapper 从数据库读取多个结果集
引言
在本文中,我们将探讨如何使用 Dapper 在一次数据库调用中从数据库读取多个结果集。我们将讨论可能需要这样做的场景,以及 Dapper 如何通过其 Query
和 QueryMultiple
方法实现这一点。
背景
在谈论以数据为中心的应用程序时,有时我们可能需要从数据库中检索多个结果。这些多个结果集可以是相关的,也可以是无关的。为了做到这一点,我们实际上可以使用 Dapper 在一次数据库调用中检索结果,然后将结果映射到代码中所需的对象,而不是进行多次数据库往返。
在开始探讨如何实现这一点之前,我们首先尝试了解在应用程序中可能需要这样做的场景
- 查询不相关的实体:所请求的实体之间完全不相关。
- 查询具有一对多关系的关联实体:所请求的实体具有 1-* 关系,我们需要在代码中处理多个结果集。
- 查询具有一对一关系的关联实体:所请求的实体具有 1-1 关系,我们需要在代码中执行多重映射。
在第一个场景中,我们有完全不相关的实体,所以本质上,我们只是想执行两个独立的查询来检索数据,然后将其映射到这些实体。在第二个场景中,返回的实体是 1-* 相关的,所以我们想检索数据,然后将结果映射到具有一对多关系的 POCO。最后,在第三个场景中,返回的实体是 1-1 相关的,所以我们想检索数据,然后将结果映射到具有一对一关系的 POCO。
现在让我们看一些代码来了解如何使用 Dapper 实现这一切。
注意:我们将基于我们在之前的 Dapper 文章中构建的现有项目。强烈建议在阅读本文之前阅读第一篇文章:绝对初学者教程:理解和使用 Dapper ORM[^]。
Using the Code
所有这一切都可以通过使用 Dapper 的 Query
、QueryMultiple
和 Read
方法来实现。现在让我们将重点转向如何在代码中实际执行这些操作。
查询不相关的实体
假设我们想从 API 检索图书和视频列表。我们可以通过执行两个简单的 select all
查询来实现这一点,数据库结果将如下所示
现在为了在代码中实现同样的功能,我们首先需要定义我们的实体
public class Book
{
public int ID { get; set; }
public string BookName { get; set;}
public string ISBN { get; set; }
}
public class Video
{
public int ID { get; set; }
public string VideoName { get; set; }
}
有了这些模型,让我们看看如何使用 Dapper 在一次数据库调用中检索这些结果
public IActionResult Index()
{
// define our SQL query - it contains multiple queries separated by ;
var query = "SELECT * from Books; Select * from Videos";
// Execute the query
var results = dbConnection.QueryMultiple(query);
// retrieve the results into the respective models
var books = results.Read<Book>();
var videos = results.Read<Video>();
return Ok(new { Books = books, Videos = videos});
}
现在让我们在 POSTMAN 中运行它,看看实际结果
注意:我创建了一个简单的 API 控制器来测试此代码,所有数据库访问代码都内置其中。这仅用于演示目的,在实际应用程序中,绝不应使用此类代码。
查询具有一对多关系的关联实体
检索关联实体的另一个典型场景是实体之间存在一对多关系。让我们通过一个 Organization
和 Contacts
的例子来形象化这一点。一个 Organization
通常会关联多个 Contacts
。如果我们想检索一个组织并检索所有关联的联系人,我们可以利用 QueryMultiple
来实现这一点。这就是数据库中关系的样子。
首先让我们看看如何使用 SQL 查询实现同样的功能。
现在,如果我们要使用代码实现同样的功能,我们首先需要定义我们的实体。请注意,我们的实体也将以每个 Organization
拥有一个 Contacts
列表的方式建模一对多关系。
public class Organization
{
public int ID { get; set; }
public string OrganizationName { get; set; }
public List<contact> Contacts { get; set; }
}
public class Contact
{
public int ID { get; set; }
public int OrganizationId { get; set; }
public string ContactName { get; set; }
}
现在让我们看看我们可以用来检索这些关联实体的代码,并看看如何使用 Dapper 的 QueryMultiple
方法填充具有一对多关系的实体。
[HttpGet("{id}")]
public IActionResult GetOrganization(int id)
{
// define our SQL query - it contains multiple queries separated by ;
var query = @"SELECT* from Organizations where id = @id;
Select * from Contacts where OrganizationId = @id";
// Execute the query
var results = dbConnection.QueryMultiple(query, new { @id = id });
// retrieve the results into the respective models
var org = results.ReadSingle<Organization>();
org.Contacts = results.Read<Contact>().ToList();
return Ok(org);
}
在上面的代码中,我们可以看到我们是如何同时执行两个查询的。我们取了第一个查询的结果并填充了我们的 Organization
对象。第二个查询的结果作为同一 Organization
对象的 Contact
集合被推入。
现在让我们在 POSTMAN 中运行它,看看实际结果
查询具有一对一关系的关联实体
前两个场景都相当直接,因为它们需要我们编写两个单独的查询,然后独立地从每个查询中收集结果以创建所需的模型对象。
但是具有一对一关系的场景却相当棘手。从数据库的角度来看,我们可以在单个 SQL 查询中检索相关的实体,但随后我们希望将单个结果集映射到代码中的多个对象。这可以使用 Dapper 中提供的多重映射功能来完成。让我们借助一个例子来理解这一点。
注意:我们仍然可以使用我们在处理一对多关系时使用的方法来检索一对一相关的数据,但本节展示了如何通过单个 SQL 和映射结果来完成。
让我们以 Contact
和 Passport
为例。每个 Contact
只能有一个 passport
。让我们首先尝试可视化这种数据库关系。
现在让我们看看如果我们需要从数据库中检索联系人及其护照信息的列表,我们如何在 SQL 中做到这一点。
现在让我们看看 Contact
和 Passport
的实体是如何定义的。
public class Contact
{
public int ID { get; set; }
public int OrganizationId { get; set; }
public string ContactName { get; set; }
public Passport Passport { get; set; }
}
public class Passport
{
public int ID { get; set; }
public int Contactid { get; set; }
public string PassportNumber { get; set; }
}
现在让我们看看如何从数据库中检索这些关联实体,并使用 Dapper 多重映射来填充我们的 POCO,同时保持相同的关系不变。
[HttpGet("{id}")]
public IActionResult GetContact(int id)
{
var query = @"Select
c.ID, c.Organizationid, c.ContactName,
p.ID as PassPortId, p.ContactId, p.PassportNumber
from Contacts c,
Passports p
where c.ID = p.ContactID
and c.id = @id";
// Execute the query
var contact = dbConnection.Query<Contact, Passport, Contact>
(query, MapResults, new { @id = id }, splitOn: "PassportId");
return Ok(contact);
}
private Contact MapResults(Contact contact, Passport passport)
{
contact.Passport = passport;
return contact;
}
在上述代码中,我们正在使用 Query
方法的重载版本,该版本接受多种类型。传入的类型是我们要映射的每个对象的类型参数,最后一个类型参数是表示此查询将返回的对象类型的附加参数。
因此,在我们的查询中,我们希望将结果映射到 Contact
和 Passport
类型,然后期望以 Contact
类型的对象形式返回结果。
现在,让我们看看传递给查询方法的实际参数。
- 第一个参数是 SQL 查询本身。
- 第二个参数是映射函数,它将获取结果,将其绑定到相应的类型,然后创建所需的返回类型并返回该返回类型。在我们的代码中,它接受
Contact
和Passport
类型,并将Contact
的Passport
属性赋值为传入的Passport
值。完成后,返回结果Contact
类型。 - 第三个参数是命令参数
@id
。 - 最后一个参数
splitOn
是列名,它将告诉 Dapper 哪些列必须映射到下一个对象。在我们的示例中,我们将此值传递为PassportId
,这意味着在找到PassportId
列之前,所有列都将映射到第一个类型,即Contact
,然后在此之后的列将映射到下一个参数类型,即Passport
。
注意:如果我们需要映射的对象超过 2 个,splitOn
将是一个逗号分隔的列表,其中每个列名都将作为分隔符和下一个对象类型映射列的起点。
现在让我们在 POSTMAN 中运行它,看看实际结果
我们成功了。我们正在使用 Dapper 从数据库检索多个结果集以避免数据库往返。
看点
在本文中,我们探讨了如何利用 Dapper 提供的功能一次性检索多个相关或不相关的实体,从而避免多次数据库往返。本文是为初学者编写的。我希望它能提供一些信息。
历史
- 2018 年 9 月 18 日:第一版