如何使用 Dapper 从多个表加载实体?
How to load entity from multiple tables with Dapper?
在我的应用程序中,我使用实体作为数据库表示中的表。
我有一个 OrderEntity,它有 ProductEntity、CustomerEntity 等字段,然后 CustomerEntity 有 AddressEntity 等字段
现在我尝试让 OrderEntity 填充所有实体类型属性等。看来我必须从 8 tables.
加载数据
我只是不知道如何正确地做到这一点。我有一个带有 Get 方法的 OrderRepository,我想要 return OrderEntity。那么我是否应该使用 7 个连接创建 SQL,一个 class 使用 SQL 中的所有列,然后在执行 SQL 之后在此存储库的 Get 方法中手动创建 OrderEntity 等?
当我必须 get/update 1 table 时,使用存储库等很容易,但是当模型由超过 1-2 table 构建时,它变得非常困难对我来说。
尝试使用 SQL 中的视图,这会让事情变得非常简单。
View 的工作方式与 table 相同,Dapper 对视图的查询与 table 相同。你想要这样做的方式会让事情变得比他们应该做的更难。
选项 1:
我使用的方法是单独加载每个关系(对于一个小的 N 表)。如果您有 8 个表,那么 8 个查询将提供您需要的所有数据。这是 3 个表的人为示例。
public class Person
{
public int PersonID { get; set; }
public string PersonName { get; set; }
public Address[] Addresses { get; set; }
}
public class Address
{
public int AddressID { get; set; }
public int PersonID { get; set; }
public string AddressLine1 { get; set; }
public string City{ get; set; }
public string StateCode { get; set; }
public string PostalCode { get; set; }
public Note[] Notes { get; set; }
}
public class Note
{
public int AddressID { get; set; }
public int NoteID { get; set; }
public string NoteText { get; set; }
}
您将查询每个表。
var people = conn.Query<Person>("select * from Person where ...");
var personIds = people.Select(x => x.PersonID);
var addresses = conn.Query<Address>("select * from Address where PersonID in @PersonIds", new { personIds });
var addressIds = addresses.Select(x => x.AddressID);
var notes = conn.Query<Note>("select * from Note where AddressID in @AddressIds", new { addressIds });
然后,一旦您拥有所有数据,将其连接起来以修复您已加载的这些记录之间的关系。
// Group addresses by PersonID
var addressesLookup = addresses.ToLookup(x => x.PersonID);
// Group notes by AddressID
var notesLookup = notes.ToLookup(x => x.AddressID);
// Use the lookups above to populate addresses and notes
people.Each(x => x.Addresses = addressesLookup[x.PersonID].ToArray());
addresses.Each(x => x.Notes = notesLookup[x.AddressID].ToArray());
还有其他方法,但视图可能无法满足所有条件,尤其是在给定复杂关系时,会导致记录激增。
选项 2:
从以下link开始,您可以使用QueryMultiple。
https://medium.com/dapper-net/handling-multiple-resultsets-4b108a8c5172
代码如下,其中您的 child 查询将必须 select 所有记录。
var results = conn.QueryMultiple(@"
SELECT Id, CompanyId, FirstName, LastName FROM dbo.Users WHERE LastName = 'Smith';
SELECT Id, CompanyName FROM dbo.Companies WHERE CompanyId IN ( SELECT CompanyId FROM dbo.Users WHERE LastName = 'Smith' );
");
var users = results.Read<User>();
var companies = results.Read<Company>();
然后您将修复选项 1 中的关系。
好的(按照上面的要求)- 使用 Tuple 和 Dapper 的示例。
我写得很快,所以如果有任何错误,请告诉我,我会改正。我 100% 确定它也可以优化!
以上结构为例:
public class Person
{
public int PersonID { get; set; }
public string PersonName { get; set; }
public IEnumerable<Address> Addresses { get; set; }
}
public class Address
{
public int AddressID { get; set; }
public int PersonID { get; set; }
public string AddressLine1 { get; set; }
public string City{ get; set; }
public string StateCode { get; set; }
public string PostalCode { get; set; }
public IEnumerable<Note> Notes { get; set; }
}
public class Note
{
public int AddressID { get; set; }
public int NoteID { get; set; }
public string NoteText { get; set; }
}
string cmdTxt = @"SELECT p.*, a.*, n.*
FROM Person p
LEFT OUTER JOIN Address a ON p.PersonId = a.PersonId
LEFT OUTER JOIN Note n ON a.AddressId = n.AddressId
WHERE p.PersonId = @personID";
var results = await conn.QueryAsync<Person,Address,Note,Tuple<Person,Address,Note>>(cmdTxt,
map: (p,a,n)=>Tuple.Create((Person)p, (Address)a, (Note)n),
param: new { personID = 1 });
if(results.Any()) {
var person = results.First().Item1; //the person
var addresses = results.Where(n => n.Item2 != null).Select(n=>n.Item2); //the person's addresses
var notes = results.Where(n => n.Item3 != null).Select(n=>n.Item3); //all notes for all addresses
if(addresses.Any()) {
person.Addresses = addresses.ToList(); //add the addresses to the person
foreach(var address in person.Addresses) {
var address_notes = notes.Where(n=>n.AddressId==address.AddressId).ToList(); //get any notes
if(address_notes.Any()) {
address.Notes = address_notes; //add the notes to the address
}
}
}
}
在我的应用程序中,我使用实体作为数据库表示中的表。
我有一个 OrderEntity,它有 ProductEntity、CustomerEntity 等字段,然后 CustomerEntity 有 AddressEntity 等字段
现在我尝试让 OrderEntity 填充所有实体类型属性等。看来我必须从 8 tables.
加载数据我只是不知道如何正确地做到这一点。我有一个带有 Get 方法的 OrderRepository,我想要 return OrderEntity。那么我是否应该使用 7 个连接创建 SQL,一个 class 使用 SQL 中的所有列,然后在执行 SQL 之后在此存储库的 Get 方法中手动创建 OrderEntity 等?
当我必须 get/update 1 table 时,使用存储库等很容易,但是当模型由超过 1-2 table 构建时,它变得非常困难对我来说。
尝试使用 SQL 中的视图,这会让事情变得非常简单。
View 的工作方式与 table 相同,Dapper 对视图的查询与 table 相同。你想要这样做的方式会让事情变得比他们应该做的更难。
选项 1:
我使用的方法是单独加载每个关系(对于一个小的 N 表)。如果您有 8 个表,那么 8 个查询将提供您需要的所有数据。这是 3 个表的人为示例。
public class Person
{
public int PersonID { get; set; }
public string PersonName { get; set; }
public Address[] Addresses { get; set; }
}
public class Address
{
public int AddressID { get; set; }
public int PersonID { get; set; }
public string AddressLine1 { get; set; }
public string City{ get; set; }
public string StateCode { get; set; }
public string PostalCode { get; set; }
public Note[] Notes { get; set; }
}
public class Note
{
public int AddressID { get; set; }
public int NoteID { get; set; }
public string NoteText { get; set; }
}
您将查询每个表。
var people = conn.Query<Person>("select * from Person where ...");
var personIds = people.Select(x => x.PersonID);
var addresses = conn.Query<Address>("select * from Address where PersonID in @PersonIds", new { personIds });
var addressIds = addresses.Select(x => x.AddressID);
var notes = conn.Query<Note>("select * from Note where AddressID in @AddressIds", new { addressIds });
然后,一旦您拥有所有数据,将其连接起来以修复您已加载的这些记录之间的关系。
// Group addresses by PersonID
var addressesLookup = addresses.ToLookup(x => x.PersonID);
// Group notes by AddressID
var notesLookup = notes.ToLookup(x => x.AddressID);
// Use the lookups above to populate addresses and notes
people.Each(x => x.Addresses = addressesLookup[x.PersonID].ToArray());
addresses.Each(x => x.Notes = notesLookup[x.AddressID].ToArray());
还有其他方法,但视图可能无法满足所有条件,尤其是在给定复杂关系时,会导致记录激增。
选项 2:
从以下link开始,您可以使用QueryMultiple。
https://medium.com/dapper-net/handling-multiple-resultsets-4b108a8c5172
代码如下,其中您的 child 查询将必须 select 所有记录。
var results = conn.QueryMultiple(@"
SELECT Id, CompanyId, FirstName, LastName FROM dbo.Users WHERE LastName = 'Smith';
SELECT Id, CompanyName FROM dbo.Companies WHERE CompanyId IN ( SELECT CompanyId FROM dbo.Users WHERE LastName = 'Smith' );
");
var users = results.Read<User>();
var companies = results.Read<Company>();
然后您将修复选项 1 中的关系。
好的(按照上面的要求)- 使用 Tuple 和 Dapper 的示例。
我写得很快,所以如果有任何错误,请告诉我,我会改正。我 100% 确定它也可以优化!
以上结构为例:
public class Person
{
public int PersonID { get; set; }
public string PersonName { get; set; }
public IEnumerable<Address> Addresses { get; set; }
}
public class Address
{
public int AddressID { get; set; }
public int PersonID { get; set; }
public string AddressLine1 { get; set; }
public string City{ get; set; }
public string StateCode { get; set; }
public string PostalCode { get; set; }
public IEnumerable<Note> Notes { get; set; }
}
public class Note
{
public int AddressID { get; set; }
public int NoteID { get; set; }
public string NoteText { get; set; }
}
string cmdTxt = @"SELECT p.*, a.*, n.*
FROM Person p
LEFT OUTER JOIN Address a ON p.PersonId = a.PersonId
LEFT OUTER JOIN Note n ON a.AddressId = n.AddressId
WHERE p.PersonId = @personID";
var results = await conn.QueryAsync<Person,Address,Note,Tuple<Person,Address,Note>>(cmdTxt,
map: (p,a,n)=>Tuple.Create((Person)p, (Address)a, (Note)n),
param: new { personID = 1 });
if(results.Any()) {
var person = results.First().Item1; //the person
var addresses = results.Where(n => n.Item2 != null).Select(n=>n.Item2); //the person's addresses
var notes = results.Where(n => n.Item3 != null).Select(n=>n.Item3); //all notes for all addresses
if(addresses.Any()) {
person.Addresses = addresses.ToList(); //add the addresses to the person
foreach(var address in person.Addresses) {
var address_notes = notes.Where(n=>n.AddressId==address.AddressId).ToList(); //get any notes
if(address_notes.Any()) {
address.Notes = address_notes; //add the notes to the address
}
}
}
}