Passing LINQ2SQL anonymous results among application layers in ASP.NET MVC

Ever had a LINQ-to-SQL query that returns an anonymous type and you needed to pass the result of your query to the view? probably yes :)

Today I was faced with a problem that required me to query several table, extract the results from them and finally create a list of objects with nested values and all this should be passed to the view so that there I could iterate it and show a sensible results.

Let's first examine the tables I needed to query, here you have the schema containing the significant fields I'll need:

 

Table User Table TutorSubjects Table Subject Table Comment
Id: Guid TutorId: Guid Id: Guid TutorId: Guid
FirstName: string SubjectId: Guid Name: string Rating: int
LastName: string      
AboutMe: string      

These are the significant fields I required to get a list of Tutors along with their ratings and their subjects. The SQL query I wrote for it was:

   1:  SELECT T.Id,
   2:         T.FirstName, 
   3:         T.LastName, 
   4:         T2.Rating, 
   5:         TutorSubjects.Id, 
   6:        (SELECT [Subject].Name
   7:         FROM [Subject]
   8:         WHERE [Subject].Id = TutorSubjects.SubjectId) SubjectName,
   9:         T.AboutMe
  10:  FROM [User] T
  11:       LEFT OUTER JOIN (SELECT Tutor.Id, SUM(Comment.Rating) Rating
  12:                        FROM [User] Tutor, Comment
  13:                        WHERE Comment.TutorId = Tutor.Id AND Tutor.Type = 2
  14:                        GROUP BY Tutor.Id) T2
  15:             ON T.Id = T2.Id
  16:       LEFT OUTER JOIN TutorSubjects
  17:             ON TutorSubjects.TutorId = T.Id
  18:  WHERE T.Type = 2
  19:  ORDER BY T2.Rating DESC

Now, the application is based on ASP.NET MVC, and it uses LINQ-to-SQL orm. First of all, we should somehow execute this query using LINQ, we could do it in several ways. Either write a ninja-query joining multiple entities or as I did, create a view in the database that executes this query and import the view to linq2sql, so that we could use it as an entity. Our new view (and the new object ) is called TutorRatingWithSubjects.

 

In the code, when I query it, I want to have a structure that looks somehow like this:

aggregation1

So that when I'll pass the results to the view I could display them without any extra effort. I could accomplish it with the C# feature - anonymous types, but the problem in here is with passing it to the view.

 

   1:  var tutors = from tutor in db.TutorRatingWithSubjects
   2:               select new
   3:                          {
   4:                              tutor.Id,
   5:                              tutor.FirstName,
   6:                              tutor.LastName,
   7:                              tutor.Rating,
   8:                              tutor.AboutMe,
   9:                              Subjects = (from ts in db.TutorRatingWithSubjects
  10:                                          where ts.Id == tutor.Id
  11:                                          select new
  12:                                                     {
  13:                                                         ts.SubjectId,
  14:                                                         ts.SubjectName
  15:                                                     }
  16:                                         )
  17:                          };

When I'm passing it, the whole anonymous type get's up-casted to an object when it's stored in the ViewData. Here, the problem is that you are unable (directly) to reproduce the original anonymous type from the ViewData.

There are several ways to read data from a boxed anonymous type:

Using reflection

In the view you cast it to IQueryable and iterate through it:

    <ul>
    <%
        IQueryable tutors = (IQueryable) ViewData["Tutors"];
        foreach(object v in tutors) {
      %>
       <li> <%= v.GetType().InvokeMember("FirstName", System.Reflection.BindingFlags.GetProperty, null, v, null) %></li>
  <% } %>
    </ul>
Now, while this method works, I highly discourage it's usage, as using reflection is just cheating. It has weak performance, the code is less readable, etc...
There is a more elegant way to read the results of the linq query in our view.
Using strongly-typed list
We create a nested class that has the same properties as our anonymous type:
internal class TutorsWithSubjectsResult
{
    public Guid TutorId { get; set; }
    public String FirstName { get; set; }
    public String LastName { get; set; }
    public int? Rating { get; set; }
    public string AboutMe { get; set; }

    internal class Subject
    {
        public Guid? SubjectId { get; set; }
        public String SubjectName { get; set; }
    }

    public List<Subject> Subjects { get; set; }
}

 

Now we need to select the data with our linq into the TutorsWithSubjectsResult:

List<TutorsWithSubjectsResult> tutors = (from tutor in db.TutorRatingWithSubjects
                                         select new TutorsWithSubjectsResult
                                                    {
                                                        TutorId = tutor.Id,
                                                        FirstName = tutor.FirstName,
                                                        LastName = tutor.LastName,
                                                        Rating = tutor.Rating,
                                                        AboutMe = tutor.AboutMe,
                                                        Subjects = (from ts in db.TutorRatingWithSubjects
                                                                    where ts.Id == tutor.Id
                                                                    select
                                                                        new TutorsWithSubjectsResult.Subject
                                                                            {
                                                                                SubjectId = ts.SubjectId,
                                                                                SubjectName = ts.SubjectName
                                                                            }
                                                                   ).ToList()
                                                    }).ToList();
Voila! we have a strongly-typed list, that could be easily unboxed in the view, or if you create a strongly typed-view you would have a type that could be the type of the ViewData.
©2008 Karim Agha. All rights reserved.