We have an existing SQL Server stored procedure with following query. We need to create a collection of Student
objects in the following class design, from the result of the query.
What is the best way to create the objects from the SqlDataReader
using LINQ
?
Note: I am using SqlDataReader
only; no ORM
Query
SELECT
S.StudentID, S.StudentName, E.ExamID, E.ExamName, SE.Mark
FROM
StudentExam SE
INNER JOIN
Student S ON S.StudentID = SE.StudentID
INNER JOIN
Exam E ON E.ExamID = SE.ExamID
Class
public class ExamMark
{
public int ExamID { get; set; }
public string ExamName { get; set; }
public int Mark { get; set; }
}
public class Student
{
public int StudentID { get; set; }
public string StudentName { get; set; }
public List<ExamMark> examResults { get; set; }
}
SqlDataReader
SqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
}
}
REFERENCES
In addition to returning output parameters, a stored procedure can return a result set (that is, a result table associated with a cursor opened in the stored procedure) to the application that issues the CALL statement.
To return a value from stored procedure, you need to use user defined session specific variable. Add @ symbol before variable name.
Well, I wouldn't do it like that,
I'd have two statements
-- Student Statement
SELECT
S.StudentID,
S.StudentName
FROM
Student S
WHERE
EXISTS (
SELECT * FROM StudentExam SE WHERE SE.StudentID = S.Student.ID);
-- Exam Statement
SELECT
SE.StudentID,
E.ExamID,
E.ExamName,
SE.Mark
FROM
StudentExam SE
JOIN
Exam E
ON E.ExamID = SE.ExamID;
Then, I'd have a function that does this,
private IEnumerable<Tuple<int, ExamMark>> GetMarks()
{
... setup the exam command here
var reader = examCommand.ExecuteReader();
while (reader.Read())
{
yield return Tuple.Create(
reader.GetInt32(0),
new ExamMark
{
reader.GetInt32(1),
reader.GetString(2),
reader.GetInt32(3)
});
}
}
Then I'd have this function to call,
private IEnumerable<Student> GetStudents()
{
var resultLookup = GetMarks().ToLookup(t => t.Item1, t => t.Item2);
... setup the student command here
var reader = studentCommand.ExecuteReader();
while (reader.Read())
{
var studentId = reader.GetInt32(0);
yield return new Student
{
studentId,
reader.GetString(1),
resultLookup[studentId].ToList()
});
}
}
If you wanted, you could do it all in one stored procedure and return multiple result sets.
This should do the work:
using (SqlDataReader reader = command.ExecuteReader())
{
var records = (from record in reader.Cast<DbDataRecord>()
select new
{
StudentID = record.GetInt32(0),
StudentName = record.GetString(1),
ExamID = record.GetInt32(2),
ExamName = record.GetString(3),
Mark = record.GetInt32(4)
})
.GroupBy(r => new { StudentID = r.StudentID, StudentName = r.StudentName })
.Select(
r => new Student
{
StudentID = r.Key.StudentID,
StudentName = r.Key.StudentName,
examResults = r.Select(e => new ExamMark
{
ExamID = e.ExamID,
ExamName = e.ExamName,
Mark = e.Mark
}).ToList()
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With