使用多对多关系时,实体框架不会更新

时间:2017-02-27 00:48:56

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

我使用Class1Class2之间的多对多关系。完全像这样:

public class Class1{
    [Key]
    public int Id { get; set; }

    public virtual IList<Class2> Classes2 { get; set; }

    //...
}

public class Class2{
    [Key]
    public int Id { get; set; }

    //... no navigational parameter
    // there is no need for navigational when using fluent API below.
    // However the navigational presence does not affects the issue
}

和流畅的API

OnModelCreating(DbModelBuilder modelBuilder) {

    modelBuilder.Entity<Class1>().HasMany(a => a.Classes2).WithMany();

}

EF使用Class1Class2中间表创建所有表模式。

然后我的应用程序将Class1打印到视图中,并将修改后的Class1从用户可以删除的用户绑定回来,或添加Classes2。 (我已经检查过,所有数据都在entity实例中正确绑定)。

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind()] Class1 entity) {
    if (ModelState.IsValid) {
        db.Entry(entity).State = EntityState.Modified;
        db.SaveChanges();
    }
    return View();
}

但是SaveChanges不会更新多对多的数据。

如何正确更新新绑定的多个参数?

我只想删除或添加关系,我不想添加或删除Classes2记录。

1 个答案:

答案 0 :(得分:2)

由于对被问及的内容存在误解,我已经取代了我在这里给出的原始答案。

随着后来的清理,我意识到旧答案与这个问题无关。

所以,这是我对EF进行多对多映射,并更新所涉及的关系。

我已经使用过&#39; EF&#39;在实体(TeacherEFsStudentEFsTeacherEFsStudentEFs)的名称中,实体框架将使用它们进行多对多的映射。

其他人(TeachersStudentsTeachersStudents)用于控制所有表格数据的普通CRUD操作。

虽然您可以使用流畅的API,但数据注释足以设置它,下面显示了两种方法的示例实体:

// Manual method - you control the relationship table
public class Teacher
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual IList<TeachersStudents> TeachersStudents { get; set; }
}

public class Student
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual IList<TeachersStudents> TeachersStudents { get; set; }
}

public class TeachersStudents
{
    [Key]
    public int Id { get; set; }
    [Index("IX_Teacher_Student", 1)]
    public int TeacherId { get; set; }
    [Index("IX_Teacher_Student", 2)]
    public int StudentId { get; set; }
    [ForeignKey("TeacherId")]
    public virtual Teacher Teacher { get; set; }
    [ForeignKey("StudentId")]
    public virtual Student Student { get; set; }
}

// Automatic method - Entity Framework controls the relationship table
public class TeacherEF
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual IList<StudentEF> StudentEFs { get; set; }
}

public class StudentEF
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual IList<TeacherEF> TeacherEFs { get; set; }
}

这通常是为上述实体生成迁移的结果:

public override void Up()
{
        CreateTable(
            "dbo.TeacherEFs",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    Name = c.String(),
                })
            .PrimaryKey(t => t.Id);

        CreateTable(
            "dbo.StudentEFs",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    Name = c.String(),
                })
            .PrimaryKey(t => t.Id);

        CreateTable(
            "dbo.Teachers",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    Name = c.String(),
                })
            .PrimaryKey(t => t.Id);

        CreateTable(
            "dbo.Students",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    Name = c.String(),
                })
            .PrimaryKey(t => t.Id);

        CreateTable(
            "dbo.TeacherEFStudentEFs",
            c => new
                {
                    TeacherEF_Id = c.Int(nullable: false),
                    StudentEF_Id = c.Int(nullable: false),
                })
            .PrimaryKey(t => new { t.TeacherEF_Id, t.StudentEF_Id })
            .ForeignKey("dbo.TeacherEFs", t => t.TeacherEF_Id, cascadeDelete: true)
            .ForeignKey("dbo.StudentEFs", t => t.StudentEF_Id, cascadeDelete: true)
            .Index(t => t.TeacherEF_Id)
            .Index(t => t.StudentEF_Id);

        CreateTable(
            "dbo.TeachersStudents",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    TeacherId = c.Int(nullable: false),
                    StudentId = c.Int(nullable: false),
                })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.Teachers", t => t.TeacherId, cascadeDelete: true)
            .ForeignKey("dbo.Students", t => t.StudentId, cascadeDelete: true)
            .Index(t => new { t.TeacherId, t.StudentId }, name: "IX_Teacher_Student");
}

请注意,Entity Framework使用我们在类中提供的关系向数据库添加了第三个表:

public virtual ICollection<TeacherEF> TeachersEF { get; set; }
public virtual ICollection<StudentEF> StudentsEF { get; set; }

它使用了两个实体的Id属性,并将它们组合在一起以创建复合和唯一主键。

