how to populate navigational properties when using Entity Framework - c#

I am using Entity Framework, Database first and need a good solution to the below problem
I have an Entity say Courses generated by Entity Framework
class Courses
{
public Courses()
{
this.Students=new HashSet<Student>();
}
public int courseid{get;set;}
public virtual ICollection<Student> Students{get;set}
}
Class Student
{
public int id{get;set;}
public string Name{get;set;}
}
i created Model classes in my business layer which corresponds to these Entity Classes
class Courses_Model
{
public Courses()
{
this.Students=new HashSet<Student>();
}
public int courseid{get;set;}
public virtual ICollection<Student> Students{get;set}
}
Class Student_Model
{
public int id{get;set;}
public string Name{get;set;}
}
i want to return the model class(Courses_Model) from my web api method which should include navigation property students instead of
public Courses GetCourses(string id)
{
var course= (from g in con.Courses.Include("Student") where g.REQUESTER_REFERENCE == id select g).First();
return course;
}
To return Courses_Model we can create new Courses_Model object while returning like, but i am not sure how to populate
public Courses_Model GetCourses(string id)
{
Course= con.Courses.Include("Student").Select(g => new Courses_Model{courseid=g.courseid }).Where(g => g.REQUESTER_REFERENCE == id).First();
return course;
}

In your projection you need to instantiate Student_Model as well (also I would suggest projecting after Where and using lambda expressions rather than strings for Include):
public Courses_Model GetCourses(string id)
{
Course= con.Courses.Include(x => x.Students)
.Where(g => g.REQUESTER_REFERENCE == id)
.Select(
g => new Courses_Model
{
courseid = g.courseid,
Students = g.Students.Select(x => new Student_Model { id = x.id, Name = x.Name })
})
.First();
return course;
}
On a side note, libraries like AutoMapper are great for abstracting this kind of thing.

below is a simple sample I created for you
public class STUDENTS
{
public STUDENTS()
{
COURSES = new List<COURSES>();
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ST_ROWID { get; set; }
public int ST_NAME { get; set; }
[ForeignKey("CR_SM_REFNO")]
public virtual List<COURSES> COURSES { get; set; }
}
public class COURSES
{
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CR_ROWID { get; set; }
public string CR_NAME { get; set; }
public int CR_SM_REFNO { get; set; }
[ForeignKey("CR_SM_REFNO")]
public virtual STUDENTS STUDENTS { get; set; }
}
and the following methods do the job:
// gets the list of courses taken by the student id
public List<COURSES> GetCoursesByStudent(int pST_ROWID)
{
using (var con = new MPContext())
{
return con.COURSES.Include(x=>x.STUDENTS).
Where(x => x.CR_SM_REFNO.Equals(pST_ROWID)).ToList();
}
}
//Gets the list of students who get the course with the course id
public List<STUDENTS> GetStudentsByCourse(int pCR_ROWID)
{
using (var con = new MPContext())
{
return con.STUDENTS.Include(x => x.COURSES).
Where(x => x.COURSES.Any(y=>y.CR_ROWID.Equals(pCR_ROWID))).ToList();
}
}

Related

Reference navigation property not loading (Entity Framework)

I got 3 models: Human, Skill and HumanSkill. There is a many to many relationship between Human and Skill, the HumanSkill is the intermediary table between them.
My query to the database loads the collection of the intermediary table HumanSkill correctly, but does not load the reference navigation property Skill through which I want to load the Skill name (Human -> HumanSkill -> Skill -> Skill.name) using a query projection with select.
public IActionResult Preview(int humanId)
{
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
PrimaryData = r.PrimaryData,
// How should I write this line?
Skills = r.Skills.ToList(),
}).SingleOrDefault();
return View(currentResume);
}
Human model:
public class Human
{
public Human()
{
this.Skills = new HashSet<HumanSkill>();
}
public int Id { get; set; }
public virtual PrimaryData PrimaryData { get; set; }
public virtual ICollection<HumanSkill> Skills { get; set; }
}
HumanSkill model:
public class HumanSkill
{
public int Id { get; set; }
public int HumanId { get; set; }
public Human Human { get; set; }
public int SkillId { get; set; }
public Skill Skill { get; set; }
}
Skill model:
public class Skill
{
public Skill()
{
this.Humans = new HashSet<HumanSkill>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HumanSkill> Humans { get; set; }
}
HumanPreviewViewModel:
public class HumanPreviewViewModel
{
public HumanPreviewViewModel()
{
}
public PrimaryData PrimaryData { get; set; }
public List<HumanSkill> Skills { get; set; }
}
}
How can I achieve this without using include?
If you use some data from Skills table in the Select, EF will perform the necessary joins to retrieve the data
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
PrimaryData = r.PrimaryData,
SkillNames = r.Skills.Select(hs => hs.Skill.Name).ToList(),
}).SingleOrDefault();
When projecting from entity to a view model, avoid mixing them. For example, do not have a view model contain a reference or set of entities. While it might not seem necessary, if you want a list of the skills with their ID and name in the HumanPreviewViewModel then create a serialize-able view model for the skill as well as the PrimaryData if that is another related entity. Where PrimaryData might be a one-to-one or a many-to-one the desired properties from this relation can be "flattened" into the view model.
[Serializable]
public class HumanPreviewViewModel
{
public Id { get; set; }
public string DataPoint1 { get; set; }
public string DataPoint2 { get; set; }
public List<SkillViewModel> Skills { get; set; }
}
[Serializable]
public class SkillViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
Then when you go to extract your Humans:
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
Id = r.Id,
DataPoint1 = r.PrimaryData.DataPoint1,
DataPoint2 = r.PrimaryData.DataPoint2,
Skills = r.Skills.Select(s => new SkillViewModel
{
Id = s.Skill.Id,
Name = s.Skill.Name
}).ToList()
}).SingleOrDefault();
The reason you don't mix view models and entities even if they share all of the desired fields is that your entities will typically contain references to more entities. When something like your view model gets sent to a Serializer such as to send to a client from an API or due to a page calling something an innocent looking as:
var model = #Html.Raw(Json.Encode(Model));
then the serializer can, and will touch navigation properties in your referenced entities which will trigger numerous lazy load calls.

