1

I'm developing a test application using ASP.Net MVC 5, Entity Framework with SQL Server.

In my scenario there are 2 tables in the database. Student and Qualification. one Student has one or more qualifications.

I need to get a student from the database by giving studentID and the Qualification list of the Student (Navigation property) should contain all the qualifications he or she has.

The controller method which I have implemented as follows.

        public JsonResult getStudentInfo(int studentId)
    {
        db.Configuration.ProxyCreationEnabled = false;
        Student ss = (Student) (from s in db.Students where s.Id == studentId select s).FirstOrDefault();
        ss.Qualifications = (from q in db.Qualifications where q.StudentId == ss.Id select q).ToList();
        return Json(ss, JsonRequestBehavior.AllowGet);
    }

But when I am trying to call this function, it shows this error.

A circular reference was detected while serializing an object of type 'App1.Models.Student'.

How to fix this issue. I need to pass full list of qualification with the Student instance.

My Student Model class as follows

    public partial class Student
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Student()
    {
        this.Qualifications = new HashSet<Qualification>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public Nullable<int> Age { get; set; }
    public Nullable<byte> Gender { get; set; }
    public string City { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Qualification> Qualifications { get; set; }
}

My Qualification Model class as follows

    public partial class Qualification
{
    public int Id { get; set; }
    public Nullable<int> StudentId { get; set; }
    public string QualificationName { get; set; }
    public string Institute { get; set; }
    public Nullable<System.DateTime> EffectiveDate { get; set; }

    public virtual Student Student { get; set; }
}

Above Model class has been generated by using Entity Data Model. I follow Database First Approach..

Thanks in advance.

JayNaz
  • 343
  • 1
  • 5
  • 24
  • What does the Qualification class look like? – Robert Oct 05 '17 at 17:28
  • @Robert Sorry for that. I just updated the question. – JayNaz Oct 05 '17 at 17:30
  • 1
    Yep ... I'm guessing Robert guessed right; student->qualification->student. is your circular reference. In the past, I wrote code to set Student.Qualification.Student to null so that this wouldn't happen. I'm sure there are better solutions. I don't know whether it would fix this or not, but you might try this library: https://github.com/zzzprojects/GraphDiff – theGleep Oct 05 '17 at 17:32
  • I think you may not need the virtual Student in Qualification, though I could be wrong. If you needed to grab the parent student, could (from s in db.Students where s.Id == qualification.StudentId select s).FirstOrDefault(); or similar – Robert Oct 05 '17 at 17:33
  • or just create a public Student { get { return Student.GetById(this.StudentId); }} or something to that effect – Robert Oct 05 '17 at 17:35
  • @Robert These model classes have been generated by automatically by the Entity Framework since I am using Database First Approach. I tried it by removing the `Student` property in `Qualification` Then it gives an error as `Schema specified is not valid` – JayNaz Oct 05 '17 at 17:41
  • The fastest/easiest way to get around the problem is to create a view model class (*or use an anonymous type/projection*) that you return to the caller which will be similar in most regards to the EF model (minus the circular dependency). Alternatively you can decorate properties/collections with JsonIgnore which will prevent the serialization of those properties. – Igor Oct 05 '17 at 18:05

1 Answers1

1

Couple things:

  1. I used nullable bit as the type of the Gender from Student class, not byte.
  2. You said one student has one or more qualifications, but you didn't mention the same qualification could be shared among different students. If yes then you need a third table StudentQualification to store the many-to-many configuration. Otherwise, your two tables setup is fine but I still wouldn't set the StudentId in Qualification table nullable, because a qualification is nothing without a student. One student has one or more qualifications, then one qualification must have one student, if you think about it the other way around.

This is the code generated by Microsoft Entity Framework.

Student

namespace DL.SO.StudentQualification.Data
{
    using System;
    using System.Collections.Generic;

    public partial class Student
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public Student()
        {
            this.Qualifications = new HashSet<Qualification>();
        }

        public int Id { get; set; }
        public string Name { get; set; }
        public Nullable<int> Age { get; set; }
        public Nullable<bool> Gender { get; set; }
        public string City { get; set; }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<Qualification> Qualifications { get; set; }
    }
}

Qualification

namespace DL.SO.StudentQualification.Data
{
    using System;
    using System.Collections.Generic;

    public partial class Qualification
    {
        public int Id { get; set; }
        public int StudentId { get; set; }
        public string QualificationName { get; set; }
        public string Institute { get; set; }
        public Nullable<System.DateTime> EffectiveDate { get; set; }

        public virtual Student Student { get; set; }
    }
}

And here is the code for the Controller method to get the student.

    public JsonResult GetStudentInfo(int studentId)
    {
        using (var db = new AppDbContext())
        {
            // Method syntax
            var student = db.Students
                .SingleOrDefault(x => x.Id == studentId);

            // Query syntax
            //var student =
            //    from s in db.Students
            //    where s.Id == studentId
            //    select s;

            return Json(student, JsonRequestBehavior.AllowGet);
        }
    }

You don't need to look for a student and its qualifications separately (otherwise why you would use EF) because EF's navigation properties can help you there. I think that's where your error came from.

If you do student.Qualifications, there you have a list of qualifications for that student.

Other recommendations:

  1. Never directly return database models back to the page, i.e., return it to the view or return it as Json object, because you don't want to expose properties you don't want to expose to public. Use ViewModel to define what property you really want to expose and transfer data to the view model. Return the view model instead.
  2. You might consider using repository pattern and have the DbContext inject into repositories. There are tons of materials out there. You can google and study.
David Liang
  • 20,385
  • 6
  • 44
  • 70