Как мне сопоставить списки вложенных объектов с помощью Dapper
В настоящее время я использую 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 выполняет сопоставление для меня, поэтому мой объект Course заполняется списком местоположений. Как мне поступить с Dapper, возможно ли это вообще, или мне нужно сделать это за несколько шагов?
8 ответов
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
трюк будет работать, если у вас меньше 2100 просмотров (Sql Server), если у вас больше, вы, вероятно, захотите изменить запрос на 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();
}
Извините, что опоздал на вечеринку (как всегда). Для меня проще использовать словарь, как @Jeroen-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;
}
Чего-то не хватает. Если вы не укажете каждое поле из Locations в запросе SQL, объект 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;
Есть еще один подход с использованием результата JSON. Принятый ответ и другие - это наиболее удачные варианты, а также хорошее объяснение подхода. Но я просто думаю о другом подходе, как о следующем
Создайте хранимую процедуру или выберите qry, чтобы вернуть результат в формате json. затем десериализуйте объект результата в требуемый формат класса. просмотрите пример кода.
using (var db = connection.OpenConnection())
{
var results = await db.QueryAsync("your_sp_name",..);
var result = results.FirstOrDefault();
string Json = result?.your_result_json_row;
if (!string.IsNullOrEmpty(Json))
{
List<Course> Courses= JsonConvert.DeserializeObject<List<Course>>(Json);
}
//map to your custom class and dto then return the result
}
Это еще один мыслительный процесс. Пожалуйста, просмотрите то же самое.