实体框架代码优先的多对多关系的最佳方法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实体框架代码优先的多对多关系的最佳方法相关的知识,希望对你有一定的参考价值。

我一直在重新审视我使用实体的方式,我需要建议。

我正在创建一个瑜伽预订服务,每个用户都会有一个简介,空间,以满足瑜伽和活动,有一个日期和时间,每个空间,以满足。

所以我的关系看起来像这样,他们都是一对多的

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; }
}

这一切都很好,但后来我学到了更多关于多对多的知识,并认为我可能需要在这里使用它,因为许多配置文件可以注册一个事件,许多事件可以注册到一个配置文件ex。一个班级可以有很多人参加,一个学生可以参加很多班级。

因此,我通过在两个实体(virtual ICollectionYogaProfile)中创建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)

所以我的问题是,我应该回到我最初的方式还是继续使用这种多对多模型?有什么区别,专业,缺点等?

答案

在进一步观察和发现后,我认为这可能是最好的解决方案。

保留我原来的'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);

我们应该对参考文献都很满意,这样我就可以查找谁...

另一答案

如果我错了,请纠正我,但在我看来,个人资料代表一个人。在个人资料可能参加的特定时间内,事件代表瑜伽课程。 Space是此活动举办地点。

空间与事件之间存在一对多关系:在一个地方可能会举行许多活动。每个活动都在一个地方举行。

Profile和Event之间存在多对多关系:每个Profile都可以参加许多活动,每个活动都可能有很多个人参加。

例如,事件E1保存在空间S1。决定参加活动E1的每个个人资料,他都应该去太空S1。这不应该记录在配置文件中。如果将事件E1移动到另一个空间,例如S2,则只需更改一条记录:与事件和空间相关的记录。这一改变将使参加E1的所有配置文件都能看到它们应该转到S2,尽管配置文件中没有任何内容被更改。

所以我不明白你为什么需要Profiles和Spaces之间的关系。我非常确定,对于定义良好的数据库中的预订服务,您最好不要在“个人档案”和“空间”之间建立这样的关系

所以:

  • 空间有很多活动,每个活动都在一个空间举行:一对多
  • 简介参加许多活动;很多个人资料都是参加活动的对象:多对多
  • Profile和Space之间没有直接关系

这为您提供了以下类。

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 code first conventions,这就是实体框架需要知道你想要的一对多和多对多关系。实体框架将创建多对多关系所需的外键和额外连接表。不需要Attributes或Fluent API。只有当您需要不同的表名或列名时,才需要流畅的API或属性。

没有保障在同一时间在同一空间举行两个不同的活动。如果你想要,你应该给每个Space零个或多个TimeSlots(每个TimeSlot只属于一个Space)。

为了方便起见,我将每个时段都放一个小时。超过一小时的事件需要多个TimeSlots

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;}
}

现在,一个事件在一个或多个TimeSlots中保存(它们是某个空间的TimeSlots)。因此,可以根据需要在一个空间或几个空间中举行一个多小时的活动

要在特定时间在特定空间创建事件,请询问Space是否在请求的时间内有TimeSlots。如果是这样,空间就被占用了。如果没有,空间是免费的。

因此,如果我想创建一个活动,例如晚间课程,则需要在20:00到21:00之间的3个星期三晚上,从2018年5月1日开始,将这三个时间转换为小时:

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);
    }

以上是关于实体框架代码优先的多对多关系的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

实体框架代码优先多对多不使用 GUID 加载相关实体

多对多关系实体框架核心db优先

代码第一个约定不在实体框架6.2中的多对多关系上创建连接表

Remove() 不适用于实体框架中的多对多关系

实体框架 CTP5,代码优先。多对多级联删除

使用 SQL 和 Linq 的多对多关系(实体框架/实体)