Многие ко многим 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 шт):
Во-первых, я бы посоветовал избавиться от лишнего запроса в БД: 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. Таким образом не запрашивается ничего лишнего.