在 Entity Framework 应用程序中通过 ID 将多个子项添加到父项的最有效方法(创建多个映射)

Most Efficient way to Add Multiple Children to Parent by ID in Many to Many in Entity Framework Application (create multiple mappings)

拿本例中的代码来说: http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx

当我有课程 ID 时,向学生添加多门课程的最有效方法是什么?

因此,例如,该函数可能如下所示:

 private static void AddCoursesToStudent(SchoolDBContext context, Student student, List<int> CourseIds)
    {}

我目前的尝试是这样的:

public static async Task AddCoursesToStudentAsync(SchoolDBContext context, Student student, List<int> CourseIds)
    {
        await context.Courses.Where(x => CourseIds.Contains(x.CourseId)).ForEachAsync(x => x.Students.Add(student));
        context.SaveChanges();
    }

    private static void AddCoursesToStudent(SchoolDBContext context, Student student, List<int> CourseIds)
    {
        var task = AddCoursesToStudentAsync(context,student,CourseIds);
        task.Wait();
    }

这行得通,但似乎有点草率,尤其是混合在异步中。我还认为 "Where" 与 "ForEach" 相结合将在某处提取所有记录。

在其他不是代码优先的项目中,我们通常会手动创建一个 "MappingTable",在这种情况下,它看起来像:

public class StudentCourse
{
    [Key] [Column(Order = 0)]
    private int StudentId;
    [Key]
    [Column(Order = 1)]
    private int CourseId;

    public StudentCourse(int courseIde, int studentId)
    {
        StudentId = studentId;
        CourseId = courseIde;
    }
}

你会做这样的事:

 public static  void AddCoursesToStudentMaps(SchoolDBContext context, Student student, List<int> CourseIds)
    {
        List<StudentCourse> maps = CourseIds.ConvertAll(courseId => new StudentCourse(courseId, student.StudentId)).ToList();
        context.StudentCourses.AddRange(maps);
    }

以 Entity Framework 管理的方式来做这件事会很好,也许我可以创建学生课程 class 并将其添加到上下文中?但这似乎违背了这一点,尤其是当我们有很多多对多并且让 EF 为我们创建映射 table 时会减少代码。我只是想如果 EF 能够自动创建映射,它一定有一种方法可以有效地创建新链接而无需重做它已经完成的工作?

为了测试这一点,我创建了这个示例代码,带有惰性设置功能,所以如果有人想要 bash,这应该会让你起床 运行:

 static void Main(string[] args)
    {
        Run();
    }

    private static void Run()
    {
        using (SchoolDBContext context = new SchoolDBContext())
        {
            /* Run once
             * SetUp(context);
             */
            Test(context);
            Output(context);
        }
    }

    static void SetUp(SchoolDBContext context)
    {
        context.Students.Add(new Student() { StudentName = "Gerry" });
        context.Students.Add( new Student() { StudentName = "Bob" }  );
        context.Students.Add( new Student() { StudentName = "Jane" } );

        context.Courses.Add( new Course() { CourseName = "Science" });
        context.Courses.Add( new Course() { CourseName = "Math" }  );
        context.Courses.Add( new Course() { CourseName = "History" } );
        context.Courses.Add(new Course() { CourseName = "Other History" });
        context.Courses.Add(new Course() { CourseName = "Roman History" });
        context.Courses.Add(new Course() { CourseName = "English History" });
        context.Courses.Add(new Course() { CourseName = "Super History" });
        context.Courses.Add(new Course() { CourseName = "Tudor History" });
        context.Courses.Add(new Course() { CourseName = "Queen History" });
        context.Courses.Add(new Course() { CourseName = "King History" });
        context.SaveChanges();
    }

    public static void Test(SchoolDBContext context)
    {

        var ids = context.Courses.Where(x => x.CourseName.Contains("History")).Select(x => x.CourseId).ToList();
        var student = context.Students.FirstOrDefault(x => x.StudentName == "Bob");
        AddCoursesToStudent(context, student, ids);
    }

    private static void Output(SchoolDBContext context)
    {
        OutputStudents(context);
    }

    private static void OutputStudents(SchoolDBContext context)
    {
        var allStudents = context.Students.ToList();
        foreach (var student in allStudents)
        {
            Console.WriteLine($"Student: {student.StudentName}");
            if (student.Courses.Any())
            {
                Console.WriteLine("is enrolled on");
                Console.WriteLine("**************************");
                foreach (var contextCourseStudent in student.Courses)
                {
                    Console.WriteLine("     " + contextCourseStudent.CourseName);
                }
                Console.WriteLine("************************ ");
            }
            Console.WriteLine("-------------------------");
        }
        Console.WriteLine("Press enter to close...");
        Console.ReadLine();
    }
    private static void OutputCourse(SchoolDBContext context)

p.s。不只是有课程列表的原因是因为我们已经设置了 forms/apps 等发送 id 列表,并且如前所述,然后将这些模型从数据库中拉出似乎很愚蠢且效率低下,纯粹是为了将它们添加到父级。

我不认为带有隐式 link table 的 many-to-many 是 Code First 特定的。此外,虽然它使查询更容易和更自然,但正如您已经看到的那样,它在使用 link 修改时有明显的缺点。

通过使用 存根实体:

可以有效地实现添加新的 links
static void AddCoursesToStudent(SchoolDBContext context, Student student, List<int> CourseIds)
{
    student.Courses = CourseIds.Select(id => context.Courses.Attach(new Course { CourseId = id })).ToList();
    context.SaveChanges();
}

这有效,但在以下限制条件下:

(1) context 已专门为此调用创建
(2) student 已经附加到 context
(3) 所有CourseIds存在于数据库
(4) 上下文中没有加载 Course 个实体,其中一些 CourseIds
(5) 初始student.Courses 集合不是eager 或lazy loaded。这是专门用于执行 add link 操作(vs replace all links 通过确定添加和删除的项目)
(6) 以后不要使用 context 或任何 Course 对象,因为它们的所有属性除了主键外都不是真实的。

一个考虑所有约束的例子:

public void AddCoursesToStudent(int studentId, List<int> coursesIds)
{
    using (var context = new SchoolDBContext())
    {
        var student = context.Students.Find(studentId);
        AddCoursesToStudent(context, student, coursesIds);
    }
}