2017-02-27 65 views
0

我正在使用从Class1Class2的多对多关系。 exactaly这样的:实体框架在使用多对多关系时不会更新

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记录。

+0

afaik,多对多的关系要求两个类都拥有navyational属性 - 而一对多只需要在类中成为关系中的'一'。无论如何'虚拟IList Classes2 {get;组; }'试图改变它'公共虚拟...'? –

+0

使用Fluent API时不需要导航参数。但是导航属性不会影响此问题。我忘了在问题中写'公开'。已编辑。 –

回答

1

我已经替换了我在这里给出的原始答案,因为误解了被问到的内容。

由于后来被清除,我意识到旧的答案对问题没有影响。

因此,这里是我与EF的多对多映射,并更新所涉及的关系。

我在实体名称(TeacherEFs,StudentEFsTeacherEFsStudentEFs)中使用'EF',实体框架的许多映射将使用它们。

其他(Teachers,Students,TeachersStudents)用于控制所有表格数据的正常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"); 
} 

公告

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

由于现在可以看到,如果你TeacherEFsStudentEFs这些关系将被放置在该表之间添加关系 - 不会有关于在父表(TeacherEFsStudentEFs)之间的关系的任何信息。

还有一个级联删除集,以便如果从TeacherEFs表中删除TeacherEF,将删除TeacherEFsStudentEFs表中与该TeacherEF的所有关系(以防止孤立数据)。 因此,要添加或删除TeacherEFsStudentEFs之间的关系,此第三个表是唯一要更新的表。

此关系数据可以由实体框架自动更新,也可以由您自己手动控制。我个人更喜欢控制这个(因为我喜欢直接知道数据到底发生了什么)。正如你可以从上面看到的那样,为了控制这个,你需要自己使用TeachersStudents表,根据意愿添加和删除TeachersStudentEFs的Id(利用任何现有的Id)。

另一种方法是让实体框架做的工作为你在幕后,但在这种情况下,你需要让实体框架知道每一个变化通过TeacherEFStudentEF实体的关系做出了TeacherEFsStudentEFs表。

由于您无法直接访问关系表,因此您需要加载所有TeacherEFsStudentEFs,这些关系的关系会发生变化,即添加或删除。然后将每个实体的状态设置为是添加还是删除相关项目。然后,Entity Framework将计算关系表(TeacherEFsStudentEFs)中哪些行需要更改,然后执行操作。

就我个人而言,我觉得这有点复杂,你必须在执行任何必需的更新之前点击数据库。这是我喜欢控制关系表中的数据仅命中在数据库上SaveChanges();

我们来看看两种情况的原因:

  • 你现有师生在数据库中。
  • 你想添加一个新的关系。
  • 您选择一位教师,然后联系学生。

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

方法1(我的偏好)。

手册CRUD的正常进行:

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

这可以使用TeachersStudents实体来完成,与Id小号两个所选TeacherStudent要被相关,则调用SaveChanges()(具有用于关系的实体正常CRUD填充这两个属性的表)。

但是,如果这种相同的关系刚刚被其他人添加,会怎么样?它会因独特的约束而失败!你可以优雅地处理这个错误,或者如果它是有意义的,你可以通知用户他们试图添加的特定关系已经存在。

  • 您在数据库中存在教师和学生。
  • 您想编辑现有的关系。
  • 您选择一位教师,然后将学生更改为另一位教师。
  • 你想要做的是通过它的Id更改关系表中的StudentId。

你会选择TeachersStudents记录(其IdTeacherIdStudentId),加载用于显示TeacherStudent数据(使用视图模型最好),以及替代品的列表,以便进行选择编辑。

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

  • 您在数据库中存在教师和学生。
  • 你想删除一段关系。
  • 您显示要删除的可用关系。
  • 您选择一种关系。
  • 你想要做的是从关系表中删除给定的Id的记录。

这种方法可以让你把你的单一实体(或多个,如果您允许同时在UI上许多清除)到服务器,到删除方法来删除关系,

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

我不会为方法1的关系表的CRUD操作提供代码示例,因为我们都知道如何执行该操作。

方法2。

使用实体框架做的工作:

除了最初加载所有将出现在用户界面(如提到的方法1),该数据时,您将更改发送回服务器,您将需要:

通过在TeacherEFs中传递的循环,您已收到并从存储中检索其数据及其相关的StudentEFs

循环遍历TeacherEFs中的传递,并将每个与从存储中检索到的内容进行比较,以查看是否已添加或删除StudentEF

将每个存储的TeacherEFStudentEF集合设置为根据需要添加和/或删除。

