How to join two collection and transpose a collection as an entity using LINQ (c#)
Question
I have the following classes:
public class Student
{
public string StudentID { get; set; }
public string StudentName { get; set; }
}
public class Marks
{
public string StudentID { get; set; }
public string SubjectName { get; set; }
public string Score { get; set; }
}
Populating the collection:
Collection<Student> objStudentCollection = new Collection<Student>();
Student student = new Student();
student.StudentID = "104517";
student.StudentName = "John";
objStudentCollection.Add(student);
student = new Student();
student.StudentID = "104520";
student.StudentName = "Stella";
objStudentCollection.Add(student);
Collection<Marks> objMarkCollection = new Collection<Marks>();
Marks marks = new Marks();
marks.StudentID = "104517";
marks.SubjectName = "English";
marks.Score = "85";
objMarkCollection.Add(marks);
marks = new Marks();
marks.StudentID = "104517";
marks.SubjectName = "Science";
marks.Score = "60";
objMarkCollection.Add(marks);
marks = new Marks();
marks.StudentID = "104517";
marks.SubjectName = "Mathematics";
marks.Score = "75";
objMarkCollection.Add(marks);
marks = new Marks();
marks.StudentID = "104517";
marks.SubjectName = "Optional 1";
marks.Score = "75";
objMarkCollection.Add(marks);
marks = new Marks();
marks.StudentID = "104520";
marks.SubjectName = "French";
marks.Score = "54";
objMarkCollection.Add(marks);
marks = new Marks();
marks.StudentID = "104520";
marks.SubjectName = "Science";
marks.Score = "60";
objMarkCollection.Add(marks);
marks = new Marks();
marks.StudentID = "104520";
marks.SubjectName = "Mathematics";
marks.Score = "75";
objMarkCollection.Add(marks);
marks = new Marks();
marks.StudentID = "104520";
marks.SubjectName = "Optional 1";
marks.Score = "50";
objMarkCollection.Add(marks);
I would like to bind the above collections in GridView to display like:
StudentID | StudentName | English | French | Mathematics | Science | Optional 1 | Total
Solution
I think you need to utilise the GroupJoin method to get what you're after. You could do it like this if the list of subjects if fixed and not going to change:
var q =
objStudentCollection
.GroupJoin(
objMarkCollection,
stu => stu.StudentID,
mark => mark.StudentID,
(stu, mark) =>
new
{
student.StudentID,
student.StudentName,
English = mark.Where(m => m.SubjectName == "English").Sum(m => Convert.ToInt32(m.Score)),
French = mark.Where(m => m.SubjectName == "French").Sum(m => Convert.ToInt32(m.Score)),
Mathematics = mark.Where(m => m.SubjectName == "Mathematics").Sum(m => Convert.ToInt32(m.Score)),
Science = mark.Where(m => m.SubjectName == "Science").Sum(m => Convert.ToInt32(m.Score)),
Optional1 = mark.Where(m => m.SubjectName == "Optional 1").Sum(m => Convert.ToInt32(m.Score)),
Total = mark.Sum(m => Convert.ToInt32(m.Score)),
})
It's not the prettiest code in the world, but it will give you an anonymous type with each row containing data you asked for. You could replace Sum
with SingleOrDefault
if there is only ever one mark per subject.
var subjects =
objMarkCollection
.Select(mark => mark.SubjectName)
.Distinct()
.Dump();
var q =
objStudentCollection
.GroupJoin(
objMarkCollection,
stu => stu.StudentID,
mark => mark.StudentID,
(stu, mark) =>
new
{
student.StudentID,
student.StudentName,
Marks =
from s in subjects
join m in mark on s equals m.SubjectName into outer
from o in outer.DefaultIfEmpty()
select new
{
SubjectName = s,
Score = (o == null) ? 0 : Convert.ToInt32(o.Score),
},
Total = mark.Sum(m => Convert.ToInt32(m.Score)),
})
.Dump();
This 2nd solution will create you an anonymous type with each student and a collection of marks including each subject (those the student has not take will have a 0 score).