2017-08-21 50 views
1

我有一个应用程序使用C#编写的ASP.NET MVC 5和实体框架6的顶部使用数据库优先的方法。如何创建与实体框架6的Has-Many-Through关系?

我有一个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对象。

如何使用实体框架为每个学生获取Student和所有Class-Rooms的集合?

+0

不要浪费你的时间搜索。英孚不支持这种关系。 –

+0

@IvanStoev谢谢:)有没有解决方法来获取相同的数据集? –

+0

仅当您确实需要隐式联结表的“多对多”。尽管如果绑定到现有数据库,即使这存在问题(“StudentToClassRoom”不适用于自动联接表)。 –

回答

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。

我希望这有助于

+0

非常感谢。这工作! –

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() 
+0

我的'ClassRoom'没有'StudentId'属性/列,所以我可以从学生直接创建一个导航属性到'ClassRoom'。模型'StudentToClassRoom'需要能够识别两者之间的关系。 –

+0

我已编辑我的帖子。请再检查一次。 –

0

实体框架有一个更好的形式给出处理多对多的关系。

的EF的方式认为这是学生有教室和课堂上的学生:

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(); 

我希望我已经澄清了一点。

+0

我很困惑。我的'ClassRoom'没有'StudentId'属性/列,所以我可以从'Student'直接创建一个导航属性到'ClassRoom'。模型'StudentToClassRoom'需要能够识别两者之间的关系。 –

+0

我编辑了几个查询答案,我想会澄清一点关于如何使用这张地图。 –

1

既然你已经暴露的表桥,你可以去:

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

here

而且,你并不真正需要的[反]注解 - 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(); 
+0

这是一个好的开始,但有没有办法选择一个学生和许多ClassRooms,因此最终结果是访问student.ClassRooms? –

+0

请参阅编辑。另外,您的模型需要修复。 –

+0

非常感谢您的帮助。我认为@Jaylen的回答非常类似于这种方法,但工作。 –

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(); 
      } 

     } 
    } 
}