首页 > 解决方案 > 如果您不能从 C# Windows 应用程序(使用 Linq)使用自动生成的链接表,它的目的是什么?

问题描述

下面的代码,很好。有用。东西被插入,东西出现在左侧的右侧等等。但是,有一个问题。使用LinQPad ( https://www.linqpad.net ) 之类的程序,我可以query查看数据并查看它的列表。

但是,尝试在 C# 中做同样的事情,表示上下文中不存在“自动”生成的链接表。

关系:多对多

我正在使用Entity Framework 6

编辑:如果您不能从 C# Windows 应用程序中使用自动生成的链接表,它的目的是什么?使用 Linq?

我的代码优先表:

public class Student
{
    [Key, Column(Order = 1)]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int StudentID { get; set; }
    public string StudentName { get; set; }
    public DateTime? DateOfBirth { get; set; }
    public virtual ICollection<Course> Courses { get; set; }
}

public class Course
{
    [Key, Column(Order = 1)]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int CourseId { get; set; }
    [Index("CourseName", 2, IsUnique = true)]
    public string CourseName { get; set; }

    public virtual ICollection<Student> Students { get; set; }
}

DBContext.cs

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Student>()
       .HasMany<Course>(s => s.Courses)
       .WithMany(c => c.Students)
       .Map(cs =>
       {
           cs.MapLeftKey("FK_StudentID");
           cs.MapRightKey("FK_CourseID");
           cs.ToTable("StudentCourse");
       });

    base.OnModelCreating(modelBuilder);
}

从 LinqPad 中查询,工作得非常好:

void Main()
{
    var data = (from s in Students
    join sc in StudentCourse on s.StudentID equals sc.FK_StudentID
    join c in Courses on c.CourseId equals sc.FK_CourseID
    select new { s,c });
    
    Console.WriteLine(data);
}

标签: c#entity-frameworklinq

解决方案


没有什么可以阻止您将其声明为实体,但是在链接表仅保留所需键的情况下,使用导航属性时不必声明它是一种优化。

我认为您在这里面临的关键问题是您正在编写类似于 SQL 的 Linq 并且完全缺少导航属性。

例如,如果我想要特定学生的所有课程: 如果没有链接表,我可以这样做:

var studentCourses = context.Students
    .Where(x => x.StudentId == studentId)
    .SelectMany(x => x.Courses)
    .ToList();

要获得您概述的每个学生/课程组合的列表:

var studentCourses = context.Students
    .SelectMany(s => s.Courses.Select(c => new {s, c}))
    .ToList();

这给了我该学生的课程列表。我不必像编写 SQL 语句那样通过 StudentCourse 手动将学生加入课程。

现在您当然可以声明一个 StudentCourse 实体,但这会稍微改变关系。而不是多对多,你有一个多对一对多:

public class Student
{
   // ...
   public virtual ICollection<StudentCourse> StudentCourses { get; set; } = new List<StudentCourse>();
}

public class Course
{
   // ...
   public virtual ICollection<StudentCourse> StudentCourses { get; set; } = new List<StudentCourse>();
}

public class StudentCourse
{
   [Key, Column(Order=0), ForeignKey(Student)]
   public int StudentId { get; set; }
   [Key, Column(Order=1), ForeignKey(Course)]
   public int CourseId { get; set; }
   public virtual Student Student { get; set; }
   public virtual Course Course { get; set; }
}

将 StudentCourses 属性分别命名为“Courses”和“Students”可能很诱人,但 IMO 在浏览导航时会产生误导,正如我将在下面的示例中指出的那样。

对于那些使用 EFCore 的人,我相信这仍然是多对多唯一支持的选项。如果您想对连接表进行任何更改,例如使用专用的 PK 列或 CreatedAt 等其他列来跟踪关系的编辑等,这也是必需的选项。

然后要在 Linq 中执行第一个示例查询,您需要将其稍微更改为:

var studentCourses = context.Students
    .Where(x => x.StudentId == studentId)
    .SelectMany(x => x.StudentCourses.Course)
    .ToList();

要使用此链接实体获取每个学生/课程组合的列表:

var studentCourses = context.StudentCourses.ToList();
// or, if there is other data in the linking entity and you just want student and course reference: 
var studentCourses = context.StudentCourses
  .Select(sc => new {sc.Student, sc.Course})
  .ToList();

或者,您可以像在 Linqpad 中那样编写 Linq QL 语句,因为该实体已声明并且可以作为 DbSet 添加到上下文中。(如果您使用导航属性,则不需要 DbSet)

编辑:在下面添加示例:(我不能保证它的正确性,因为我几乎完全使用 Fluent Linq 语法,而不是 Linq QL)

var data = (from s in context.Students
join sc in context.StudentCourse on s.StudentID equals sc.StudentID
join c in context.Courses on c.CourseId equals sc.CourseID
select new { s,c });

这就是为什么我建议将 Student 上的属性命名为 StudentCourses 而不是 Courses。替代方案是:

    .SelectMany(x => x.Courses.Course)

Courses 暗示我应该得到课程(因为避免链接实体的优化可以给你)但是你得到 StudentCourses 所以你留下它看起来很奇怪,因为 .Courses.Course 得到实际的课程。


推荐阅读