简体   繁体   English

在 Entity Framework Core/LINQ 中对 1:M:M:1 相关数据进行分组和扁平化

[英]grouping and flatening 1:M:M:1 related data in Entity Framework Core/LINQ

In a small ASP.NET Core Web API using Entity Framework and LINQ I want to achieve a method which queries to which rooms a person needs access to.在一个使用 Entity Framework 和 LINQ 的小型 ASP.NET Core Web API 中,我想实现一种方法来查询一个人需要访问的房间。 The desired result for a single person looks like that一个人想要的结果是这样的


{
  "id": 1,
  "firstname": "First1",
  "lastname": "Last1",
  "rooms": [
      {
        "id": 1,
        "name": "test room 1"
      },
      {
        "id": 2,
        "name": "test room 2"
      },
      {
        "id": 3,
        "name": "test room 3"
      }
    ]
}

Now the rooms are not directly assigned to a person but a person has multiple tasks (each task can have only one person), each task requieres access to multiple rooms (so there is a M:N between tasks and rooms).现在房间不是直接分配给一个人而是一个人有多个任务(每个任务只能有一个人),每个任务都需要访问多个房间(所以任务和房间之间有一个M:N)。 And the rooms table stores the room id and name.房间表存储房间 id 和名称。

So I have 4 tables:所以我有4张桌子:

  1. Person (id, firstname, lastname)人(身份证,名字,姓氏)
  2. Task (id, personId, name)任务(id、personId、姓名)
  3. TaskRoom (id, roomId, taskId)任务室(id、roomId、taskId)
  4. Room (id, name) -> the data that should be displayed Room (id, name) -> 应该显示的数据

The connections between the tables are:表之间的连接是:

  • Person 1:M Task人 1:M 任务
  • Task 1:M TaskRoom任务 1:M 任务室
  • TaskRoom N:1 Room任务室 N:1 间

My current methods looks like that我目前的方法看起来像这样


[HttpGet("RoomAccess/{personId:int}")]
public IActionResult RoomAccess(int personId)
{
    _logger.LogDebug(string.Format("{0}.RoomAccess(person id: {1})", GetType().Name, personId));

    var result = _dbContext.Person
        .Include(p => p.Task).ThenInclude(t => t.TaskRoom).ThenInclude(tr => tr.Room)
        .Where(p => p.Id == personId)
        .Select(person => new
        {
            person.Id,
            person.Firstname,
            person.Lastname,

            // how to do that line?
            rooms = person.Task.Select(ta => ta.TaskRoom.Select(tr => new {id = tr.Room.Id, name = tr.Room.Name} ) )
        }
    )
    .FirstOrDefault();

    if (result == null) 
    {
        return NotFound(string.Format("person id: {0}", personId));
    }

    return Ok(result);
}

And the result so far:到目前为止的结果:


{
  "id": 1,
  "firstname": "First1",
  "lastname": "Last1",
  "rooms": [
    [
      {
        "id": 1,
        "name": "test room 1"
      }
    ],
    [
      {
        "id": 2,
        "name": "test room 2"
      },
      {
        "id": 3,
        "name": "test room 3"
      }
    ],
    [
      {
        "id": 3,
        "name": "test room 3"
      }
    ],
    []
  ]
}

Here are also empty brackets [] at the end for a task to which no room is assigned (so far).对于尚未分配空间的任务(到目前为止),这里还有末尾的空括号[] And test room 3 occurs multiple times since two different tasks require access to this room.并且测试室 3 出现多次,因为两个不同的任务需要访问这个房间。

How do I get a grouped list of related rooms holding only the id and name of the room (like in the example for the desired result)如何获得仅包含房间 id 和名称的相关房间的分组列表(如所需结果的示例)

Any help is welcome.欢迎任何帮助。


Now there is an open issue on EF Core Github现在EF Core Github上有一个未解决的问题


Answer回答

Well the working code is:那么工作代码是:


[HttpGet("RoomAccess/{personId:int}")]
        public IActionResult RoomAccess(int personId)
        {
            _logger.LogDebug(string.Format("{0}.RoomAccess(person id: {1})", GetType().Name, personId));

            var result = _dbContext.Person
                .Include(p => p.Task).ThenInclude(t => t.TaskRoom).ThenInclude(tr => tr.Room)
                .Where(p => p.Id == personId)
                .ToList()
                .Select(person => new
                    {
                        person.Id,
                        person.Firstname,
                        person.Lastname,

                        rooms = person.Task.SelectMany(ta => ta.TaskRoom.Select(
                            tr => new { id = tr.Room.Id, name = tr.Room.Name })
                        )
                        .Distinct()
                        .OrderBy(adt => adt.name)
                    }
                )
                .FirstOrDefault();


            if (result == null) 
            {
                return NotFound(string.Format("person id: {0}", personId));
            }

            return Ok(result);
        }

In addition to the answer of Nkosi it is important to use Include() and ThenInclude() as well as .ToList() after the Where().除了 Nkosi 的答案之外,在 Where() 之后使用 Include() 和 ThenInclude() 以及 .ToList() 也很重要。

Use the SelectMany extension to flatten the lists.使用SelectMany扩展来展平列表。

//...
rooms = person.Task
    .SelectMany(ta => ta.TaskRoom.Select(tr => new {id = tr.Room.Id, name = tr.Room.Name}))
    .Distinct()
//...

Not sure how it will get transposed in EF though and any performance impact.不确定它将如何在 EF 中转置以及任何性能影响。

Note the use of Distinct to remove any duplicates.请注意使用Distinct删除任何重复项。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM