Как вернуть записи "один ко многим" в определенном порядке с помощью Dapper и мульти-картографирования?
Из Github:
Dapper позволяет сопоставить одну строку с несколькими объектами. Это ключевая функция, если вы хотите избежать посторонних запросов и активных загрузок.
Пример:
Рассмотрим 2 класса: почта и пользователь
> class Post {
> public int Id { get; set; }
> public string Title { get; set; }
> public string Content { get; set; }
> public User Owner { get; set; } }
>
> class User {
> public int Id { get; set; }
> public string Name { get; set; } }
Теперь давайте скажем, что мы хотим отобразить запрос, который объединяет как записи, так и таблицу пользователей. До сих пор, если бы нам нужно было объединить результат двух запросов, нам понадобился бы новый объект для его выражения, но в этом случае имеет больше смысла помещать объект User внутри объекта Post.
Когда я делаю это (мои классы имеют разные имена, но одну и ту же конструкцию), я получаю сообщение и пользователя, сообщение и пользователя. Я использую Web API, так что это все JSON, если это имеет значение. Вот как я мог бы это увидеть, если бы я выполнил прямой SQL в Management Studio, вы получите много строк и соответствующие записи пользователя.
Что делать, если я хочу отправить обратно JSON, в котором есть пользователь и все сообщения в массиве, затем следующий пользователь, массив сообщений и т. Д.
id title content id name
1 Article1 Content1 55 Smith
2 Article2 Content2 55 Smith
3 Article3 Content3 55 Smith
Я получаю обратно JSON, который снова и снова содержит информацию о пользователе (как и ожидалось, но не хотел). Это назад.
То, что я хочу, это объект JSON, который имеет такой формат (я думаю, что это правильно):
{
"User": 55,
"Name": "Smith",
"Post": [
{
"id": 1,
"title": "title1",
"content":"MyContent1"
},
{
"id": 2,
"title": "title2",
"content":"MyContent2"
},
{
"id": 3,
"title": "title3",
"content":"MyContent2"
}
]
}
Как мне это сделать? Прямо сейчас я получаю обратное. Я думал, что просто поменяю классы вокруг, но я не сделал этого из-за инструкций по Github, части "имеет больше смысла". Я использую это,
(List<Post>)db.Query<Post, User, Paper>(sqlString, (post, user) => { post.user = user; return post; }, splitOn: "id");
Я знаю, что здесь не нужен splitOn, но в моем реальном запросе имя отличается от id.
Это довольно близко:
https://www.tritac.com/developers-blog/dapper-net-by-example/
public class Shop {
public int? Id {get;set;}
public string Name {get;set;}
public string Url {get;set;}
public IList<Account> Accounts {get;set;}
}
public class Account {
public int? Id {get;set;}
public string Name {get;set;}
public string Address {get;set;}
public string Country {get;set;}
public int ShopId {get;set;}
}
var lookup = new Dictionary<int, Shop>()
conn.Query<Shop, Account, Shop>(@"
SELECT s.*, a.*
FROM Shop s
INNER JOIN Account a ON s.ShopId = a.ShopId
", (s, a) => {
Shop shop;
if (!lookup.TryGetValue(s.Id, out shop)) {
lookup.Add(s.Id, shop = s);
}
if (shop.Accounts == null)
shop.Accounts = new List<Account>();
shop.Accounts.Add(a);
return shop;
},
).AsQueryable();
var resultList = lookup.Values;
Это делает первый идентификатор объекта. Не уверен, смогу ли я использовать это так или нет. Но это делает массив книг, как я просил, и мне не нужно было создавать специальный объект. Первоначально он должен был быть в Google Code, но я не смог найти этот тест на Github.
2 ответа
Поскольку ваш SQL-запрос возвращает плоские записи, я предлагаю вам создать плоский POCO и использовать dapper для сопоставления результирующего набора с коллекцией этого. Если у вас есть данные в этой коллекции, вы можете использовать метод LINQ GroupBy, чтобы сгруппировать их так, как вы хотите.
Если у вас есть такие классы, как
public class User
{
public int Id { set;get;}
public string Name { set;get;}
public IEnumerable<Post> Posts { set;get;}
}
public class Post
{
public int Id { set;get;}
public string Title{ set;get;}
public string Content { set;get;}
}
Теперь создайте POCO для плоской строки набора результатов
public class UserPost
{
public int Id { set; get; }
public string Title { set; get; }
public string Content { set; get; }
public int UserId { set; get; }
public string Name { set; get; }
}
Теперь обновите ваш SQL-запрос, чтобы он возвращал результирующий набор с именем столбца, соответствующим указанным выше свойствам.
Теперь используйте Dapper, чтобы получить плоские записи
var userposts= new List<UserPost>();
using (var conn = new SqlConnection("YourConnectionString"))
{
userposts = conn.Query<UserPost>(query).ToList();
}
Теперь примените GroupBy
var groupedPosts = userposts.GroupBy(f => f.UserId, posts => posts, (k, v) =>
new User()
{
UserId = k,
Name = v.FirstOrDefault().Name,
Posts = v.Select(f => new Post() { Id = f.Id,
Title= f.Title,
Content = f.Content})
}).ToList();
Другим вариантом является использование. QueryMultiple
[Test]
public void TestQueryMultiple()
{
const string sql = @"select UserId = 55, Name = 'John Doe'
select PostId = 1, Content = 'hello'
union all select PostId = 2, Content = 'world'";
var multi = _sqlConnection.QueryMultiple(sql);
var user = multi.Read<User>().Single();
user.Posts = multi.Read<Post>().ToList();
Assert.That(user.Posts.Count, Is.EqualTo(2));
Assert.That(user.Posts.First().Content, Is.EqualTo("hello"));
Assert.That(user.Posts.Last().Content, Is.EqualTo("world"));
}
Обновить:
Чтобы вернуть нескольких пользователей и их сообщения:
[Test]
public void TestQueryMultiple2()
{
const string sql = @"select UserId = 55, Name = 'John Doe'
select UserId = 55, PostId = 1, Content = 'hello'
union all select UserId = 55, PostId = 2, Content = 'world'";
var multi = _sqlConnection.QueryMultiple(sql);
var users = multi.Read<User>().ToList();
var posts = multi.Read<Post>().ToList();
foreach (var user in users)
{
user.Posts.AddRange(posts.Where(x => x.UserId == user.UserId));
}
Assert.That(users.Count, Is.EqualTo(1));
Assert.That(users.First().Posts.First().Content, Is.EqualTo("hello"));
Assert.That(users.First().Posts.Last().Content, Is.EqualTo("world"));
}