Как вернуть записи "один ко многим" в определенном порядке с помощью 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"));
    }
Другие вопросы по тегам