[orm] Dapper로 중첩 된 개체 목록을 매핑하는 방법

현재 DB 액세스를 위해 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;}
   ...
}

따라서 하나의 코스를 여러 위치에서 가르 칠 수 있습니다. Entity Framework는 저를 위해 매핑을 수행하므로 코스 개체가 위치 목록으로 채워집니다. Dapper로이 문제를 해결하려면 어떻게해야합니까? 가능합니까 아니면 여러 쿼리 단계에서 수행해야합니까?



답변

Dapper는 완전한 ORM이 아니며 쿼리의 매직 생성 등을 처리하지 않습니다.

특정 예의 경우 다음이 작동합니다.

과정 파악 :

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

관련 매핑을 가져옵니다.

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

관련 위치 파악

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

모든 것을 매핑

이것을 독자에게 맡기고 몇 개의 맵을 만들고 위치로 채워지는 코스를 반복합니다.

주의in 미만의 경우 트릭이 작동 2,100 조회 (SQL 서버)이있는 경우, 더 당신은 아마에 쿼리를 수정하려면 select * from CourseLocations where CourseId in (select Id from Courses ... )사용 가서 그게 당신이뿐만 아니라 하나의 모든 결과를 꺼낼 수있는 경우에 해당QueryMultiple


답변

또는 조회와 함께 하나의 쿼리를 사용할 수 있습니다.

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;

여기를 참조하십시오 https://www.tritac.com/blog/dappernet-by-example/


답변

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();


답변

나는 이것에 정말로 늦었다는 것을 알고 있지만 다른 옵션이 있습니다. 여기에서 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();
}


답변

파티에 늦어서 죄송합니다 (언제나 그렇듯이). 나를 위해, 그것은을 사용하는 것이 더 쉽습니다 Dictionary, 제론 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;
}


답변

무언가가 빠졌어. 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;

l.*쿼리에서 사용하면 위치 목록이 있지만 데이터는 없습니다.


답변

누구에게 필요한지 확실하지 않지만 빠르고 유연한 코딩을 위해 Model없이 동적 버전이 있습니다.

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;