I am working with the following technologies: C#, SQL Server, ASP.NET and Entity Framework and Linq.
I have a many-to-many relation in my database:
The model classes:
public class Courses
{
[Required]
public int Id { get; set; }
//more properties here
public student stud { get; set; }
}
public class inscribe
{
[Key]
public intId { get; set; }
//properties here
public student student{ get; set; }
[Required]
[ForeignKey("student")]
public string StudentId{ get; set; }
public Courses Courses{ get; set; }
}
Given a student Id, I would like to return a list of courses where he/she is inscribed.
This is what I have tried so far:
public IEnumerable<CursoDto> GetCursosAlumno(Int studentId)
{
//some code here to validate
var x = _dbContext
.Inscribe.Include(c => c.Courses)
.Where(c => c.StudentId == studentId).toList();
// x variable is a list<inscribe>
}
My problem is that I do not know how to access to the courses entity and return it as a list, for instance:
var result = X.Courses;
return result; //result is a list<courses>
How can I do it? If possible, not using a foreach block please.
Thanks
In Code First approach you don't need to add "link table" (inscribe in OP) into your models (it will be created transparently).
//Models
public class Course
{
[Key]
public int Id { get; set; }
//more properties here
public virtual /*important*/ ICollection<Student> studs { get; set; }
}
public class Student
{
[Key]
public int Id { get; set; }
//more properties here
public virtual /*important*/ ICollection<Course> courses { get; set; }
}
//Controller
var stud = _dbContext.studs.Where(s => s.Id == /*param*/id).FirstOrDefault();
var courses = stud.courses.ToList();//Real courses with id, name, etc. No Include required
Update
If you do need the "link table" (for example to add some properties like sortOrder or enrollmentDate) then the models will be a little different.
//Models
public class Course
{
[Key]
public int Id { get; set; }
//more properties here
public virtual /*important*/ ICollection<StudentCourse> studs { get; set; }
}
public class Student
{
[Key]
public int Id { get; set; }
//more properties here
public virtual /*important*/ ICollection<StudentCourse> courses { get; set; }
}
[Table("inscribe")]
public class StudentCourse
{
[Key, Column(Order = 0)]
public int StudentId {get; set'}
[Key, Column(Order = 1)]
public int CourseId {get; set'}
//extra properties
[ForeignKey("StudentId")]
public virtual Student stud { get; set; }
[ForeignKey("CourseId")]
public virtual Course course { get; set; }
}
//Controller
var courses = _dbContext.courses.Where(c/*c is "link"*/ => c.Student/*StudentCourse prop*/.Any(s/*link again*/ => s.StudentId == someId/*param*/));//again courses
As you see Include is not required.
var result = _dbContext
.Inscribe.Include(c => c.Courses)
.Where(c => c.StudentId == studentId)
.SelectMany(c => c.Courses).ToList();
Related
I have a many-to-many relationship set up with Entity Framework like this:
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public ICollection<StudentCourse> StudentCourses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public ICollection<StudentCourse> StudentCourses { get; set; }
}
public class StudentCourse
{
public int StudentId { get; set; }
public Student Student { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
}
This works great, I have Students and I have Courses, and StudentCourses is the many-to-many relationship between them.
I also have an Advisor class, which has a collection of StudentCourses that the Advisor is in charge of:
public class Advisor
{
public int AdvisorId { get; set; }
public ICollection<StudentCourse> StudentCourses { get; set; }
}
I would like to get a collection of Advisors and the StudentCourses they're in charge of, but also the data properties from the Student and Course objects (like Name), all at once. This works for me:
var advisors = await _dbContext.Advisors
.Include(a => a.StudentCourses)
.ThenInclude(sc => sc.Student)
Include(a => a.StudentCourses)
.ThenInclude(sc => sc.Course)
.ToListAsync();
But is this the only way I can do that? Seems wrong to have that duplicate Include statement
I have an application written using c# on the top of the ASP.NET MVC 5 and Entity Framework 6 using Database-First approach.
I have a Student model, a ClassRoom model and a relational model to link the two relations together called StudentToClassRoom.
I want to be able to select all students and for each student I want to get all the ClassRoom that the student has relation too.
Here are my models
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; }
}
Here is what I tried
var students = DbContext.Students.Include(x => x.ClassRoomRelations)
.ToList();
However, that gives me the relation collection for each student. But I want to be able to get the ClassRoom information for each student. So I want to create a Has-Many-Through between Student and ClassRoom. In the end results, I don't really care about the ClassRoomRelations, I only care about the Student and theClassRoom objects.
How can I get a list of Student and a collection of all Class-Rooms for each student using Entity Framework?
Since you've exposed the bridge table, you could go with:
var studentRooms = DbContext.StudentToClassRoom
.Include(x => x.Student)
.Include(x => x.ClassRoom)
.ToList();
See here
Also, you don't really need the [Inverse] annotations - EF knows you are linking to Id with the FK.
EDIT: A student and their classrooms
First you will need to fix your student model:
public virtual ICollection<StudentToClassRoom> ClassRoomRelations { get; set; }
Then you can run
var studentAndRooms = DbContext.Students
.Select(s => new
{
student = s,
classrooms = s.ClassRoomRelations.Select(r => r.ClassRoom)
}).ToList();
Why don't you use simply ? You can get student's classroom information already.
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()
If you want to use an explicit bridge table, it should generally not have an artificial key. The Foreign Key columns (StudentId,ClassRoomId) on the bridge table need to be a key, and so having an extra key is useless overhead.
And querying across the M2M relationship looks like this:
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();
}
}
}
}
Entity Framework has a better aproach to handle many to many relationships.
The EF's way to think this is Student has classrooms and ClassRoom has students:
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; }
}
The relationship table is completely ignored in EF map. Give a look in this tutorial.
EDIT:
Below a few queries to simplify how to use this map scenario:
Get a list of all class rooms of a specific student:
var classRoomsOfSpecificStudent = DbContext
.Students
.First(s => s.Id == studentId)
.ClassRooms
.ToList();
Get a list of all class rooms of students that has a name containing "a".
var classRooms = DbContext
.Students
.Where(s => s.Name.Contains("a"))
.SelectMany(s => s.ClassRooms)
.ToList();
Get all students that has name containing "a" and the class rooms' name containing "2b".
var students = DbContext
.Students
.Where(s => s.Name.Contains("a"))
.Where(s => s.ClassRooms.Any(c => c.Name.Contains("2b")))
.ToList();
I hope I have clarified a little.
Currently I'm working with WebApi and Entity Framework, So I have 3 entities: Products, Categories and ProductCategory; their relationships are:
My problem is that Category entity has a Category Parent property, so it's recursive, my Category Controller looks like this:
public async Task<IHttpActionResult> GetCategory()
{
var category = await db.Category.Select(x=>new {
x.categoryDesc,
x.CategoryId,
x.categoryImage,
x.categoryName,
x.categoryParent
}).ToListAsync();
return Ok(category);
}
I'm returning an anonymous object, the propierty categoryParent its the same object as category so its recursive; when I fill the database with mock data in the Category table and call the get method, everything runs OK because I dont have any data en ProductCategory, but when I fill it(the ProductCategory table) the program crashes.
MY entity classes are:
public class Category {
public int CategoryId { set; get; }
public string categoryName { set; get; }
public string categoryDesc { set; get; }
public string categoryImage { set; get; }
public int? categoryParentId { set; get; }
public virtual ICollection<ProductCategory> ProductCategories { set; get; }
public virtual Category categoryParent { set; get; }
}
public class Product{
public int ProductId { set; get; }
public string productName { set; get; }
public string productDesc { set; get; }
public double productPrice { set; get; }
public string productUrl { set; get; }
public DateTime productPublishDate { set; get; }
public DateTime productModifyDate { set; get; }
public bool productStatus { set; get; }
public int productStock { set; get; }
public virtual ICollection<ProductCategory> ProductCategories { set; get; }
}
public class ProductCategory : IProductCategory {
[Required]
[Key]
[ForeignKey("Category")]
[Column(Order = 1)]
public int CategoryId { set; get; }
[Required]
[Key]
[ForeignKey("Product")]
[Column(Order = 2)]
public int ProductId { set; get; }
public virtual Product Product { set; get; }
public virtual Category Category { set; get; }
}
Can you help me to fix it?, So when I return categoryParent return it recursively, Thanks
I'm guessing you might have better luck if you explicitly state how you want the information organized, and remove the virtual property
IQueryable<Category> category = db.Category;
var result = category.Where(w => w.categoryParentId != null)
.Join(category,
child => (int)child.categoryParentId,
parent => parent.CategoryId,
(child, parent) => new {
child.categoryDesc,
child.CategoryId,
child.categoryImage,
child.categoryName,
parent
}
);
return Ok(await result.ToListAsync());
That should get you the same result as your query above, then you could remove:
public virtual Category categoryParent { set; get; }
Thank you very much but I found the solution: https://practiceaspnet.wordpress.com/2015/11/09/many-to-many-relationships-with-additional-fields/
I used fluent API to resolve the navigation recursive problem I had:
modelBuilder.Entity<Category>()
.HasMany(x => x.ProductCategories)
.WithRequired(x => x.Category)
.HasForeignKey(x=>x.CategoryId);
modelBuilder.Entity<Product>()
.HasMany(x => x.ProductCategories)
.WithRequired(x => x.Product)
.HasForeignKey(x => x.ProductId);
Basically the WithRequired method prevents a navigation property on the other side of the relationship so it stops the recursion
Assuming I've the following Entity Framework Code-First models:
public class Employee {
[Key]
public int Id { get; set; }
public int DepartmentId { get; set; }
[ForeignKey(nameof(DepartmentId ))]
public Department Department { get; set; }
public DateTime EntryDate { get; set; }
}
public class Manager {
[Key]
public int Id { get; set; }
public int DepartmentId { get; set; }
[ForeignKey(nameof(DepartmentId ))]
public Department Department { get; set; }
}
public class Department {
[Key]
public int Id { get; set; }
[InverseProperty(nameof(Employee.Department))]
public virtual ICollection<Employee> Employees { get; set; }
[InverseProperty(nameof(Manager.Department))]
public virtual ICollection<Manager> Managers { get; set; }
}
And the following DTO object:
public class MyDto {
public int ManagerId { get; set; }
public Employee NewestEmployee { get; set; }
}
I want to use AutoMapper to convert a list of managers to a list of DTO objects. AutoMapper configuration is like this:
CreateMap<Manager, MyDto>()
.ForMember(
m => m.NewestEmployee,
opt => opt.MapFrom(
manager =>
manager.Department.Employees.OrderBy(employee => employee.EntryDate).FirstOrDefault()))
And called like this:
IEnumerable<Manager> managers = GetAllManagers();
var data = Mapper.Map<IEnumerable<MyDto>>(managers);
While this works it makes individual SQL statement requests for each manager to the employee table. Is there a way to improve performance by having only one SQL query executed (either by changing something in the Entity Framework models or AutoMapper configuration)?
I'm trying to retrieve a list of UserProfile-objects of a given CustomerId. I Have theese pocos:
public class UserRole
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserRoleId { get; set; }
public Role Role { get; set; }
public Customer Customer { get; set; }
public virtual UserProfile UserProfile { get; set; }
}
public class Customer
{
public Customer()
{
this.UserRoles = new Collection<UserRole>();
}
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int CustomerId { get; set; }
[Display(Name = "Gruppens namn")]
public string Name { get; set; }
[InverseProperty("Customers")]
public virtual ICollection<UserRole> UserRoles { get; set; }
}
I can't seem to find a way to actually get the UserProfiles. I can get a list of UserRoles by doing:
dbContext.Customers.First(c => c.CustomerId == customer.CustomerId).UserRoles
but cannot access UserProfiles of of that object. I also tried
dbContext.UserRoles.Where(c => c.Customer.CustomerId == customerId)
but same result. Ideas?
To get the list of profiles for a specific customer:
var result = dbContext.UserRoles
.Where(ur => ur.Customer.CustomerId == customer.CustomerId)
.Select(ur => ur.UserProfile);