Làm cách nào để ánh xạ danh sách các đối tượng lồng nhau với Dapper


126

Tôi hiện đang sử dụng Entity Framework để truy cập db của mình nhưng muốn xem qua Dapper. Tôi có các lớp học như thế này:

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

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

Vì vậy, một khóa học có thể được dạy tại một số địa điểm. Entity Framework thực hiện ánh xạ cho tôi để đối tượng Khóa học của tôi được điền với một danh sách các vị trí. Làm thế nào tôi có thể làm điều này với Dapper, thậm chí là có thể hay tôi phải làm điều đó trong một số bước truy vấn?



đây là giải pháp của tôi: stackoverflow.com/a/57395072/8526957
Sam Sch

Câu trả lời:


56

Dapper không phải là một ORM toàn diện, nó không xử lý việc tạo ra các truy vấn kỳ diệu và như vậy.

Đối với ví dụ cụ thể của bạn, những điều sau đây có thể sẽ hoạt động:

Lấy các khóa học:

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

Lấy bản đồ liên quan:

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

Lấy các vị trí liên quan

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

Bản đồ tất cả lên

Để lại cho người đọc, bạn tạo một vài bản đồ và lặp qua các khóa học của bạn với các địa điểm.

Nên biết trước sự inlừa sẽ làm việc nếu bạn có ít hơn 2100 tra cứu (Sql Server), nếu bạn có nhiều bạn có thể muốn sửa đổi các truy vấn để select * from CourseLocations where CourseId in (select Id from Courses ... )nếu đó là trường hợp bạn cũng có thể yank tất cả các kết quả trong một đi sử dụngQueryMultiple


Cảm ơn đã làm rõ Sam. Giống như bạn đã mô tả ở trên, tôi chỉ chạy một truy vấn thứ hai tìm nạp các Vị trí và gán chúng theo cách thủ công cho khóa học. Tôi chỉ muốn chắc chắn rằng tôi đã không bỏ lỡ điều gì cho phép tôi làm điều đó với một truy vấn.
b3n

2
Sam, trong một ứng dụng lớn ~ nơi các bộ sưu tập thường xuyên được hiển thị trên các đối tượng miền như trong ví dụ, bạn muốn giới thiệu mã này ở đâu? (Giả sử bạn muốn sử dụng một thực thể [Khóa học] được xây dựng đầy đủ tương tự từ nhiều nơi khác nhau trong mã của bạn) Trong hàm tạo? Trong một nhà máy đẳng cấp? Ở đâu đó khác?
xương sống

177

Ngoài ra, bạn có thể sử dụng một truy vấn với tra cứu:

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;

Xem tại đây https://www.tritac.com/blog/dappernet-by-example/


9
Điều này giúp tôi tiết kiệm rất nhiều thời gian. Một sửa đổi tôi cần mà những người khác có thể cần là bao gồm đối số splitOn: vì tôi không sử dụng "Id" mặc định.
Bill Sambrone

1
Đối với LEFT THAM GIA, bạn sẽ nhận được một mục rỗng trong danh sách vị trí. Xóa chúng bằng var items = lookup.Values; các mặt hàng.ForEach (x => x.Locations.Remove ALL (y => y == null));
Choco Smith

Tôi không thể biên dịch cái này trừ khi tôi có dấu chấm phẩy ở cuối dòng 1 và xóa dấu phẩy trước 'AsQueryable ()'. Tôi sẽ chỉnh sửa câu trả lời nhưng 62 người nâng cấp trước tôi dường như nghĩ rằng nó ổn, có lẽ tôi đang thiếu một cái gì đó ...
bitcoder

1
Đối với TRÁI PHIẾU: Không cần thực hiện một Nghiên cứu khác về nó. Chỉ cần kiểm tra trước khi thêm nó: if (l! = Null) khóa học.Locations.Add (l).
jpgrassi

1
Vì bạn đang sử dụng một từ điển. Điều này sẽ nhanh hơn nếu bạn sử dụng QueryMult Môn và truy vấn khóa học và vị trí riêng biệt sau đó sử dụng cùng một từ điển để gán vị trí cho khóa học? Về cơ bản, điều tương tự trừ đi phép nối bên trong có nghĩa là sql sẽ không chuyển nhiều byte?
MIKE

43

Không cần lookuptừ điển

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

3
Điều này là tuyệt vời - theo ý kiến ​​của tôi nên là câu trả lời được lựa chọn. Tuy nhiên, những người làm việc này coi chừng làm * vì điều đó có thể ảnh hưởng đến hiệu suất.
cr1pto

2
Vấn đề duy nhất là bạn sẽ sao chép tiêu đề trên mỗi bản ghi Vị trí. Nếu có nhiều vị trí trên mỗi khóa học, đó có thể là một lượng trùng lặp dữ liệu đáng kể đi qua dây sẽ làm tăng băng thông, mất nhiều thời gian hơn để phân tích / bản đồ và sử dụng nhiều bộ nhớ hơn để đọc tất cả điều đó.
Daniel Lorenz

10
Tôi không chắc chắn điều này đang hoạt động như tôi mong đợi. tôi có 1 đối tượng cha mẹ với 3 đối tượng liên quan. truy vấn tôi sử dụng được ba hàng trở lại. các cột đầu tiên mô tả cha mẹ được nhân đôi cho mỗi hàng; sự phân chia trên id sẽ xác định mỗi đứa trẻ duy nhất. Kết quả của tôi là 3 cha mẹ trùng lặp với 3 con .... nên là một bố mẹ có 3 con.
topwik

2
@topwik nói đúng. nó cũng không hoạt động như mong đợi đối với tôi.
Maciej Pszczolinski

3
Tôi thực sự đã kết thúc với 3 cha mẹ, mỗi đứa trẻ có 1 mã này. Không chắc chắn tại sao kết quả của tôi khác với @topwik, nhưng vẫn không hoạt động.
th3morg

29

Tôi biết tôi thực sự muộn với điều này, nhưng có một lựa chọn khác. Bạn có thể sử dụng QueryMultipl tại đây. Một cái gì đó như thế này:

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

3
Một điều cần lưu ý. Nếu có nhiều địa điểm / khóa học, bạn nên lặp qua các địa điểm một lần và đặt chúng vào tra cứu từ điển để bạn có N log N thay vì tốc độ N ^ 2. Làm cho một sự khác biệt lớn trong bộ dữ liệu lớn hơn.
Daniel Lorenz

6

Xin lỗi vì đến bữa tiệc muộn (như mọi khi). Đối với tôi, nó dễ sử dụng hơn Dictionary, như Jeroen K đã làm , về hiệu suất và khả năng đọc. Ngoài ra, để tránh nhân tiêu đề trên các vị trí , tôi sử dụng Distinct()để loại bỏ các dups tiềm năng:

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

Thiêu một thư gi đo. Nếu bạn không chỉ định từng trường Locationstrong truy vấn SQL, đối tượng Locationcó thể được điền. Hãy xem:

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;

Sử dụng l.*trong truy vấn, tôi có danh sách các vị trí nhưng không có dữ liệu.


0

Không chắc có ai cần nó không, nhưng tôi có phiên bản động của nó mà không có Model để mã hóa nhanh & linh hoạt.

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;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.