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