How do I map lists of nested objects with Dapper

2019-01-03 08:18发布

I'm currently using Entity Framework for my db access but want to have a look at Dapper. I have classes like this:

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

public class Location{
   public string Name {get;set;}
   ...
}

So one course can be taught at several locations. Entity Framework does the mapping for me so my Course object is populated with a list of locations. How would I go about this with Dapper, is it even possible or do I have to do it in several query steps?

标签: orm dapper
7条回答
Rolldiameter
2楼-- · 2019-01-03 08:28

Dapper is not a full blown ORM it does not handle magic generation of queries and such.

For your particular example the following would probably work:

Grab the courses:

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

Grab the relevant mapping:

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

Grab the relevant locations

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

Map it all up

Leaving this to the reader, you create a few maps and iterate through your courses populating with the locations.

Caveat the in trick will work if you have less than 2100 lookups (Sql Server), if you have more you probably want to amend the query to select * from CourseLocations where CourseId in (select Id from Courses ... ) if that is the case you may as well yank all the results in one go using QueryMultiple

查看更多
Evening l夕情丶
3楼-- · 2019-01-03 08:32

Sorry to be late to the party (like always). For me it's easier to use a Dictionary like @Jeroen-k did, in terms of performance and readability, also to avoid header multiplication across "locations", I use Distinct to remove potential dups:

        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;
        }
查看更多
萌系小妹纸
4楼-- · 2019-01-03 08:33

Something is missing. If you not specify each field from Locations in the SQL query, the object Location cannot be filled. Take a look:

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;

Using "l.*" on query, I had the list of locations but without data.

查看更多
Rolldiameter
5楼-- · 2019-01-03 08:34

Not sure if anybody needs it but I have dynamic version of it without Model for quick & flexible coding.

        var lookup = new Dictionary<int, dynamic>();
        conn.Query<dynamic, dynamic, dynamic>(@"
                        SELECT A.*, B.*
                        FROM Client A
                        INNER JOIN Instance B ON A.ClientID = B.ClientID                
                        ", (A, B) => {
                            // If dict has no key, allocate new obj
                            // with another level of array
                            if (!lookup.ContainsKey(A.ClientID)){
                                lookup[A.ClientID] = new {
                                    ClientID = A.ClientID,
                                    ClientName = A.Name,                                        
                                    Instances = new List<dynamic>()
                                };
                            }

                           // Add each instance                                
                           lookup[A.ClientID].Instances.Add(new {
                                    InstanceName = B.Name,
                                    BaseURL = B.BaseURL,
                                    WebAppPath = B.WebAppPath
                           });


                            return lookup[A.ClientID];
                        }, splitOn: "ClientID,InstanceID").AsQueryable();
        var resultList = lookup.Values;
        return resultList;
查看更多
爷、活的狠高调
6楼-- · 2019-01-03 08:41

Alternatively, you can use one query with a lookup:

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;

See here https://www.tritac.com/blog/dappernet-by-example/

查看更多
做自己的国王
7楼-- · 2019-01-03 08:42

No need for lookup Dictionary

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();
查看更多
登录 后发表回答