Многие ко многим LINQ

У меня есть несколько сущностей связанных друг с другом.

public class User : IdentityUser
{
    public List<Workout> Workouts { get; set; } = new List<Workout>();
}

public class Workout
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Exercise> Exercises { get; set; } = new List<Exercise>();
    public List<User> Users { get; set; } = new List<User>();
}

public class MuscleGroup
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Exercise> Exercises { get; set; }
}

public class Exercise
{
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    [Required]
    public int MuscleGroupId { get; set; }
    public MuscleGroup MuscleGroup { get; set; }
    public List<Workout> Workouts { get; set; } = new List<Workout>();
}

Есть GET в контроллере.

[HttpGet]
public async Task<ActionResult> Get()
{
    var userId = User.Identity.Name;
    if (await db.Users.FirstOrDefaultAsync(u => u.Id == userId) == null)
    {
        BadRequest();
    }

    var works = db.Users
        .Where(x => x.Id == userId)
        .Include(x => x.Workouts)
        .ThenInclude(x => x.Exercises)
        .ToList();
    return Ok(works);
}

Сейчас запрос выдает много лишнего, например данные по пользователю.

[
  {
    "workouts": [
      {
        "id": 1,
        "name": "Тренировка1",
        "exercises": [
          {
            "id": 2,
            "name": "упражнение 1",
            "muscleGroupId": 2,
            "muscleGroup": null,
            "workouts": []
          },
          {
            "id": 3,
            "name": "упражнение 2",
            "muscleGroupId": 3,
            "muscleGroup": null,
            "workouts": []
          }
        ],
        "users": []
      }
    ],
    "id": "365yh7d3-a625-4032-a36c-4bagfg83be6",
    "userName": "mardul",
    "normalizedUserName": "XXXXX",
    "email": "[email protected]",
    "normalizedEmail": "[email protected]",
    "emailConfirmed": false,
    "passwordHash": "ddddddddddddddddddddddddddddddddddddddddddddddd",
    "securityStamp": "dddddddddddddddddddddd",
    "concurrencyStamp": "dddddddddddddddddddd",
    "phoneNumber": null,
    "phoneNumberConfirmed": false,
    "twoFactorEnabled": false,
    "lockoutEnd": null,
    "lockoutEnabled": true,
    "accessFailedCount": 0
  }
]

А мне нужно по текущему пользователю (User.Identity.Name) получить список его тренировок со всеми значениями.
Например.

Тренировка: Тренировка 1
    упражнения:
       1. упражнение 1
       2. упражнение 3
       3. упражнение 5

Тренировка: Тренировка 2
    упражнения:
       1. упражнение 4
       2. упражнение 1
       3. упражнение 6

Использую Microsoft.EntityFrameworkCore.Sqlite 5.0.10

UPD

Почитал что такое DTO и для чего оно нужно. В итоге я практически получил необходимые данные, но мое решение не совсем правильное, должен быть более элегантный способ. Буду очень рад если кто-нибудь расскажет как грамотно получить данные из базы.

Итак, я создал два DTO:

public class WorkoutDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<ExerciseDto> Exercises { get; set; }
}

public class ExerciseDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string MuscleGroupName { get; set; }
}

Контроллер теперь выглядит так:

[HttpGet]
public async Task<ActionResult> Get()
{
    var userId = User.Identity.Name;
    if (await db.Users.FirstOrDefaultAsync(u => u.Id == userId) == null)
    {
        BadRequest();
    }

    var userWorkouts = db.Users
       .Include(x => x.Workouts)
       .ThenInclude(x => x.Exercises)
       .ThenInclude(x => x.MuscleGroup)
       .FirstOrDefault(x => x.Id == userId);

    List<WorkoutDto> workoutDtoList = new List<WorkoutDto>();

    foreach (var workout in userWorkouts.Workouts)
    {
        workoutDtoList.Add(new WorkoutDto
        {
            Id = workout.Id,
            Name = workout.Name,
            Exercises = new List<ExerciseDto>()
        });
        foreach (var exercise in workout.Exercises)
        {
            foreach (var workoutDto in workoutDtoList)
            {
                workoutDto.Exercises.Add(new ExerciseDto
                {
                    Id = exercise.Id,
                    Name = exercise.Name,
                    MuscleGroupName = exercise.MuscleGroup.Name
                });
            }
        }
    }
    return Ok(workoutDtoList);
}

На выходе получил:

[
    {
        "id": 1,
        "name": "Тренировка1",
        "exercises": [
            {
                "id": 2,
                "name": "тяга верхнего блока",
                "muscleGroupName": "Грудь"
            },
            {
                "id": 3,
                "name": "гантели",
                "muscleGroupName": "Бицепс"
            },
            {
                "id": 1,
                "name": "подъем ног",
                "muscleGroupName": "Ноги"
            },
            {
                "id": 2,
                "name": "тяга верхнего блока",
                "muscleGroupName": "Грудь"
            }
        ]
    },
    {
        "id": 2,
        "name": "Тренировка2",
        "exercises": [
            {
                "id": 1,
                "name": "подъем ног",
                "muscleGroupName": "Ноги"
            },
            {
                "id": 2,
                "name": "тяга верхнего блока",
                "muscleGroupName": "Грудь"
            }
        ]
    }
]

Ответы (1 шт):

Автор решения: Alexander Petrov

Во-первых, я бы посоветовал избавиться от лишнего запроса в БД: if (await db.Users.FirstOrDefaultAsync(u => u.Id == userId) == null) - потому что это очень "дорого".

Достаточно проверить что вернул другой запрос и на основании этого вернуть BadRequest или Ok.


var works = db.Users
    .Where(x => x.Id == userId)
    .Select(x => new
    {
        Workouts = x.Workouts.Select(w => new
        {
            Name = w.Name,
            Exercises = w.Exercises.Select(e => e.Name)
        })
    })
    .ToList();

if (works.Count == 0)
{
    return BadRequest();
}

Этот запрос вернёт ровно те данные, что были указана в первой версии вашего вопроса.

var works = db.Users
    .Where(x => x.Id == userId)                    
    .Select(x => new
    {
        Id = x.Id,
        Workouts = x.Workouts.Select(w => new
        {
            Id = w.Id,
            Name = w.Name,
            Exercises = w.Exercises.Select(e => new
            {
                Id = e.Id,
                Name = e.Name,
                MuscleGroupName = e.MuscleGroup.Name
            }),
        })
    })
    .ToList();

Этот запрос вернёт также данные MuscleGroup.


В данном случае использованы анонимные типы. Но можно применить DTO. Это не критично.

Обратите внимание, что в этих запросах можно не указывать Include, ThenInclude. Таким образом не запрашивается ничего лишнего.

→ Ссылка