Лучший подход для отношений "многие ко многим" в Entity Framework сначала
Я пересматривал способ использования сущностей, и мне нужен совет.
Я создаю сервис бронирования йоги, и у каждого пользователя будет свой профиль, места для встреч для йоги и события, с указанием даты и времени, для каждого места, которое нужно встретить.
Так что мои отношения выглядят так, и все они один-ко-многим
YogaProfile -> YogaSpace(s) -> YogaSpaceEvent(s)
Когда событие создается, участники (YogaProfiles
) может присоединиться к любому событию. Вроде как класс. Это система регистрации / планирования.
Первоначально я создал таблицу под названием RegisterdStudents
и добавил коллекцию в YogaSpaceEvent
, Как это
public class YogaSpaceEvent
{
// other code here left out
public virtual ICollection<RegisteredStudent> RegisteredStudents { get; set; }
}
а также RegisteredStudent
выглядит так
public class RegisteredStudent
{
[Key]
public int RegisteredStudentId { get; set; }
[Index]
public int YogaSpaceEventId { get; set; }
[ForeignKey("YogaSpaceEventId")]
public virtual YogaSpaceEvent YogaSpaceEvent { get; set; }
[Index]
public int StudentId { get; set; }
}
Все это прекрасно работает, но потом я узнал больше о "многие ко многим" и подумал, что мне может понадобиться использовать его здесь, поскольку многие профили могут регистрироваться для одного события, а многие события могут быть зарегистрированы в одном профиле, например. у класса может быть много посещений, и студент может посещать много классов.
Таким образом, я изменил код, чтобы сделать отношение многие ко многим, создав virtual ICollection
на каждой из двух сущностей (YogaProfile
, YogaSpaceEvent
) и создание таблицы соединений RegisteredStudentInEvent
как это
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<YogaProfile>()
.HasMany<YogaSpaceEvent>(x => x.YogaSpaceEvents)
.WithMany(x => x.RegisteredStudents)
.Map(x =>
{
x.MapLeftKey("YogaProfileId");
x.MapRightKey("YogaSpaceEventId");
x.ToTable("RegisteredStudentInEvent");
});
}
Теперь я могу успешно добавить несколько студентов (YogaProfile
) в один класс (YogaSpaceEvent
) и в таблице я вижу строки с событием (YogaSpaceEventId
) и кто зарегистрирован (YogaProfileId
).
Но теперь, глядя на эту настройку отношений "многие ко многим", я вижу, что НИКОГДА не нужно добавлять несколько классов (YogaSpaceEvent
) студенту (YogaProfile
) как ниже, потому что YogaSpaceEvents
добавляется в коллекцию на YogaSpaces
не YogaProfile
yogaProfile.YogaSpaceEvents.Add(yogaSpaceEvent)
Итак, мой вопрос, должен ли я вернуться к первоначальному способу, которым я это делал, или остаться с этой моделью "многие ко многим"? Какая разница, плюсы, минусы и т. Д.?
2 ответа
Поправьте меня, если я ошибаюсь, но мне кажется, что Профиль представляет Персона. Событие представляет собой что-то вроде занятия йогой в течение определенного времени, когда профиль может присутствовать. А Космос - это место, где происходит это Событие.
Между Пространством и Событием существует отношение один ко многим: в одном месте может быть проведено много событий. Каждое событие происходит ровно в одном месте.
Между Профилем и Событием существует отношение "многие ко многим": каждый Профиль может посещать множество Событий, а каждое Событие может посещать множество Профилей.
Например, событие E1 проводится в пространстве S1. Каждый профиль, который решает посетить событие E1, ему следует перейти в пространство S1. Это не должно быть записано в профиле. Если Событие E1 перемещено в другое Пространство, скажем, S2, вам нужно изменить только одну запись: ту, которая связывает Событие и Пространство. Это одно изменение приведет к тому, что все профили, которые посещают E1, могут видеть, что они должны перейти на S2, хотя ничего в профилях не изменилось.
Поэтому я не понимаю, зачем вам нужна связь между профилями и пробелами. Я уверен, что для службы бронирования в четко определенной базе данных лучше не иметь такой связи между профилями и пробелами
Так:
- Пространство имеет много событий, каждое событие проводится в одном пространстве: один ко многим
- Профиль посещает многие мероприятия; Событие посещают многие Профили: многие ко многим
- Нет прямой связи между Профилем и Пространством
Это дает вам классы, подобные следующим.
class Space
{
public int Id {get; set;}
// zero or more Events are held at this Space:
public virtual ICollection<Event> Events {get; set;}
... // other properties
}
class Event
{
public int Id {get; set;}
// every event takes place at exactly one Space using foreign key
public int SpaceId {get; set;}
public virtual Space Space {get; set;}
// every Event is attended by zero or more Profiles (many-to-many)
public virtual ICollection<Profile> Profiles {get; set;}
public DateTime StartTime {get; set;}
public TimeSpan Duration {get; set;}
... // other properties
}
class Profile
{
public int Id {get; set;}
// every Profile attend zero or more Events (many-to-many)
public virtual ICollection<Event> Events {get; set;}
... // other properties
}
Наконец, DbContext:
public MyDbContext : DbContext
{
public DbSet<Event> Events {get; set;}
public DbSet<Space> Spaces {get; set;}
public DbSet<Profile> Profiles {get; set;}
}
Поскольку я следовал первым соглашениям, касающимся кода структуры сущностей, это все, что нужно Entity Framework, чтобы знать, что вы предполагали отношения "один ко многим" и "многие ко многим". Entity Framework создаст внешние ключи и дополнительные соединительные таблицы, необходимые для отношений "многие ко многим". Нет необходимости в Атрибутах или Свободном API. Только если вам нужны разные имена таблиц или столбцов, вам понадобится свободный API или атрибуты.
Нет никакой гарантии, что два разных События проводятся в одном и том же Пространстве одновременно. Если вы хотите, вы должны указать каждому пробелу ноль или более временных интервалов (каждый временной интервал принадлежит ровно одному пробелу).
Чтобы сделать вещи проще, я делаю каждый временной интервал один час. События, длящиеся более одного часа, требуют нескольких временных интервалов
class TimeSlot
{
public int Id {get; set;}
public long Hour {get; set;}
// TODO: create a function from Hour to DateTime
// every TimeSlot is a TimeSlot of exactly one Space using foreign key
public int SpaceId {get; set;}
public virtual Space Space {get; set;}
// every TimeSlot is used by one Event using foreign key
public int EventId {get; set;}
public Event Event {get; set;}
}
Теперь событие проводится в одном или нескольких временных интервалах (которые являются временными интервалами определенного пространства). Таким образом, многочасовое мероприятие может проводиться в одном или нескольких пространствах, по вашему желанию.
Чтобы создать событие в определенном пространстве в определенное время, спросите пространство, есть ли у него временные интервалы в запрошенные часы. Если это так, пространство занято. Если нет, пространство бесплатно.
Поэтому, если я хочу создать Событие, например, вечерний курс, который длится 3 вечера среды с 20:00 до 21:00, начиная с 1 мая 2018 года, преобразуйте эти три раза в Часы:
IEnumerable<long> hours = ... // the three times the course will start
// find a space that has not any TimeSlots during these hours
Space freeSpace = myDbContext.Spaces
.Where(space => !space.TimeSlots
.Select(timeSlot => timeSlot.Hour)
.Union(hour)
.Any())
.FirstOrDefault();
if (freeSpace != null
{ // this Space has no TimeSlot at any of the three hours,
// create an time slots in this Space
IEnumerable<TimeSlot> timeSlots = myDbContext.TimeSlots.AddRange(hours
.Select(hour => new TimeSlot()
{
Hour = hour,
Space = freeSpace,
})
.ToList());
// create an Event to be held at these TimeSlots (and thus at the FreeSpace
var event = myDbContext.Add(new Event()
{
TimeSlots = timeSlots.ToList();
Description = ...
...
});
Чтобы следующие профили присутствовали на этом мероприятии:
IEnumerable<Profile> profilesWishingToAttend = ...
foreach (Profile profileWishingToAttend in profilesWishingToAttent)
{
profileWishingToAttend.Events.Add(event);
}
Вместо добавления события в профили, вы можете добавить профили к событию:
IEnumerable<Profile> profilesWishingToAttend = ...
foreach (Profile profileWishingToAttend in profilesWishingToAttent)
{
event.Profiles.Add(profileWishingToAttend);
}
После дальнейших поисков и открытий я думаю, что это может быть лучшим решением.
Оставьте мой оригинальный объект RegsiteredStudent, но сделайте так, чтобы он выглядел следующим образом
public class RegisteredStudent
{
public RegisteredStudent() { }
[Key]
public int RegisteredStudentId { get; set; }
[Key]
[Column(Order = 1)]
public int YogaProfileId { get; set; }
[Key]
[Column(Order = 2)]
public int YogaSpaceEventId { get; set; }
[ForeignKey("YogaProfileId")]
public YogaProfile YogaProfile { get; set; }
[ForeignKey("YogaSpaceEventId")]
public virtual YogaSpaceEvent YogaSpaceEvent { get; set; }
}
Таким образом, я могу добавить зарегистрированного студента (YogaProfile) к новому событию, как это
yogaSpaceEvent.RegsiteredStudents.Add(yogaProfile);
и у нас все должно быть хорошо с ссылками, чтобы я мог посмотреть, кто есть кто...