实体框架在使用多对多关系时不会更新
我正在使用从Class1
到Class2
的多对多关系。 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记录。
我已经替换了我在这里给出的原始答案,因为误解了被问到的内容。
由于后来被清除,我意识到旧的答案对问题没有影响。
因此,这里是我与EF的多对多映射,并更新所涉及的关系。
我在实体名称(TeacherEFs
,StudentEFs
和TeacherEFsStudentEFs
)中使用'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
属性并将它们组合在一起以创建组合和唯一主键。
由于现在可以看到,如果你TeacherEFs
和StudentEFs
这些关系将被放置在该表之间添加关系 - 不会有关于在父表(TeacherEFs
和StudentEFs
)之间的关系的任何信息。
还有一个级联删除集,以便如果从TeacherEFs
表中删除TeacherEF
,将删除TeacherEFsStudentEFs
表中与该TeacherEF
的所有关系(以防止孤立数据)。 因此,要添加或删除TeacherEFs
和StudentEFs
之间的关系,此第三个表是唯一要更新的表。
此关系数据可以由实体框架自动更新,也可以由您自己手动控制。我个人更喜欢控制这个(因为我喜欢直接知道数据到底发生了什么)。正如你可以从上面看到的那样,为了控制这个,你需要自己使用TeachersStudents
表,根据意愿添加和删除Teachers
和StudentEFs
的Id(利用任何现有的Id)。
另一种方法是让实体框架做的工作为你在幕后,但在这种情况下,你需要让实体框架知道每一个变化通过TeacherEF
和StudentEF
实体的关系做出了TeacherEFsStudentEFs
表。
由于您无法直接访问关系表,因此您需要加载所有TeacherEFs
和StudentEFs
,这些关系的关系会发生变化,即添加或删除。然后将每个实体的状态设置为是添加还是删除相关项目。然后,Entity Framework将计算关系表(TeacherEFsStudentEFs
)中哪些行需要更改,然后执行操作。
就我个人而言,我觉得这有点复杂,你必须在执行任何必需的更新之前点击数据库。这是我喜欢控制关系表中的数据仅命中在数据库上SaveChanges();
我们来看看两种情况的原因:
- 你现有师生在数据库中。
- 你想添加一个新的关系。
-
您选择一位教师,然后联系学生。
你想要做的是将这两个ID放入关系表中。
方法1(我的偏好)。
手册CRUD的正常进行:
- 你现有的教师和学生在数据库中。
- 你想添加一个新的关系。
- 您选择一位教师,然后联系学生。
- 你想要做的是将这两个ID放入关系表中。
这可以使用TeachersStudents
实体来完成,与Id
小号两个所选Teacher
和Student
要被相关,则调用SaveChanges()
(具有用于关系的实体正常CRUD填充这两个属性的表)。
但是,如果这种相同的关系刚刚被其他人添加,会怎么样?它会因独特的约束而失败!你可以优雅地处理这个错误,或者如果它是有意义的,你可以通知用户他们试图添加的特定关系已经存在。
- 您在数据库中存在教师和学生。
- 您想编辑现有的关系。
- 您选择一位教师,然后将学生更改为另一位教师。
- 你想要做的是通过它的Id更改关系表中的StudentId。
你会选择TeachersStudents
记录(其Id
,TeacherId
和StudentId
),加载用于显示Teacher
和Student
数据(使用视图模型最好),以及替代品的列表,以便进行选择编辑。
现在,您可以让用户改变要么Student
并更新其Id
和新StudentId
从Edit
方法相关的现有记录。
- 您在数据库中存在教师和学生。
- 你想删除一段关系。
- 您显示要删除的可用关系。
- 您选择一种关系。
- 你想要做的是从关系表中删除给定的Id的记录。
这种方法可以让你把你的单一实体(或多个,如果您允许同时在UI上许多清除)到服务器,到删除方法来删除关系,
的优势以上是使用基本CRUD处理数据库表中的所有数据的相同方式。
我不会为方法1的关系表的CRUD操作提供代码示例,因为我们都知道如何执行该操作。
方法2。
使用实体框架做的工作:
除了最初加载所有将出现在用户界面(如提到的方法1),该数据时,您将更改发送回服务器,您将需要:
通过在TeacherEFs
中传递的循环,您已收到并从存储中检索其数据及其相关的StudentEFs
。
循环遍历TeacherEFs
中的传递,并将每个与从存储中检索到的内容进行比较,以查看是否已添加或删除StudentEF
。
将每个存储的TeacherEF
的StudentEF
集合设置为根据需要添加和/或删除。
最后,您可以拨打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);
}
如果我将一个child2实例设置为删除状态。它会从数据库中删除Child2还是删除关系?我想删除关系。我猜想设置一个要删除的状态会将其从数据库中删除 –
如果您从对象中删除了关系,它将不会在数据库中进行更新 - 只有当EF被告知存储中存在对象并且需要更新时才会更新影响它。尽管如此,从父集合中删除项目,但不从数据库中删除项目,意味着下次从数据库加载父项目时,先前“删除”的子项将再次加载。 .. –
我以前遇到同样的问题,我以同样的方式解决了这个问题。不过,我认为这是一个可怕的方法来应用于开发。我急切地期待着一个新的,更好的和可行的解决方案。 – gdmanandamohon
afaik,多对多的关系要求两个类都拥有navyational属性 - 而一对多只需要在类中成为关系中的'一'。无论如何'虚拟IList Classes2 {get;组; }'试图改变它'公共虚拟...'? –
使用Fluent API时不需要导航参数。但是导航属性不会影响此问题。我忘了在问题中写'公开'。已编辑。 –