如何使用 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
             }
         }
    }
}