Кажется, что каждый Profile
имеет ноль или более Courses
, а каждый Course
принадлежит нулю или более Profiles
: прямое отношение «многие ко многим».
Обычно вВ реляционной базе данных это будет реализовано с использованием соединительной таблицы: отдельной таблицы с двумя столбцами, которые действуют как внешние ключи: ProfileId и CourseId.В вашем примере:
- Для Зишана, у которого есть Id 1, у нас были бы [1, 1] и [1, 2], что означает, что Зишан посещает курсы с Id 1 и 2;
- Для Эллен с Id = 2 у нас будет [2, 2] [2, 3] [2,4].
Увы, ваш дизайнер баз данных решил не следоватьэто стандартный шаблон базы данных.И вы застряли с проблемами.
Лучшее решение зависит немного от того, как долго вы будете зависать с этой базой данных, как часто она будет меняться, как часто вы будете запрашивать «Профили с их курсами» и«Курсы с их помощниками».
Лучшее решение: добавить таблицу соединений в базу данных
Лучшим решением будет однократная миграция вашей базы данных: создайте таблицу соединений и добавьте[profile.Id, Course.Id]
комбинаций в этом списке.После этого удалите столбец CourseId.
class ProfileCourse
{
public int ProfileId {get; set;}
public int CourseId {get; set;}
}
var ProfileIdCourseIdCombinations = db.Profiles
.SelectMany(profile => profile.CourseIds.Split(',", StringSplitOptions.RemoveEmptyEntries),
(profile, splitCourseId) => new ProfileCourse
{
ProfileId = profile.Id,
CourseId = splitCourseId,
});
Это нужно сделать только один раз.Подобные запросы в будущем будут достаточно стандартными.Каждый, кто свободно владеет LINQer, будет знать, как реализовать запросы.Недостаток: изменение базы данных.
Неоптимальное решение: имитировать соединительную таблицу
Довольно часто люди имеют промежуточный (адаптерный) уровень между вариантами использования и реальной базой данных.Таким образом, вы можете изменить базу данных без необходимости менять пользователей или наоборот.Шаблон для этого уровня адаптера довольно часто называется шаблоном репозитория.
Идея состоит в том, что вы создаете класс Repository, который представляет ваши таблицы как последовательность элементов IEnumerable.Соединительная таблица не будет реальной таблицей, она будет создаваться всякий раз, когда ее запрашивают.
class Repository
{
public Respository(MyDbContext db)
{
this.db = db;
}
private readonly MyDbContext db;
private IReadonlyCollection<ProfileCourse> profilesCourses = null;
public IEnumerable<Profile> Profiles => this.db.Profiles;
public IEnumerable<Course> Courses => this.db.Courses;
public IEnumerable<ProfileCourse> ProfilesCourses
{
get
{
if (this.profilesCourses == null
{
this.profilesCourses = ... // the SelectMany from above
.ToList();
}
return this.profilesCourses;
}
}
}
Таким образом, ваша соединительная таблица будет создаваться только один раз за каждый раз, когда вы создаете свой репозиторий.Он будет создан только при использовании.
Это решение является неоптимальным, поскольку таблицу соединений необходимо пересоздавать каждый раз, когда вы будете использовать ее в новом репозитории.Кроме того, вы не можете добавлять профили или курсы, используя этот класс репозитория.Если будет довольно много работы по созданию функций для добавления / удаления профилей и курсов в ваш репозиторий.Возможно, будет проще воссоздать хранилище.
Вы получите ответ, спросив хранилище вместо вашего db
var repository = new Repository(db);
var profilesWithTheirCourses = repository.Profiles.GroupJoin(repository.ProfilesCourses,
profile => profile.Id, // from every profile take the Id
profileCourse => profileCourse.ProfileId, // from every profileCourse take the ProfileId,
(profile, profileCourses) => new // take every profile with all its matching
{ // profileCourses, to make a new obhect
// Profile properties; add only those you plan to use:
Id = profile.Id,
Name = profile.Name,
Courses = profileCourses.Join(repository.Courses, // join profileCourse with courses
profileCourse => profileCourse.CourseId, // from profileCourse take CourseId
course => course.Id, // from course take Id
(profileCourse, course) => new // when they match make a new
{ // again: only the properties you actually plan to use
Id = course.Id,
Name = course.Name,
})
.ToList(),
});
Решение только для этой проблемы
Есливы решили решить только эту проблему, вы можете объединить SelectMany
и Join
в один большой оператор LINQ.
Преимущество: быстрое решение;Недостаток: трудно читать, тестировать и поддерживать.Подобные проблемы в будущем будут иметь аналогичное неоптимальное решение,
var profilesWithTheirCourses = db.Profiles.SelectMany(
profile => profile.CourseIds.Split(',", StringSplitOptions.RemoveEmptyEntries),
(profile, splitCourseIds) => new
{
Id = profile.Id,
Name = profile.Name,
Courses = splitCourseIds.Join(db.Courses,
courseId => courseId,
course => course.Id,
(courseId, course) => new
{
Id = course.Id,
Name = course.Name,
})
.ToList(),
});