Search code examples
c#asp.net-mvcdapperdapper-extensions

Multi-Mapper in Dapper one to many relations


I am trying to get the value of my database with a relation one to many i have my object like this

Student

[Table("Student")]
    public class Student  : IStudent
    {    

        public int Id { get; set; }
        public string Lastname { get; set; }
        public string FirstMidName { get; set; }
        public DateTime? EnrollmentDate { get; set; }

        [Write(false)]
        public IEnumerable<Enrollment> Enrollments { get; set; }
    }

Enrollment

[Table("Enrollment")]
    public class Enrollment
    {
        public int Id { get; set; }
        public int CourseId { get; set; }
        public int StudentId { get; set; }
        public string Grade { get; set; }
        public virtual Course Course { get; set; }
        public virtual Student Student { get; set; }
    }

Course

[Table("Course")]
public class Course
{   
    public int Id { get; set; }
    public string Title { get; set; }
    public int Credits { get; set; }
    public virtual IEnumerable<Enrollment> Enrollments { get; set; }
}

Here is my dapper code

_connection = Connect.GetOpenConnection();
        const string query = @"SELECT * FROM Student stu where id = @id " +
                             "SELECT * FROM Enrollment enr WHERE enr.StudentId in (SELECT id FROM Student stu where id = @id) " +
                             "SELECT * FROM Course cou WHERE cou.Id in (SELECT CourseId from Enrollment where StudentId = @id)";

        var result = _connection.QueryMultiple(query, new { id = id })
            .Map<Student, Enrollment, Course, int>(
                student => student.Id,     
                enrollment => enrollment.StudentId,
                course=>course.Id,
                (student, enrollments) => student.Enrollments = enrollments ,
                (student, courses) => student.Enrollments.ForEach(s=>courses.ForEach(x=>s.Course.Title = x.Title) )
            ).FirstOrDefault();

as per instruction Here All i have to do is to extend 3 level hierarchy but i cant make it work

here is my GridReader with mapper

public static IEnumerable<TFirst> Map<TFirst, TSecond, TThird, TKey>
        (
        this SqlMapper.GridReader reader,
        Func<TFirst, TKey> firstKey,
        Func<TSecond, TKey> secondKey,
        Func<TThird, TKey> thirdKey,
        Action<TFirst, IEnumerable<TSecond>> addSecond,
        Action<TFirst, IEnumerable<TThird>> addThird
        )
    {
        var result = reader.Read<TFirst>().ToList();

        var secondMap = reader
            .Read<TSecond>()
            .GroupBy(s=>secondKey(s))
            .ToDictionary(g => g.Key, g => g.AsEnumerable());

        var thirdMap = reader
           .Read<TThird>()
           .GroupBy(t => thirdKey(t))
           .ToDictionary(g => g.Key, g => g.AsEnumerable());     


        foreach (var item in result)
        {
            IEnumerable<TThird> third;
            if (thirdMap.TryGetValue(firstKey(item), out third))
            {
                addThird(item, third);
            }

            IEnumerable<TSecond> second;
            if (secondMap.TryGetValue(firstKey(item), out second))
            {
                addSecond(item, second);
            }

        }



        return result.ToList();
    }

when i run my app here is the result with the id of 1 notice that the Course title have no value. i hope you can help me thank you. failed

UPDATE 1

I notice that i am getting the null value here, it doesnt get inside the if statement

IEnumerable<TSecond> second;
        if (secondMap.TryGetValue(firstKey(item), out second))
        {
            addSecond(item, second);
        }

UPDATE 2

I solve the problem here is what i do

var ctr = 0;
        var mapped2 = conn.QueryMultiple(query, new {id})
            .Map<Student, Enrollment, Course, int>(
                student => student.StudentId,
                enrollment => ctr = enrollment.StudentId,
                course=>course.CourseId = ctr,
                ((student, enrollments) => { student.Enrollments = enrollments; }),
                ((student, courses) => courses.ToList().ForEach(s=> student.Enrollments.ToList().ForEach(x=>x.Course = new Course
                {
                    Title = s.Title
                }))));

