4

I developed a Web application using .NET framework 4.5.1, MVC 5 and EF 6. The data are stored into a MySQL database. The problem is that the performances are really bad and I can't find the reason. Some pages take several seconds (up to 10 seconds) to load completely!

I have tried to implement some suggestions found on the Web, but they gave little improvement.

As instance, the following Controller gets the list of users from the database and passes it to the View.

public class UserController : Controller
{
    // GET: /User/
    public async Task<ActionResult> Index()
    {
        using (AuthorDbContext db = new AuthorDbContext())
        {
            return View(await db.Users.OrderBy(u => u.Surname)
                                        .ThenBy(u => u.Name)
                                        .Include(u => u.Company)
                                        .Include(u => u.Department)
                                    .ToListAsync());
        }
    }

    // [...]
}

The View prints the users' details into an HTML table (which uses the DataTable plugin of jQuery).

@model IEnumerable<Author.Models.User>
@using Author.Helpers

<div class="geco-button-container">
    @ButtonHelper.CreateButton(Url.RouteUrl("New User"), "Add a new user")
</div>

<table id="longTable" class="longTable">
    <thead>
        <tr>
            <th></th>
            <th>
                @Html.DisplayNameFor(model => model.Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Surname)
            </th>
[...]
        </tr>
    </thead>
    <tbody>
    @foreach (var item in Model) {
        <tr@(item.QuittedOn != null && item.QuittedOn.Value < DateTime.Now ? " class=user_dismissed" : "")>
            <td class="tableButton">
                @ButtonHelper.ViewButton(Url.RouteUrl("View User", new RouteValueDictionary {{ "id", item.id }}), "", "Details")
                @if (item.canBeEdited)
                {
                    @ButtonHelper.EditButton(Url.RouteUrl("Edit User", new RouteValueDictionary {{ "id", item.id }}), "", "Edit")
                    @ButtonHelper.DeleteButton(Url.RouteUrl("Delete User", new RouteValueDictionary {{ "id", item.id }}), "", "Delete")
                }
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Surname)
            </td>
[...]
            <td>
                @if (item.Department != null)
                {
                    @Html.RouteLink(item.Department.Code.ToString(), "View Department", new RouteValueDictionary { { "id", item.DepartmentId } }, new Dictionary<string,object> { { "title", item.Department.Description.ToString() } })
                }
            </td>
        </tr>
    }
    </tbody>
</table>
@section Scripts {
    @Scripts.Render("~/Scripts/DataTables-1.9.4/media/js/jquery.dataTables.min.js")
    @Scripts.Render("~/Scripts/longTable.js")
}

When I visit this page, it takes from 5 to 10 seconds to finish loading the list of 698 users.

I've installed Glimpse, which says that the problem seems to be the rendering of the resulting page.

Glimpse - HTTP tab

Glimpse - Execution tab

Glimpse - Timeline

As you can see, I implemented some tips that should improve the performance, like:

  • using RouteLink and RouteUrl instead of ActionLink and ActionUrl
  • using async calls
  • disabling all view engines except RazorViewEngine (in Global.asax)

I am running the app with the debug mode on my local machine. Someone suggests setting the option debug="false" into web.config, but I have also published it on a Web server where it runs with that option and the problem still remains.

Many people complain about the slowness of the Entity Framework, but they usually talk about hundreds of milliseconds, while in my case it takes several seconds - which is really unacceptable in a Web scenario!

What is the problem? What am I doing wrong?


UPDATE

I have installed dotTRACE and this is the result on the page showing the users list.

dotTRACE on a page

As you can see, the bottleneck lies into the Microsoft framework.

What is the MappingDataUtilities class? What is its goal? What about its functions referring to stuff like Json, Css, TagName, Selectors, ...?

My code never calls these stuff explicitly. How can I improve their bad performance?

tereško
  • 58,060
  • 25
  • 98
  • 150