最后,您可以拨打SaveChanges()

在设置实体关系后,实体框架将为您执行对TeacherEFsStudentEFs表的所需编辑。

这与如果您要为实体创建CRUD页面(例如,针对TeacherCourse细节)类似,但从未加载Id和其现有属性。

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

下面是一个如何做到实体框架“黑匣子”方式的例子。 该示例使用单个TeacherEF并更新其集合StudentEF。 如果您希望将多个TeacherEF发送到服务器进行更新,只需更改代码以循环访问该集合。

public ActionResult Edit(TeacherEF teacherEF) 
     { 
      if (ModelState.IsValid) 
      { 
       using (var context = new MyContext()) 
       { 
        TeacherEF existingTeacherEF = context.TeacherEFs.Include("StudentEFs").FirstOrDefault(t => t.Id == teacherEF.Id); 

        if (teacherEF.StudentEFs == null) 
        { 
         teacherEF.StudentEFs = new List<StudentEF>(); 
        } 

        // Add new StudentEfs to the existingTeacherEF 
        List<StudentEF> studentEfsToAdd = new List<StudentEF>(); 

        foreach (StudentEF studentEf in teacherEF.StudentEFs) 
        { 
         // Use a loop/where clause/extension method etc. on the passed in teacherEF's StudentEFs to see if they are already related in the existingTeacherEF. 
         // If not, add them to the list of studentEFsToAdd. 
         if (existingTeacherEF != null) 
         { 
          bool match = false; 
          foreach (StudentEF studentLookup in existingTeacherEF.StudentEFs) 
          { 
           if (studentLookup.Id == studentEf.Id) 
           { 
            match = true; 
            break; 
           } 
          } 
          if (!match) 
          { 
           // If we do not have a match (the existingTeacher's StudentEFs do not contain the one we are currently looking at ('student')...) 
           // Let's add this 'student' to studentEfsToAdd. 
           studentEfsToAdd.Add(studentEf); 
          } 
          else 
          { 
           // No need for action - already related 
          } 
         } 
        } 

        // Delete non-existant StudentEfs from the existingTeacherEF 
        List<StudentEF> studentEfsToDelete = new List<StudentEF>(); 

        if (existingTeacherEF != null) 
        { 
         foreach (StudentEF studentEf in existingTeacherEF.StudentEFs) 
         { 
          bool match = false; 
          // Use a loop/where clause/extension method etc. on the passed in teacherEF's StudentEFs to see if they are already related in the existingTeacherEF. 
          // If not, add them to the list of studentEFsToAdd. 
          foreach (StudentEF studentLookup in teacherEF.StudentEFs) 
          { 
           if (studentLookup.Id == studentEf.Id) 
           { 
            match = true; 
            break; 
           } 
          } 

          if (!match) 
          { 
           // If we do not have a match (the teacherEF's StudentEFs contains a 'student' that is not already related with existingTeacherEF...) 
           // Let's add this 'student' to studentEfsToDelete. 

           studentEfsToDelete.Add(studentEf); 
          } 
          else 
          { 
           // No need for action - already related 
          } 
         } 

         // Update the context with the StudentEFs we have, and Add or Delete them from the existingTeacherEF before SaveChanges(); 
         foreach (StudentEF studentEf in studentEfsToAdd) 
         { 
          if (context.Entry(studentEf).State == EntityState.Detached) 
          { 
           context.StudentEFs.Attach(studentEf); 
          } 

          existingTeacherEF.StudentEFs.Add(studentEf); 
         } 

         foreach (StudentEF studentEf in studentEfsToDelete) 
         { 
          if (context.Entry(studentEf).State == EntityState.Detached) 
          { 
           context.StudentEFs.Attach(studentEf); 
          } 

          existingTeacherEF.StudentEFs.Remove(studentEf); 
         } 
        } 

        context.SaveChanges(); 
       } 
      } 

      return View(teacherEF); 
     } 
+0

如果我将一个child2实例设置为删除状态。它会从数据库中删除Child2还是删除关系?我想删除关系。我猜想设置一个要删除的状态会将其从数据库中删除 –

+0

如果您从对象中删除了关系,它将不会在数据库中进行更新 - 只有当EF被告知存储中存在对象并且需要更新时才会更新影响它。尽管如此,从父集合中删除项目,但不从数据库中删除项目,意味着下次从数据库加载父项目时,先前“删除”的子项将再次加载。 .. –

+1

我以前遇到同样的问题,我以同样的方式解决了这个问题。不过,我认为这是一个可怕的方法来应用于开发。我急切地期待着一个新的,更好的和可行的解决方案。 – gdmanandamohon