如何与Entity Framework 6创建Has-Many-Through关系?

时间:2017-08-21 18:35:45

标签: c# asp.net-mvc entity-framework entity-framework-6 entity

我使用数据库优先方法在ASP.NET MVC 5和Entity Framework 6的顶部使用c#编写了一个应用程序。

我有一个Student模型,一个ClassRoom模型和一个关系模型,用于将两个关系链接在一起,称为StudentToClassRoom

我希望能够选择所有学生,并希望每个学生都能获得学生关系中的所有ClassRoom

以下是我的模特

public class Student
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<ClassRoom> ClassRoomRelations { get; set; }
}


public class StudentToClassRoom
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [ForeignKey("Student")]
    [InverseProperty("Id")]
    public int StudentId { get; set; }

    [ForeignKey("ClassRoom")]
    [InverseProperty("Id")]
    public int ClassRoomId { get; set; }

    public virtual Student Student { get; set; }

    public virtual ClassRoom ClassRoom { get; set; }
}

public class ClassRoom
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }
}

这是我试过的

var students = DbContext.Students.Include(x => x.ClassRoomRelations)
                                 .ToList();

然而,这给了我每个学生的关系收集。但我希望能够获得每个学生的ClassRoom信息。所以我想在Student和ClassRoom之间创建一个Has-Many-Through。在最终结果中,我并不关心ClassRoomRelations,我只关心StudentClassRoom对象。

如何使用Entity Framework获取每位学生的学生列表和所有课堂的集合?

5 个答案:

答案 0 :(得分:1)

由于您已经公开了桥接表,您可以使用:

var studentRooms = DbContext.StudentToClassRoom
                            .Include(x => x.Student)
                            .Include(x => x.ClassRoom)
                            .ToList();

请参阅here

此外,您并不需要[Inverse]注释 - EF知道您正在使用FK链接到Id。

编辑:学生和他们的教室

首先,您需要修复学生模型:

public virtual ICollection<StudentToClassRoom> ClassRoomRelations { get; set; }

然后你可以运行

var studentAndRooms = DbContext.Students
                      .Select(s => new 
                      {
                         student = s,
                         classrooms = s.ClassRoomRelations.Select(r => r.ClassRoom)
                      }).ToList();

答案 1 :(得分:1)

实体不支持此类关系。

但是,您可能会得到相同的结果(我没有测试过代码)

var studentRooms = DbContext.StudentToClassRoom
                            .Include(x => x.Student)
                            .Include(x => x.ClassRoom)
                            .GroupBy(x => x.Student)
                            .Select(x => new {
                               Student => x.Key
                               ClassRooms => x.Select(relation => relation.ClassRoom)
                            })
                            .ToList();

您基本上选择了所有关系结果以及StudentClassRoom模型。然后由学生将它们分组,让1名学生参加许多ClassRooms。

我希望这会有所帮助

答案 2 :(得分:0)

你为什么不简单地使用?您已经可以获得学生的课堂信息。

public class Student
{
    public int Id { get; set; }

    public string Name { get; set; }

    public Guid ClassRoomId { get; set; }

    // public virtual ClassRoom ClassRoom { get; set; }
}

public class ClassRoom
{
    public int Id { get; set; }

    public string Name { get; set; }

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

public class StudentToClassRoom
{
    public int Id { get; set; }

    public Guid StudentId { get; set; }

    public virtual Student Student { get; set; }

    public Guid ClassRoomId { get; set; }

    public virtual ClassRoom ClassRoom { get; set; }
}

// var students = DbContext.Students.Include(x => x.ClassRoom).ToList();

var mergedRecords = DbContext.StudentToClassRoom
                             .Include(x => x.Student)
                             .Include(x => x.ClassRoom)
                             .ToList()

答案 3 :(得分:0)

实体框架可以更好地处理多对多的关系。

EF的思考方式是学生有教室,ClassRoom有学生:

public class Student
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<ClassRoom> ClassRooms { get; set; }
}

public class ClassRoom
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }

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

在EF地图中完全忽略关系表。看看this tutorial

修改
下面是几个查询,以简化如何使用此地图场景:

获取特定学生的所有教室的列表:

var classRoomsOfSpecificStudent = DbContext
    .Students
    .First(s => s.Id == studentId)
    .ClassRooms
    .ToList();

获取名称中包含“a”的学生的所有教室的列表。

var classRooms = DbContext
    .Students
    .Where(s => s.Name.Contains("a"))
    .SelectMany(s => s.ClassRooms)
    .ToList();

让所有名字中包含“a”且课堂名称中包含“2b”的学生。

var students = DbContext
    .Students
    .Where(s => s.Name.Contains("a"))
    .Where(s => s.ClassRooms.Any(c => c.Name.Contains("2b")))
    .ToList();

我希望我澄清一点。

答案 4 :(得分:0)

如果要使用显式桥接表,则通常不应使用人工密钥。桥表上的外键列(StudentId,ClassRoomId)需要是一个键,因此有一个额外的键是无用的开销。

查询M2M关系看起来像这样:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Ef6Test
{

    public class Student
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<StudentToClassRoom> StudentToClassRoom { get; set; } = new HashSet<StudentToClassRoom>();
    }


    public class StudentToClassRoom
    {

        [ForeignKey("Student"), Column(Order = 0), Key()]
        public int StudentId { get; set; }

        [ForeignKey("ClassRoom"), Column(Order = 1), Key()]
        public int ClassRoomId { get; set; }

        public virtual Student Student { get; set; }

        public virtual ClassRoom ClassRoom { get; set; }
    }

    public class ClassRoom
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
        public string Name { get; set; }
    }

    class Db: DbContext
    {

        public DbSet<Student> Students { get; set; }
        public DbSet<ClassRoom> Classrooms { get; set; }

        public DbSet<StudentToClassRoom> StudentToClassRoom { get; set; }

    }
    class Program
    {
        static void Main(string[] args)
        {
            Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Db>());

            using (var db = new Db())

            {

                var students = Enumerable.Range(1, 150).Select(i => new Student() { Name = $"Student{i}" }).ToList();
                var classRooms = Enumerable.Range(1, 20).Select(i => new ClassRoom() { Name = $"ClassRoom{i}" }).ToList();

                var rand = new Random();
                foreach( var s in students)
                {
                    var classRoomId = rand.Next(0, classRooms.Count - 10);
                    s.StudentToClassRoom.Add(new StudentToClassRoom() { Student = s, ClassRoom = classRooms[classRoomId] });
                    s.StudentToClassRoom.Add(new StudentToClassRoom() { Student = s, ClassRoom = classRooms[classRoomId+1] });
                    s.StudentToClassRoom.Add(new StudentToClassRoom() { Student = s, ClassRoom = classRooms[classRoomId+2] });

                }

                db.Students.AddRange(students);
                db.Classrooms.AddRange(classRooms);
                db.SaveChanges();

            }
            using (var db = new Db())
            {
                db.Configuration.LazyLoadingEnabled = false;
                var q = db.Students.Include("StudentToClassRoom.ClassRoom");

                var results = q.ToList();
                Console.WriteLine(q.ToString());


                Console.ReadKey();
            }

        }
    }
}