如何使用Dapper映射嵌套对象的列表

b3n*_*b3n 112 orm dapper

我目前正在使用Entity Framework进行数据库访问,但想看看Dapper.我有这样的课程:

public class Course{
   public string Title{get;set;}
   public IList<Location> Locations {get;set;}
   ...
}

public class Location{
   public string Name {get;set;}
   ...
}
Run Code Online (Sandbox Code Playgroud)

因此,可以在多个地点教授一门课程.实体框架为我执行映射,因此我的Course对象填充了一个位置列表.我如何与Dapper一起讨论这个问题,它是否可能,或者我必须在几个查询步骤中执行此操作?

Jer*_*n K 159

或者,您可以将一个查询与查找一起使用:

var lookup = new Dictionary<int, Course>();
conn.Query<Course, Location, Course>(@"
    SELECT c.*, l.*
    FROM Course c
    INNER JOIN Location l ON c.LocationId = l.Id                    
    ", (c, l) => {
        Course course;
        if (!lookup.TryGetValue(c.Id, out course))
            lookup.Add(c.Id, course = c);
        if (course.Locations == null) 
            course.Locations = new List<Location>();
        course.Locations.Add(l); /* Add locations to course */
        return course;
     }).AsQueryable();
var resultList = lookup.Values;
Run Code Online (Sandbox Code Playgroud)

请参见https://www.tritac.com/blog/dappernet-by-example/

  • 这为我节省了大量时间.其他人可能需要的一个修改是包含splitOn:参数,因为我没有使用默认的"Id". (7认同)
  • 对于 LEFT JOIN,您将在位置列表中获得一个空项目。通过 var items = lookup.Values 删除它们;items.ForEach(x =&gt; x.Locations.RemoveAll(y =&gt; y == null)); (2认同)
  • 对于 LEFT JOIN:不需要对其进行另一个 Foreach。在添加之前检查一下:if(l != null) course.Locations.Add(l)。 (2认同)
  • 由于您正在使用字典。如果您分别使用 QueryMultiple 和查询课程和位置,然后使用相同的字典为课程分配位置,这会更快吗?它本质上是一样的东西减去内部连接,这意味着 sql 不会传输尽可能多的字节? (2认同)

Sam*_*ron 48

Dapper并不是一个完整的ORM它不会处理魔法生成的查询等.

对于您的特定示例,以下可能会起作用:

抓住课程:

var courses = cnn.Query<Course>("select * from Courses where Category = 1 Order by CreationDate");
Run Code Online (Sandbox Code Playgroud)

抓住相关的映射:

var mappings = cnn.Query<CourseLocation>(
   "select * from CourseLocations where CourseId in @Ids", 
    new {Ids = courses.Select(c => c.Id).Distinct()});
Run Code Online (Sandbox Code Playgroud)

抓住相关位置

var locations = cnn.Query<Location>(
   "select * from Locations where Id in @Ids",
   new {Ids = mappings.Select(m => m.LocationId).Distinct()}
);
Run Code Online (Sandbox Code Playgroud)

全部映射

将此信息留给读者,您可以创建一些地图并遍历填充位置的课程.

需要注意in伎俩将工作,如果你有小于2100个查询(SQL Server)的,如果你有更多你可能想查询修改为select * from CourseLocations where CourseId in (select Id from Courses ... )如果这是你可以使用以及抽出一个全力以赴的结果的情况下QueryMultiple

  • Sam,在一个〜大型应用程序中,在该应用程序中,集合定期暴露在域对象上,如示例中所示,*您建议将此代码物理放置在哪里*?(假设您想从代码中的*多个*不同位置使用类似的完全构造的 [Course] 实体)在构造函数中?在班级工厂?别的地方? (3认同)

tch*_*dze 31

不需要lookup字典

var coursesWithLocations = 
    conn.Query<Course, Location, Course>(@"
        SELECT c.*, l.*
        FROM Course c
        INNER JOIN Location l ON c.LocationId = l.Id                    
        ", (course, location) => {
            course.Locations = course.Locations ?? new List<Location>();
            course.Locations.Add(location); 
            return course;
        }).AsQueryable();
