Chris
Chris

Reputation: 75

A table join in MVC application is being very slow

I'm at a bit of a loss as to why one of my ASP views in my MVC application is running so slow.

I select some data using linq in the controller. This runs quickly:

public ActionResult Progress(int ID)
        {


            var reviewitems = from ri in db.ReviewItems
                         where ri.Enrolment.Course.LearningArea.LearningAreaID == ID && ri.Review.ReviewSeries.StartDate < DateTime.Now && ri.Review.ReviewSeries.EndDate > DateTime.Now && ri.Progress < 2
                         select ri;


            return View("Progress", reviewitems);


        }

I then do a foreach loop in the ASP view, going through each row of the data of type 'reviewitem' which has been passed to the view. Again this is fast:

<%foreach (var ri in Model)
  {  %>
<tr>
<td><%= ri.Progress %></td>
</tr>
<%} %>   

I need some more information to display, so I need to join to the 'Review' table (which again gives fast results) and then to the 'Student' table. This is where the problem is and it starts taking upwards of 30 seconds:

 <%foreach (var ri in Model)
  {  %>
<tr>
<td><%= ri.Review.Student.Surname %></td>
</tr>
<%} %>   

Each review item links through to one unique student so I don't understand why it is taking so long. Does anyone have any ideas where I should start looking to see why it is so slow? Presumably it is something to do with the 'Student' table (which is actually a SQL Server view) but I can select all rows from it in under a second using SQL?

Upvotes: 1

Views: 734

Answers (4)

Chris
Chris

Reputation: 75

After the above answers helpfully pointed out that this was an n+1 problem I did a bit of Googling. The include method did not work for me (my var type was IQueryable not ObjectQuery) but I did find a solution here: http://l2sprof.com/Learn/Alerts/SelectNPlusOne

By adding this code just before my linq query, the page loads very quickly:

var loadoptions = new DataLoadOptions();
        loadoptions.LoadWith<ReviewItem>(ri => ri.Review);
        loadoptions.LoadWith<Review>(r => r.Student);
        db.LoadOptions = loadoptions;

Upvotes: 0

paramjeet singh
paramjeet singh

Reputation: 165

you can resolved this problem only by creating a store procedure in a sql server (which returns select statement with all columnsof your result) and then update your Entity framework with new complex type and associate this newly complex type with return type of your function import in selected store procedure

Result will many times faster than as beforee in linq Join

this will more helpful..i think new procedure => update EF => new ComplexType(same name and type of select statemnet columns)=>Add function import(right click on procedure)=> link new complex type(in returns type)

Upvotes: -1

Schiavini
Schiavini

Reputation: 2939

When you are requesting student data in the foreach loop, you are making loads of queries to the database: One for each time the loop is repeated (search for N+1 problem if interested).

Changing the query with the include method and making it a list (to avoid the delayed execution) should solve your problem:

        var reviewitems = (
            from ri in db.ReviewItems
            where ri.Enrolment.Course.LearningArea.LearningAreaID == ID
                && ri.Review.ReviewSeries.StartDate < DateTime.Now
                && ri.Review.ReviewSeries.EndDate > DateTime.Now
                && ri.Progress < 2
            select ri
            ).Include("Review.Student").ToList();

Upvotes: 2

Dan
Dan

Reputation: 3258

When you write a LINQ query the query is not actually executed until you need the data (see delayed execution). Your first db call is not made until you foreach over ri.Progress, which is a single call.

When you then ask for ri.Review.Student you are asking for additional data. So you make a call to the database. Because you are in a foreach loop you are making a single call to the database for each item in that loop.

This is a "Linq n+1" problem. To solve it, you should get all the data in one query. You can either populate a model class and strongly type the view to that, or I think you can use the Linq .Include method to include the Student data in the selection

You can take a look at what calls are being made by firing up SQL server profiler and performing a trace

Upvotes: 6

Related Questions