Question

I'm in a weird situation - I've been asked to optimize a search feature that depends on lazy loading. I've realized that the main issue with speed is due to the wait time between paging requests (approximately 1.5 seconds) and in code, I realize that each page request that the controller handles causes the model to do a full DB query (instead of a partial query only for the results of that page).

We're using a custom ORM, so I'm not sure what it handles in terms of paging (and I think that's why it was implemented this way). We're storing the DB results to an IEnumerable, which we then filter with LINQ to retrieve the correct page results.

What I'd like to do is cache this request in memory - since we're putting the full result into an IEnumerable, it'd be handy to just keep that around somewhere (even if I have to pass it into my model).

Someone suggested using a static variable in my controller or model, but I think that this will fail hard with multiple requests. I'm thinking that there might be a way to store this in a server-side Session variable temporarily, and delete it when all of the page requests have completed. Manual caching, if you will.

I know this isn't the best way to do this, but it may be the only sane option I have in terms of optimization (unless we move to a different ORM). Does anyone have any suggestions, ideas, or criticisms? How would I implement something like this?

Thank you.

Controller code:

    [HttpGet, JsonErrorCatcher, NoCache]
    public ActionResult Accounts(int page = 0)
    {
        using (var context = PersistenceContext.Create())
        {
            var model = new ContactsModel(context);
            return new JsonNetResult(model.GetAccountsWebView(CurrentUser, page));
        }
    }

Model method:

    public ContactsWebViewModel GetAccountsWebView(User user, int page)
    {
        // This is what I'd like to avoid doing every time, by persisting this value.
        // I can pass it into this method if necessary.
        IEnumerable<Account> accounts = Context.Get<Account>()
            .Include(x => x.AssignedGroups)
            .Include(x => x.AssignedUsers)
            .Include(x => x.Contacts)
            .Where(x => x.IsActive)
            .OrderBy(x => x.Name)
            .ToArray();
        if (!user.HasPermission("Administrator.Contacts:View")) accounts = accounts.Where(x => x.IsReadableBy(user));
        int totalPages = Convert.ToInt32(Math.Ceiling(Convert.ToDouble(accounts.Count()) / 25.0));
        accounts = accounts.Skip(page * 25).Take(25);
        var model = GetWebView(user, null, accounts, page == 0);
        model.TotalPages = totalPages;
        return model;
    }
Was it helpful?

Solution 2

If the data you wish to cache is read-only and the same for all users, then using a static variable is a non issue.

If that isn't the case, then you should cache the IEnumerable as a CacheItem in a System.Runtime.Caching.MemoryCache object. You can cache multiple versions to fit the various scenarios that may be needed.

More about MemoryCache on MSDN.

OTHER TIPS

The easiest solution is probably to use the built-in asp.net cache. You don't say what version of the framework you're using, but something like this should work.

if (HttpContext.Cache["Key1"] == null)
  HttpContext.Cache.Add("Key1", "Value 1", null, DateTime.Now.AddSeconds(60), 
      Cache.NoSlidingExpiration, CacheItemPriority.High, null);

You can adjust the cache time however you like. Be aware though, if the results are large, and you have multiple users, you can easily run out of memory or cause huge paging delays..

You can make the search criteria be the key, so long as the same results are always returned with the same search criteria.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top