notice that i added ctr to get the value of CourseId in enrollment => ctr = enrollment.StudentId. Ok now I face another problem, how to get the value of courses here is my code

((student, courses) => courses.ToList().ForEach(s=> student.Enrollments.ToList().ForEach(x=>x.Course = new Course { Title = s.Title }))

i am only getting the last value


Solution

  • Here is how i solve my problem

    As the above UPDATE 2 details i added a variable ctr to get the value of CourseId from Enrollment and pass to Course to get the right value. After that i face another problem how to pass the value of Course from Student.Enrollment.Course here's how i figure it out.

    I create a variable var enrollLst = new List<Enrollment>(); to hold the value of Enrollment from the first action

    ((student, enrollments) =>
                    {
                        enrollLst = enrollments.ToList();
                    }),
    

    Second Action

    ((student, courses) =>
                    {
                        var ctrId = 0;  
                        courses.ToList().ForEach(cour =>
                        {
                            if (cour != null)
                            {
                                enrollLst[ctrId].Course = cour;
                                ctrId++;
                            }
                            else
                            {
                                enrollLst[ctrId].Course = null;
                                ctrId++;
                            }   
                        });
                        student.Enrollments = enrollLst;
                    })).FirstOrDefault(); 
    

    below is my whole code

    _connection = Connect.GetOpenConnection();
            const string query = "SELECT * from Student stu where StudentId = @id  " +
                                 "SELECT * from Enrollment enr where enr.StudentId in ( SELECT StudentId from Student stu where Studentid = @id)  " +
                                 "SELECT * FROM Course cou WHERE cou.CourseId in (SELECT CourseId from Enrollment where StudentId = @id)";
    
            var enrollLst = new List<Enrollment>();
            var ctr = 0;
            var result = _connection.QueryMultiple(query, new { id })
                .Map<Student, Enrollment, Course, int>(
                    student => student.StudentId,
                    enrollment => ctr = enrollment.StudentId,
                    course => course.CourseId = ctr,
                    ((student, enrollments) =>
                    {
                        enrollLst = enrollments.ToList();
                    }),
                    ((student, courses) =>
                    {
                        var ctrId = 0;  
                        courses.ToList().ForEach(cour =>
                        {
                            if (cour != null)
                            {
                                enrollLst[ctrId].Course = cour;
                                ctrId++;
                            }
                            else
                            {
                                enrollLst[ctrId].Course = null;
                                ctrId++;
                            }   
                        });
                        student.Enrollments = enrollLst;
                    })).FirstOrDefault(); 
    

    GridReader with mapper extended with 3 level hierarchy as instructed here

    public static IEnumerable<TFirst> Map<TFirst, TSecond, TThird, TKey>
            (
            this SqlMapper.GridReader reader,
            Func<TFirst, TKey> firstKey,
            Func<TSecond, TKey> secondKey,
            Func<TThird, TKey> thirdKey,
            Action<TFirst, IEnumerable<TSecond>> addSecond,
            Action<TFirst, IEnumerable<TThird>> addThird
            )
        {
            var result = reader.Read<TFirst>().ToList();
    
            var secondMap = reader
                .Read<TSecond>()
                .GroupBy(secondKey)
                .ToDictionary(g => g.Key, g => g.AsEnumerable());
    
            var thirdMap = reader
               .Read<TThird>()
               .GroupBy(thirdKey)
               .ToDictionary(g => g.Key, g => g.AsEnumerable());     
    
            foreach (var item in result)
            {
                IEnumerable<TSecond> second;
                if (secondMap.TryGetValue(firstKey(item), out second))
                {
                    addSecond(item, second);
                }
    
                IEnumerable<TThird> third;
                if (thirdMap.TryGetValue(firstKey(item), out third))
                {
                    addThird(item, third);
                }  
            }  
            return result.ToList();
        }