Run Code Online (Sandbox Code Playgroud)

  • 实际上,我最终得到了 3 个父母,每个人中有 1 个孩子使用此代码。不知道为什么我的结果与@topwik 不同,但仍然不起作用。 (6认同)
  • 唯一的问题是您将在每个位置记录上复制标题。如果每门课程有很多位置,则可能会在线路上进行大量数据复制,这将增加带宽,解析/映射需要更长的时间,并使用更多内存来读取所有这些。 (5认同)
  • 这个答案是错误的,因为返回一门课程和数据库中的 3 个位置,将返回 3 个课程,每个课程有一个位置。 (5认同)
  • 我不确定这是否按预期工作。我有1个父对象和3个相关对象。我使用的查询返回三行。第一列描述父级,每行重复一遍;id上的拆分将标识每个唯一的孩子。我的结果是有3个孩子的3个重复父母。...应该是有3个孩子的1个父母。 (4认同)
  • 这很好 - 我认为这应该是选定的答案.但是,这样做的人会注意做*因为这会影响性能. (2认同)
  • @topwik是正确的。它也不符合我的预期。 (2认同)

Dan*_*enz 27

我知道我已经迟到了,但还有另一种选择.您可以在此处使用QueryMultiple.像这样的东西:

var results = cnn.QueryMultiple(@"
    SELECT * 
      FROM Courses 
     WHERE Category = 1 
  ORDER BY CreationDate
          ; 
    SELECT A.*
          ,B.CourseId 
      FROM Locations A 
INNER JOIN CourseLocations B 
        ON A.LocationId = B.LocationId 
INNER JOIN Course C 
        ON B.CourseId = B.CourseId 
       AND C.Category = 1
");

var courses = results.Read<Course>();
var locations = results.Read<Location>(); //(Location will have that extra CourseId on it for the next part)
foreach (var course in courses) {
   course.Locations = locations.Where(a => a.CourseId == course.CourseId).ToList();
}
Run Code Online (Sandbox Code Playgroud)

  • 需要注意的一件事。如果有很多位置/课程,您应该遍历这些位置一次并将它们放入字典查找中,这样您就有 N log N 而不是 N^2 速度。在较大的数据集上有很大的不同。 (7认同)

Fra*_*ena 8

很抱歉迟到了(像往常一样)。对我来说,在性能和可读性方面Dictionary像 Jeroen K 那样使用 更容易。此外,为了避免跨位置的标题乘法,我使用Distinct()删除潜在的重复:

string query = @"SELECT c.*, l.*
    FROM Course c
    INNER JOIN Location l ON c.LocationId = l.Id";
using (SqlConnection conn = DB.getConnection())
{
    conn.Open();
    var courseDictionary = new Dictionary<Guid, Course>();
    var list = conn.Query<Course, Location, Course>(
        query,
        (course, location) =>
        {
            if (!courseDictionary.TryGetValue(course.Id, out Course courseEntry))
            {
                courseEntry = course;
                courseEntry.Locations = courseEntry.Locations ?? new List<Location>();
                courseDictionary.Add(courseEntry.Id, courseEntry);
            }

            courseEntry.Locations.Add(location);
            return courseEntry;
        },
        splitOn: "Id")
    .Distinct()
    .ToList();

    return list;
}
Run Code Online (Sandbox Code Playgroud)


小智 6

缺了点什么。如果不指定LocationsSQL 查询中的每个字段,Location则无法填充对象。看一看:

var lookup = new Dictionary<int, Course>()
conn.Query<Course, Location, Course>(@"
    SELECT c.*, l.Name, l.otherField, l.secondField
    FROM Course c
    INNER JOIN Location l ON c.LocationId = l.Id                    
    ", (c, l) => {
        Course course;
        if (!lookup.TryGetValue(c.Id, out course)) {
            lookup.Add(c.Id, course = c);
        }
        if (course.Locations == null) 
            course.Locations = new List<Location>();
        course.Locations.Add(a);
        return course;
     },
     ).AsQueryable();
var resultList = lookup.Values;
Run Code Online (Sandbox Code Playgroud)

l.*在查询中使用时,我有位置列表但没有数据。