Entity Framework Core - Call A Populate Extension Method Inside Include

I will explain my issue using an example.
Lets say I have this following classes and methods (I created them only for this example)
public class Student
{
public string Name { get; set; }
public string Id { get; set; }
public Subject Expertise { get; set; }
}
public class Subject
{
public string Name { get; set; }
public Teacher Teacher { get; set; }
}
public class Teacher
{
public string Name { get; set; }
public string LicenseId{ get; set; }
public License License { get; set; }
}
public class License
{
public string LicsenseType;
}
public static IQueryable<Subject> PopulateWithTeacherAndLicense(this IQueryable<Subject> subjects)
{
return subjects
.Include(c => c.Teacher)
.ThenInclude(p => p.License);
}
Now lets assume I want to select all students with all their subject,teacher and license. In order to do so, I want to use my PopulateWithTeacherAndLicense. I want the query to look something like:
db.Students.Include(s => s.Expertise.PopulateWithTeacherAndLicense())
And not have to do Include(s=>s.Expertise).TheInclude(s => s.Teacher)...
You can create extension method for Student collection itself
public static IQueryable<Student> IncludeExpertise(this IQueryable<Student> students)
{
return students
.Include(s => s.Expertise)
.ThenInclude(c => c.Teacher)
.ThenInclude(p => p.License);
}

Best way to update an object containing a list of objects in Entity Framework

I have the following models in my API:
namespace API.Models
{
public class StudentDetailsViewModel
{
[Key]
public int StudentId { get; set; }
public AddressViewModel Address { get; set; }
public List<CoursesViewModel> Courses { get; set; }
}
public class AddressViewModel
{
public int AddressId { get; set; }
public int StudentId { get; set; }
public string Address { set; set; }
}
public CoursesViewModel
{
public int CourseId { get; set; }
public int StudentId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Grade { get; set; }
}
}
I am writing a PUT method for StudentDetailsViewModel. The list in this model could have a number of records removed or added or a number of fields in one of the records updated. For example, grade for one of the courses updated or a course added or dropped.
What is the best approach in updating a model containing an object list like the above? Is it best to delete the entire list and re-add them?
I have the following thus far:
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutStudenDetailsViewModel(StudentDetailsViewModel studentDetailsViewModel)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);
var address = new DataAccess.Address
{
AddressID = studentDetailsViewModel.Address.AddessId,
StudentID = studentDetailsViewModel.Address.StudentId,
Address = studentDetailsViewModel.Address.Address
};
_context.Entry(address).State = EntityState.Modified;
// TODO: This is where the list Course entity needs to be updated
try
{
await _context.SaveChangesAsync();
}
catch(DbUpdateConcurrencyException)
{
if(!AddressViewModelExists(address.AddressID))
return NotFound();
throw;
}
return StatusCode(HttpStatusCode.NoContent);
}
Just an example from MS documentation for EF Core
public static void InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
var existingBlog = context.Blogs
.Include(b => b.Posts)
.FirstOrDefault(b => b.BlogId == blog.BlogId);
if (existingBlog == null)
{
context.Add(blog); //or 404 response, or custom exception, etc...
}
else
{
context.Entry(existingBlog).CurrentValues.SetValues(blog);
foreach (var post in blog.Posts)
{
var existingPost = existingBlog.Posts
.FirstOrDefault(p => p.PostId == post.PostId);
if (existingPost == null)
{
existingBlog.Posts.Add(post);
}
else
{
context.Entry(existingPost).CurrentValues.SetValues(post);
}
}
}
context.SaveChanges();
}

