Лучший подход для отношений "многие ко многим" в 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);

и у нас все должно быть хорошо с ссылками, чтобы я мог посмотреть, кто есть кто...

Другие вопросы по тегам