Entity Framework 6.2 从一个DbContext多对多复制到另一个DbContext
Entity Framework 6.2 copy many to many from one DbContext to another DbContext
当使用网络数据库(例如 MySQL)时,DbContext 应该是短暂的,但根据 https://www.entityframeworktutorial.net/EntityFramework4.3/persistence-in-entity-framework.aspx,当使用本地数据库(例如 SQLite)时,DbContext 可以是长期存在的.
我的应用程序使用长期存在的 DbContext 与 HDD 上的 SQLite 一起工作,我想将多对多实体复制到另一个 DbContext 以用于 USB 上相同类型的 SQLite 数据库。
我正在使用代码优先方法。
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 个重复的)?这样做的正确方法是什么?
正如我所说,我正在使用长期使用的 DbContext,因为我正在使用 SQLite。
首先,不要使用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 中的学生相同。结果,学生 A
在 Course3 中是一个新的 Student
实例。您最终将有 6 个学生,3 个重复(如果 StudentName
上没有唯一索引阻止这种情况)。通过跟踪,EF 确实检测到
两门课程都有相同的 Student
实例。
将实体添加到上下文时,EF 还会标记嵌套
Added
尚未附加到上下文的实体。
这就是为什么只添加课程就足够了,这就是为什么EF不添加课程的原因
当课程包含相同的学生实例时抱怨。
由于添加的课程已正确填充其 Students
集合,因此 EF 还会在 StudentCourse
table 中插入所需的联结记录。这并没有发生在您的代码中(也许,或者部分地,稍后见)。
现在为什么你有 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 是克隆对象图的好工具,但必须正确填充图、正确的关系并且没有重复项,并且应该一次性添加。
当使用网络数据库(例如 MySQL)时,DbContext 应该是短暂的,但根据 https://www.entityframeworktutorial.net/EntityFramework4.3/persistence-in-entity-framework.aspx,当使用本地数据库(例如 SQLite)时,DbContext 可以是长期存在的.
我的应用程序使用长期存在的 DbContext 与 HDD 上的 SQLite 一起工作,我想将多对多实体复制到另一个 DbContext 以用于 USB 上相同类型的 SQLite 数据库。
我正在使用代码优先方法。
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 个重复的)?这样做的正确方法是什么?
正如我所说,我正在使用长期使用的 DbContext,因为我正在使用 SQLite。
首先,不要使用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 中的学生相同。结果,学生 A 在 Course3 中是一个新的
Student
实例。您最终将有 6 个学生,3 个重复(如果StudentName
上没有唯一索引阻止这种情况)。通过跟踪,EF 确实检测到 两门课程都有相同的Student
实例。将实体添加到上下文时,EF 还会标记嵌套
Added
尚未附加到上下文的实体。 这就是为什么只添加课程就足够了,这就是为什么EF不添加课程的原因 当课程包含相同的学生实例时抱怨。由于添加的课程已正确填充其
Students
集合,因此 EF 还会在StudentCourse
table 中插入所需的联结记录。这并没有发生在您的代码中(也许,或者部分地,稍后见)。
现在为什么你有 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 是克隆对象图的好工具,但必须正确填充图、正确的关系并且没有重复项,并且应该一次性添加。