Entity Framework returns ICollection<> with no values

I have 2 entities in a Many-To-Many relationship and I'd like to add a student to a class, but after I receive the class object from a query it shows that the property is empty. I've seen lots of similar problems but no answer really helped me in this case.
Here are my classes:
Student Entity
class Student
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Surname { get; set; }
public Student()
{
Classes = new HashSet<Class>();
}
public virtual ICollection<Class> Classes { get; set; }
}
Class Entity
class Class
{
public virtual int Id { get; set; }
public virtual string ClassName { get; set; }
public Class()
{
Students = new HashSet<Student>();
}
public virtual ICollection<Student> Students { get; set; }
}
Context
class DatabaseContext : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Class> Classes { get; set; }
public DatabaseContext()
{
Database.SetInitializer<DatabaseContext>(new Initializer());
}
}
Initializer
class Initializer : DropCreateDatabaseAlways<DatabaseContext>
{
protected override void Seed(DatabaseContext context)
{
Student student1 = new Student { Id = 1, Name = "Name", Surname = "Surname" };
Class class1 = new Class { Id = 1, ClassName = "Math"};
class1.Students.Add(student1); // The Count of the collection is 1
context.Students.Add(student1);
context.Classes.Add(class1);
base.Seed(context);
}
}
Now when I try and receive the object through a method, the Count of the collection is 0
public static Class GetClass(int classId)
{
using (var context = new DatabaseContext())
{
Class receivedClass = context.Classes.Find(classId); // The collection is empty, the ClassName is there, though
return receivedClass;
}
}
I'd like to know how can I add an object to a Collection of another object and then be able to also retrieve the object with the contents in the Collection
I think you're looking for .Include(). There are a couple different strategies for loading related entities.
using (var context = new DatabaseContext())
{
// Load all students and related classes
var classes1 = context.Classes
.Include(s => s.Students)
.ToList();
// Load one student and its related classes
var classt1 = context.Classes
.Where(s => s.Name == "someClassName")
.Include(s => s.Students)
.FirstOrDefault();
// Load all students and related classes
// using a string to specify the relationship
var classes2 = context.Classes
.Include("Students")
.ToList();
// Load one student and its related classes
// using a string to specify the relationship
var class2 = context.Classes
.Where(s => s.Name == "someName")
.Include("Students")
.FirstOrDefault();
}

One to many linq Eager load

I am working with the following technologies: C#, SQL Server, ASP.NET and Entity Framework and Linq.
I have a many to many relation , using eager load. I want to get all the courses where a student is inscribed. As you can see I have a one to many relation from student to inscribe table.
The model classes:
public class Courses
{
[Required]
public int Id { get; set; }
//more properties here
public student stud { get; set; }
}
public class Enroll
{
[Key]
public intId { get; set; }
//properties here
[Required]
public string StudentId{ get; set; }
public Courses Courses{ get; set; }
}
public class student{
public intId { get; set; }
//other properties
public Inscripe Inscription {get;set}
}
This is what my controller:
public IEnumerable<course> GetCoursesStudent(Int studentId)
{
//some code here to validate
var result = _dbContext
.Enroll.Include(c => c.Courses)
.Where(c => c.StudentId == studentId)
.SelectMany(c => c.Courses).ToList();
}
problem :
I receive an error from the SelectMany: the type aregument for method Queribly.selectMany(IQueryableExpression>> can not be infered from the usage.
How can I fix it?
The type you specified for the IEnumerable is wrong. It should be "Courses" instead of "course":
public IEnumerable<Courses> GetCoursesStudent(Int studentId)
{
var result = _dbContext
.Enroll
.Include(c => c.Courses)
.Where(c => c.StudentId == studentId)
.SelectMany(c => c.Courses)
.ToList();
}
And the "Courses" property of Enroll class should be an enumerable:
public class Enroll
{
[Key]
public intId { get; set; }
[Required]
public string StudentId { get; set; }
public IEnumerable<Courses> Courses { get; set; }
}

Categories