web-dev-qa-db-fra.com

Comment mapper des listes d'objets imbriqués avec Dapper

J'utilise actuellement Entity Framework pour mon accès à la base de données, mais j'aimerais jeter un coup d'œil à Dapper. J'ai des cours comme ça:

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

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

Donc, un cours peut être enseigné à plusieurs endroits. Entity Framework effectue le mappage pour moi afin que mon objet Cours soit rempli avec une liste d'emplacements. Comment ferais-je avec Dapper, est-ce même possible ou dois-je le faire en plusieurs étapes de requête?

92
b3n

Dapper n'est pas un ORM complet, il ne gère pas la génération magique de requêtes et autres. 

Pour votre exemple particulier, ce qui suit fonctionnerait probablement:

Prenez les cours:

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

Prenez la cartographie correspondante:

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

Prenez les endroits appropriés

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

Cartographiez le tout

En laissant cela au lecteur, vous créez quelques cartes et parcourez vos cours en fonction de leur emplacement. 

Caveat l'astuce in fonctionnera si vous avez moins de 2100 lookups (Sql Server), si vous en avez plus, vous voudrez probablement modifier la requête en select * from CourseLocations where CourseId in (select Id from Courses ... ) si c'est le cas, vous pouvez aussi tout supprimer. les résultats en une fois en utilisant QueryMultiple

42
Sam Saffron

Alternativement, vous pouvez utiliser une requête avec une recherche:

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;

Voir ici https://www.tritac.com/blog/dappernet-by-example/

138
Jeroen K

Pas besoin de 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();
23
tchelidze

Je sais que je suis vraiment en retard pour cela, mais il y a une autre option. Vous pouvez utiliser QueryMultiple ici. Quelque chose comme ça:

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

Quelque chose manque. Si vous ne spécifiez pas chaque champ à partir des emplacements dans la requête SQL, l'objet Emplacement ne peut pas être renseigné. Regarde:

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;

En utilisant "l. *" Sur une requête, j'avais la liste des emplacements mais sans données. 

2
Eduardo Pires

Je ne sais pas si quelqu'un en a besoin, mais j'en ai une version dynamique sans Model pour un codage rapide et flexible.

        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;
0
Kiichi

Désolé d'être en retard à la fête (comme toujours). Pour moi, il est plus facile d'utiliser un dictionnaire comme @ Jeroen-k, en termes de performances et de lisibilité, ainsi que pour éviter la multiplication d'en-têtes entre " emplacements ", j'utilise Distinct pour supprimer les erreurs potentielles:

        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;
        }
0
Francisco Tena