【问题标题】:How to insert properly into a many-to-many relationship?如何正确插入多对多关系?
【发布时间】:2019-10-21 22:04:57
【问题描述】:

在多对多关系中插入数据时,应该插入连接表还是两个原始表?

我的桌子模型:

public DbSet<User> Users { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<GroupMember> GroupMembers { get; set; }

它们之间的关系是用 Fluent API 配置的:

builder.Entity<GroupMembers>().HasKey(gm => new { gm.UserId, gm.GroupId });
builder.Entity<GroupMembers>().HasOne(gm => gm.Group).WithMany(group => group.GroupMembers).HasForeignKey(gm => gm.GroupId);
builder.Entity<GroupMembers>().HasOne(gm => gm.User).WithMany(user => user.GroupMembers).HasForeignKey(gm => gm.UserId);
public class Group
{
    [Key]
    public Guid Id { get; set; }
    public string Name { get; set; }

    public List<GroupMember> GroupMembers { get; set; } = new List<GroupMembers>();
}

public class User
{
    [Key]
    public Guid Id { get; set; }
    public string Username { get; set; }

    public List<GroupMembers> GroupMembers { get; set; } = new List<GroupMembers>();
}

public class GroupMembers
{
    [Key]
    public Guid UserId { get; set; }
    public User User { get; set; }

    [Key]
    public Guid GroupId { get; set; }
    public Group Group { get; set; }
}

现在,问题是;我应该在哪些表/类中插入有关组成员的数据? 是不是这样的:

GroupMembers groupMember = new GroupMembers
{
    Group = group,
    GroupId = group.Id,
    User = user,
    UserId = user.Id
};


user.GroupMembers.Add(groupMember);
group.GroupMembers.Add(groupMember)

_databaseContext.Users.Update(user);
_databaseContext.SaveChanges();;

_databaseContext.Groups.Update(group);
_databaseContext.SaveChanges();

或者像这样,保持用户和组不变,仅在连接表中提供有关他们关系的信息:

GroupMembers groupMember = new GroupMembers
{
    Group = group,
    GroupId = group.Id,
    User = user,
    UserId = user.Id
};


_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();

【问题讨论】:

    标签: c# asp.net entity-framework-core


    【解决方案1】:

    就实体框架而言,这不是多对多的关系

    这里有三个实体类型,它们之间定义了两个一对多的关系。您可能知道这样做是为了表示多对多,但 EF 不知道这一点。

    如果我在维护结构的同时任意更改实体的名称,您将无法判断这是否是多对多关系。

    简单示例:

    public class Country {}
    
    public class Company {}
    
    public class Person
    {
        public int CountryOfBirthId { get; set; }
        public virtual Country CountryOfBirth { get; set; }
    
        public int EmployerId { get; set; }
        public virtual Company Employer { get; set; }
    }
    

    您最初不会认为Person 表示CountryCompany 之间的多对多关系,对吗?然而,这在结构上与您的示例相同。

    基本上,您处理代码的方式与处理任何一对多关系的方式没有任何不同。 GroupMembers 是一个和其他任何表一样的表(数据库集),EF 希望您将其视为普通实体表。

    这里唯一不同的是,因为 GroupMember两个 一对多关系,其中它是“多”,因此您必须提供 两个 em> FKs(每个相关实体一个)。但是处理和这里只有一对多的关系是完全一样的。

    换句话说,将您的groupMember 添加到表本身:

    GroupMembers groupMember = new GroupMembers
    {
        // You don't have to fill in the nav props if you don't need them
        GroupId = group.Id,
        UserId = user.Id
    };
    
    _databaseContext.GroupMembers.Add(groupMember);
    _databaseContext.SaveChanges();
    

    注意:以下内容仅适用于非核心实体框架,如EF Core does not yet support it

    如果中间表不是由您管理,那么(非核心)EF 中“真正的”多对多关系的示例是:

    public class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<Group> Groups { get; set; }
    }
    
    public class Group
    {
        [Key]
        public Guid Id { get; set; }
        public string Name { get; set; }
    
        public virtual ICollection<User> Users { get; set; }
    }
    
    public class User
    {
        [Key]
        public Guid Id { get; set; }
        public string Username { get; set; }
    
        public virtual ICollection<Group> Groups { get; set; }
    }
    

    在这种情况下,EF 仍会在数据库中生成交叉表,但EF 会向您隐藏此表。在这里,您应该通过导航道具工作:

    var user = myContext.Users.First();
    var group = myContext.Groups.First();
    
    user.Groups.Add(group);
    
    myContext.SaveChanges();
    

    您是使用“真正的”多对多关系还是自己管理交叉表取决于您。当我无法避免时,我倾向于只自己管理交叉表,例如当我想要交叉表上的其他数据时。

    【讨论】:

    • 谢谢你,很好的回答。但是,微软表示,Entity Framework CORE 尚不支持没有连接表的多对多关系。 docs.microsoft.com/en-us/ef/core/modeling/… 另外,这是否意味着 User 和 Group 中的列表永远为空?
    • @Ramriez 糟糕,我错过了这是 EF Core。是的,这使答案的第二部分无效(将修改),但前半部分仍然正确。在您创建(并提交)交叉表条目之后,用户和组中的列表将不会为空如果您从数据库中获取它并包含其相关实体。但是 EF 不会神奇地填充在创建交叉表条目之前已获取的实体对象的 nav 道具,不。
    • 好的,我知道我们可以通过在创建 GroupMembers 时手动添加来填充 List。但我为什么要这个?这是否意味着数据保存在多个位置?根据 GroupMembers 的 ID 获取用户和组不是更容易吗?
    • @Ramriez:我不确定您的问题/问题是什么。对于 A 和 B 之间的任何关系,EF 将允许您获取 B 作为 A 的相关实体,或将 A 作为 B 的相关实体。无论哪个漂浮你的船。这并不意味着数据“保存在多个地方”,您可以自己检查数据库表来确认这一点。此外,EF 在执行单个查询时不会多次实例化同一实体。获取许多具有相同父用户的 GroupMember 实体将使 EF 创建此用户对象一次,然后在每个 GroupMember 实体中引用相同的对象。
    【解决方案2】:

    确保数据 id 正确且存在

    GroupMembers groupMember = new GroupMembers
    {
        GroupId = group.Id,
        UserId = user.Id
    };
    
    _databaseContext.GroupMembers.Add(groupMember);
    _databaseContext.SaveChanges();
    

    代码行更少,你必须假设对象在插入时是完全独立的

    【讨论】:

    • 谢谢,这是否意味着用户和组中的列表始终为空?
    • 添加时只有 ID 就足够了。获取数据时 List Or Get By Id,根据您的请求请求一个或两个 User and Group
    猜你喜欢
    • 1970-01-01
    • 2011-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-01
    • 1970-01-01
    • 2023-03-20
    相关资源
    最近更新 更多