现在可以看出,如果您在TeacherEFsStudentEFs之间添加关系,这些关系将放在此表中 - 将不会有任何关于父表中关系的信息({{ 1}}和TeacherEFs)。

还有一个Cascade Delete集,这样如果从StudentEFs表中删除了TeacherEF,则TeacherEFs表中与TeacherEF的所有关系都将被删除(防止孤立的数据)。 因此,要添加或删除TeacherEFsStudentEFsTeacherEFs之间的关系,此第三个表格是唯一要更新的表格。

此关系数据可由Entity Framework自动更新,也可由您自己手动控制。

就个人而言,我更喜欢控制它(因为我喜欢直接知道数据发生了什么);正如您从上面所看到的,为了控制这一点,您可能希望自己使用StudentEFs表,随意添加和删除TeachersStudentsTeachers的ID(使用任何现有的Id)。

另一种方法是让实体框架在幕后为您完成工作,但在这种情况下,您需要让实体框架知道StudentEFs表中通过{TeacherEFsStudentEFs表的每个更改。 1}}和TeacherEF个实体。

由于您无法直接访问关系表,因此您需要加载所有会更改关系的StudentEFTeacherEFs,即添加或删除。然后将每个实体的状态设置为是要添加还是删除相关项。然后,实体框架将确定需要更改关系表(StudentEFs)中的哪些行,然后最终执行操作。

就个人而言,我发现这有点令人费解,你必须在执行任何必要的更新之前点击数据库。这就是我更喜欢控制关系表中的数据并且仅在TeacherEFsStudentEFs

上点击数据库的原因

我们将介绍两种情况:

  • 您在数据库中有现有的教师和学生。
  • 您希望添加新关系。
  • 您选择一名教师,然后与学生联系。

    您要做的是将这两个ID放入关系表中。

方法1 (我的偏好)。

正常执行的手动CRUD:

  • 您在数据库中有现有的教师和学生。
  • 您希望添加新关系。
  • 您选择一名教师,然后与学生联系。
  • 您要做的是将这两个ID放入关系表中。

可以使用SaveChanges();实体完成此操作,使用所选TeachersStudentsId的{​​{1}}填充这两个属性,然后调用Teacher(普通CRUD具有关系表的实体)。

但是,如果其他人添加了同样的关系怎么办?它会在唯一约束上失败!您可以优雅地处理此错误,或者如果它有意义,您可以通知用户他们尝试添加的特定关系已经存在。

  • 您在数据库中有现有的教师和学生。
  • 您希望修改现有关系。
  • 选择教师,然后将学生更改为其他学生。
  • 您要做的是通过其ID更改关系表中的StudentId。

您需要选择Student条记录(包含SaveChanges()TeachersStudentsId),并加载TeacherIdStudentId数据用于显示(最好使用视图模型),以及可供编辑选择的备选列表。

现在,您可以让用户更改Teacher并使用Student方法更新其Student和新Id相关的现有记录。

  • 您在数据库中有现有的教师和学生。
  • 您希望删除某段关系。
  • 显示要删除的可用关系。
  • 您选择了一种关系。
  • 您要做的是从关系表中删除具有给定Id的记录。

此方法允许您将单个实体(或多个,如果允许在UI上一次删除多个)发送到服务器,使用Delete方法删除关系,

以上优点是使用基本CRUD以相同的方式处理数据库表中的所有数据。

我不会为方法1的关系表的CRUD操作提供代码示例,因为我们都知道如何做到这一点。

方法2。

使用Entity Framework完成工作:

除了最初加载UI上显示的所有数据(如方法1所述),当您将更改发送回服务器时,您需要:

循环传入已收到的StudentId,并从存储中检索他们的数据及其相关的Edit

循环传入TeacherEFs并将每一个与从存储中检索到的内容进行比较,看看是否添加或删除了StudentEFs

将每个存储的TeacherEFs StudentEF个收藏集设置为适当添加和/或删除。

最后,您可以致电TeacherEF

实体框架将在实体&#39;之后为您执行StudentEF表格所需的编辑。关系已经确定。

这类似于您为实体创建CRUD页面(例如,对于TeacherCourse详细信息),但从不为其加载Id和现有属性。

现在,用户可以将各种数据添加到属性中(如果他们知道,则指定ID),但在发送回服务器时,您不知道自己拥有什么 - 您将不得不询问存储以查看课程是否存在,然后检查它是否是添加,编辑或删除,然后执行所需的操作。

以下是如何执行实体框架&#39;黑匣子&#39;办法。 该示例使用单个SaveChanges()并更新其TeacherEFsStudentEFs集合。 如果您希望将多个TeacherEF发送到服务器进行更新,只需更改代码以循环访问该集合。

StudentEF
相关问题