在使用诸如MySQL之类的网络数据库时,DbContext应该是短暂的,但是根据https://www.entityframeworktutorial.net/EntityFramework4.3/persistence-in-entity-framework.aspx,当使用诸如SQLite之类的本地数据库时,DbContext可以是长期的。
我的应用程序使用的DbContext寿命长,可以在HDD上使用SQLite,并且我想将USB上相同类型的SQLite数据库的多对多实体复制到另一个DbContext。
我正在使用“代码优先”方法。
public class Student
{
public Student()
{
this.Courses = new HashSet<Course>();
}
public int StudentId { get; set; }
[Required]
public string StudentName { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public Course()
{
this.Students = new HashSet<Student>();
}
public int CourseId { get; set; }
public string CourseName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
DbContextHDD包含学生StudentA,StudentB和StudentC以及课程Course1,Course2和Course3:
StudentA attends Course1 and Course3
StudentB attends Course2 and Course3
StudentC attends Course1 and Course2
DbContextUSB没有学生,也没有课程。
var courses = DbContextHDD.Courses.AsNoTracking();
List<Student> students = new List<Student>();
foreach(Course course in courses)
{
foreach(Student student in course.Students)
{
if(!students.Any(s => s.StudentId == student.StudentId))
{
students.Add(student);
}
}
}
Debug.WriteLine(students.Count); // output: 3
Debug.WriteLine(DbContextUSB.Students.Local.Count); // output: 0
DbContextUSB.Students.AddRange(students);
Debug.WriteLine(DbContextUSB.Students.Local.Count); // output: 4
DbContextUSB.SaveChanges(); // exception: UNIQUE constraint failed
DbContextUSB.Courses.AddRange(courses);
DbContextUSB.SaveChanges();
在将3个唯一学生插入具有0个学生的DbSet中后,为什么会有4个学生(3个唯一和1个重复)?正确的方法是什么?
正如我所说,由于我使用的是SQLite,因此我使用的DbContext寿命长。
答案 0 :(得分:1)
首先,不要使用AsNoTracking
:
var courses = DbContextHDD.Courses. ...
第二,Include
必需的数据:
var courses = DbContextHDD.Courses
.Include(c => c.Students)
.ToList();
第三,将课程添加到其他上下文中:
DbContextUSB.Courses.AddRange(courses);
DbContextUSB.SaveChanges();
您可能不相信,但本质上就是全部!
一个警告是您应该在源上下文中禁用代理创建:
DbContextHDD.Configuration.ProxyCreationEnabled = false;
否则,EF会创建代理对象,该代理对象引用了它们来自的上下文。它们不能附加到另一个上下文。
另一个问题是,可能有些学生没有上课。查询课程时会想念它们。因此,您必须单独添加它们:
var lazyStudents = DbContextHDD.Students.Where(s => s.Courses.Count() == 0).ToList();
...
DbContextUSB.Students.AddRange(lazyStudents);
...
DbContextUSB.SaveChanges();
为什么这样做?
在没有跟踪的情况下,Entity Framework无法在以下位置检测到StudentA
Course1与Course3是同一学生。结果,StudentA
Course3中的一个是新的Student
实例。您最终将有6个学生,3个重复项(如果StudentName
上没有唯一索引会阻止这样做)。通过跟踪,EF可以检测
这两个课程都具有相同的Student
实例。
在将实体添加到上下文时,EF还会标记为嵌套
尚未附加到上下文的实体为Added
。
这就是为什么仅添加课程就足够了,这就是为什么EF不这样做
当课程包含相同的学生实例时抱怨。
由于添加的课程已正确填充其Students
集合,因此EF还将在StudentCourse
表中插入所需的联结记录。这在您的代码中没有发生(也许,或部分,请参阅稍后)。
现在为什么要招4个学生?
查看课程:
Course1 StudentA*, StudentC*
Course2 StudentB*, StudentC
Course3 StudentA , StudentB
由于AsNoTracking
的所有学生都是不同的实例,但是由于添加方式的原因,只有标记的*学生在students
中。但这是棘手的部分。即使使用AsNoTracking()
,Entity Framework也会使用在一个查询中实现的相关实体执行关系修正。这意味着foreach(Course course in courses)
循环会生成带有填充的Students
集合的课程,每个学生在其Courses
集合中都有一门课程。尤其是几乎不可能跟踪到底发生了什么。因为调试也会触发延迟加载,但是可以肯定的是,该行...
DbContextUSB.Students.AddRange(students);
还将嵌套课程和其学生标记为Added
,直到他们最终成为不同的实例。在这种情况下,最终结果是将另一个学生实例添加到缓存中。此外,创建了许多结点记录,但不一定是正确的结点记录。
结论是EF是克隆对象图的好工具,但是必须正确填充图,正确的关系且没有重复,并且应一次性添加。