Dapper ile iç içe geçmiş nesnelerin listelerini nasıl eşlerim


128

Şu anda db erişimim için Entity Framework kullanıyorum ancak Dapper'a bir göz atmak istiyorum. Bunun gibi derslerim var:

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

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

Yani bir kurs birkaç yerde öğretilebilir. Entity Framework eşlemeyi benim için yapar, böylece Course nesnem bir konum listesiyle doldurulur. Bunu Dapper ile nasıl hallederim, mümkün mü yoksa bunu birkaç sorgu adımında yapmak zorunda mıyım?



İşte benim çözümüm: stackoverflow.com/a/57395072/8526957
Sam Sch

Yanıtlar:


57

Dapper tam gelişmiş bir ORM değildir, sihirli nesil sorguları ve benzerlerini işlemez.

Sizin özel örneğiniz için aşağıdakiler muhtemelen işe yarayacaktır:

Kursları alın:

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

İlgili haritayı alın:

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

İlgili yerleri alın

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

Hepsini eşleştirin

Bunu okuyucuya bırakarak, birkaç harita oluşturursunuz ve kurslarınızı konumlarla doldurarak yinelersiniz.

Caveatin Eğer az varsa hüner çalışacaktır 2100 aramalarını (SQL Server) Eğer varsa, daha büyük olasılıkla sorgu değişiklik istiyorum select * from CourseLocations where CourseId in (select Id from Courses ... )kullanarak İnternet'e ki siz de hepsi bir arada sonuçlarını yank olabilir durum buysaQueryMultiple


Açıklama için teşekkürler Sam. Yukarıda tanımladığınız gibi, Konumları getiren ve bunları derse manuel olarak atayan ikinci bir sorgu çalıştırıyorum. Sadece tek bir sorgu ile yapmama izin verecek bir şeyi kaçırmadığımdan emin olmak istedim.
b3n

2
Sam, koleksiyonların düzenli olarak etki alanı nesneleri üzerinde örnekte olduğu gibi sergilendiği büyük bir uygulamada, bu kodun fiziksel olarak nereye yerleştirilmesini önerirsiniz ? ( Kodunuzdaki birçok farklı yerden benzer şekilde tamamen oluşturulmuş bir [Course] varlığını kullanmak isteyeceğinizi varsayarsak) Yapıcıda mı ? Sınıf fabrikasında mı? Başka bir yer?
tbone

178

Alternatif olarak, aramayla birlikte tek bir sorgu da kullanabilirsiniz:

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;

Buraya bakın https://www.tritac.com/blog/dappernet-by-example/


9
Bu bana çok zaman kazandırdı. Başkalarının ihtiyaç duyabileceği bir değişiklik, varsayılan "Id" yi kullanmadığım için splitOn: argümanını eklemekti.
Bill Sambrone

1
LEFT JOIN için konum listesinde boş bir öğe alacaksınız. Bunları var items = lookup.Values ​​ile kaldırın; items.ForEach (x => x.Locations.RemoveAll (y => y == null));
Choco Smith

1. satırın sonunda noktalı virgül yoksa ve 'AsQueryable ()' önündeki virgül kaldırılmadıkça bunu derleyemem. Cevabı düzenlerdim ama önümdeki 62 oylayıcı sorun olmadığını düşünüyor gibiydi, belki bir şeyi kaçırıyorum ...
bitcoder

1
LEFT JOIN için: Başka bir Foreach yapmanıza gerek yok. Eklemeden önce kontrol edin: if (l! = Null) course.Locations.Add (l).
jpgrassi

1
Sözlük kullandığından beri. QueryMultiple'ı kullanıp dersi ve konumu ayrı ayrı sorguladıktan sonra aynı sözlüğü kursa yer atamak için kullansaydınız bu daha hızlı olur muydu? Temelde aynı şey eksi iç birleşim, yani sql çok bayt transfer etmeyecek mi?
MIKE

43

Gerek yok lookupSözlük

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
Bu mükemmel - bence seçilen cevap bu olmalı. Ancak bunu yapan insanlar, performansı etkileyebileceğinden * yapmaya dikkat ederler.
cr1pto

2
Bununla ilgili tek sorun, başlığı her Konum kaydına kopyalayacak olmanızdır. Kurs başına çok sayıda konum varsa, bant genişliğini artıracak, ayrıştırması / eşlemesi daha uzun sürecek ve bunların tümünü okumak için daha fazla bellek kullanacak kablo boyunca önemli miktarda veri çoğaltması olabilir.
Daniel Lorenz

10
Bunun beklediğim gibi çalıştığından emin değilim. 3 ilgili nesneye sahip 1 üst nesnem var. kullandığım sorgu üç satır geri alıyor. her satır için çoğaltılan ebeveyni tanımlayan ilk sütunlar; kimlikteki bölünme, her bir benzersiz çocuğu tanımlayacaktır. sonuçlarım 3 çocuklu 3 yinelenen ebeveyn .... 3 çocuklu bir ebeveyn olmalıdır.
topwik

2
@topwik haklı. benim için de beklendiği gibi çalışmıyor.
Maciej Pszczolinski

3
Aslında bu kodla her birinde 1 çocuk olmak üzere 3 ebeveynle sonuçlandım. Sonucumun neden @topwik'ten farklı olduğundan emin değilim, ama yine de işe yaramıyor.
th3morg

29

Buna gerçekten geç kaldığımı biliyorum ama başka bir seçenek daha var. QueryMultiple'ı burada kullanabilirsiniz. Bunun gibi bir şey:

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
Dikkat edilmesi gereken bir şey. Çok fazla yer / kurs varsa, konumlar arasında bir kez dönmeli ve bunları bir sözlük aramasına koymalısınız, böylece N ^ 2 hızı yerine N log N'ye sahip olursunuz. Daha büyük veri kümelerinde büyük fark yaratır.
Daniel Lorenz

6

Partiye geç kaldığım için üzgünüm (her zamanki gibi). Benim için bu bir kullanımı daha kolay Dictionary, Jeroen K yaptığı gibi performans ve okunabilirlik açısından. Ayrıca, konumlar arasında başlık çarpımını önlemek Distinct()için olası kopyaları kaldırmak için kullanıyorum :

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

Bir şey eksik. LocationsSQL sorgusundaki her alanı belirtmezseniz , nesne Locationdoldurulamaz. Bir göz at:

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.*Sorguda kullanarak , konumların listesine sahiptim, ancak veri yoktu.


0

Kimsenin buna ihtiyacı olup olmadığından emin değilim, ancak hızlı ve esnek kodlama için Model olmadan dinamik versiyonuna sahibim.

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;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.