winterlude
  • 131
  • 6
  • possible duplicate of [Entity Framework + MySQL - Why is the performance so terrible?](http://stackoverflow.com/questions/1281330/entity-framework-mysql-why-is-the-performance-so-terrible) – JMK May 28 '14 at 12:35
  • EntityFramework 6.1 was released a few months ago that fixed a number of performance regressions introduced in EF6. Might be worth updating to the latest version and re-testing before further investigation. – Martin Costello May 28 '14 at 12:40
  • You can trace SQL queries that are generated by the Entity Framework, probably you EF just generates too many of them to render all required data. – takemyoxygen May 28 '14 at 12:49
  • I am already using version 6.1.0 of the Entity Framework. @JMK, the question you posted is not relevant for me, because my query is executed in few milliseconds, so the problem should not be due to MySQL. – winterlude May 28 '14 at 12:49
  • The framework generates only one query, which is executed in 0.8 ms and the SQL connection is open for just 111.38 ms. So, the problem is not there. – winterlude May 28 '14 at 12:52
  • 2
    Get rid of async stuff, just to see if that has anything to do with performance + get a decent profiler that can tell you where the slowness goes. I suggest dotTracer + find out if this issue has anything to do with the Web? WHat if you call Index() from console application.. – Erti-Chris Eelmaa May 28 '14 at 13:05
  • The async calls should [improve performance](http://www.asp.net/aspnet/overview/developing-apps-with-windows-azure/building-real-world-cloud-apps-with-windows-azure/web-development-best-practices#async) - indeed I had the same problems before. I will try dotTracer. – winterlude May 28 '14 at 13:39
  • 1
    According to symptoms described, each item requires additional DB querying, like Lazy loading of additional data. While you have some `Include`s in query, there might be additional logic which queries DB per each item during rendering. – Lanorkin May 28 '14 at 13:52
  • 1
    If you comment out the script that initializes the DataTable (presumably longtable.js), do you end up with the performance you are looking for, albeit with a long HTML table instead of a formatted grid? The majority of execution time appears to be client-side. The sub-second (query + view render) time on the server doesn't raise any red flags to me. In my experience, you will need to use "Server-side processing" option with the DataTables plug-in to get good performance as your row count grows. – Brian R. Mullin May 28 '14 at 14:04
  • 1
    The code generates only one query because I use `Include` and then the `ToList()` instruction triggers the query execution, so that the View does not need any additional queries. @Lanorkin, I tried to set `this.Configuration.LazyLoadingEnabled = false;` into the DBContext in order to be sure, but nothing changes. – winterlude May 28 '14 at 14:12
  • @BrianR.Mullin, I have commented the DataTables javascript but same performance. As always, the page starts to load quite immediately, then I see the browser scrollbar marker becoming smaller and smaller (because it is loading additional table rows) - it is here that it takes several seconds. – winterlude May 28 '14 at 14:23
  • Oh, it is 6 seconds on client, not on server side. Just save resulted html, and try opening that (static) html file, thus getting rid of server side - how much will it take? It looks like it has nothing with EF or MySQL or Razor - just client browser performance on rendering of BIG table? – Lanorkin May 28 '14 at 14:55
  • 1
    @Lanorkin, good suggestion. However, loading the static HTML page previously saved on my desktop takes 2 seconds - which is a few less than 6 seconds. Actually, I'm using a lot of stuff: FontAwesome, Bootstrap, jQuery, DataTables, jQuery UI... But I think the real problem is somewhere else. – winterlude May 28 '14 at 15:12
  • Perform the orderby in the webserver instead of the database. – Rodrigo Aug 01 '14 at 19:39

1 Answers1

1

I would strongly recommend decoupling your ViewModel from your DataModel. When those two are the same thing it means exposing everything to your ViewModel even though you only want a handful of fields. I recommend creating a new ViewModel that only contains the field you're interested, and then update your EF query to only select the fields you need instead of doing SELECT * on many tables. I believe that will help a great deal with performance.

jhilden
  • 12,207
  • 5
  • 53
  • 76