0

I've got a wrapper class that's used in an MVC application that is designed to loop through the items in a collection and check that the current user has authorisation to access that item before returning it for display. For the most part it works like a charm. However for one particular object type it runs like an absolute dog and I can't work out why.

The Interface classes are written like this:

    private readonly IEnumerable<T> _ViewData;

    public IEnumerator<T> GetEnumerator()
    {
        foreach (T item in _viewData)
        {
            if (item.UserCanEdit || item.UserCanView)
                yield return item;
        }
    }


    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
         return GetEnumerator();
    }

Now my first question is check my understanding of what's going on here. I'm probably wrong, but my understanding is that when I try and run a foreach loop against a collection of type objects it never needs to use System.Collections.IEnumerable.GetEnumerator() - certainly that code never gets hit?

The problem lies when I try and use this wrapper on log file entries, which are delivered via entity framework in a LogEntryViewDaya class. When the code hits the foreach loop inside GetEnumerator it stop for a full 6-8 seconds, even though there are only 20 items in the enumeration!

Here's the code for LogEntryView

public class LogEntryViewData:BaseViewData
{
    private LogEntry _entry;
    public LogEntryViewData(LogEntry entry, ICustomerSecurityContext securityContext) : base(securityContext)
    {
        _entry = entry;
    }

    public string Application { get { return _entry.Application; } }
    public string CurrentUser { get { return _entry.CurrentUser; } }
    public string CustomerName { get { return _entry.CustomerName; } }
    public DateTime Date { get { return _entry.Date; } }
    public string Exception { get { return _entry.Exception; } }
    public string HostName { get { return _entry.HostName; } }
    public long Id { get { return _entry.Id; } }
    public string Level { get { return _entry.Level; } }
    public string Message { get { return _entry.Message; } }
    public int? ProcessId { get { return _entry.ProcessId; } }
    public int? ServiceId { get { return _entry.ServiceId; } }
    public string ServiceName { get { return _entry.ServiceName; } }
    public int? TaskId { get { return _entry.TaskId; } }
    public int? TaskName { get { return _entry.TaskName; } }
    public string Thread { get { return _entry.Thread; } }
}

As far as I can tell there's no appreciable performance in instantiating these classes - putting a breakpoint in the constructor and F5ing through seems slick as anything.

So why is a collection of these particular objects so slow to iterate through? I have no idea: suggestions gratefully appreciated.

Bob Tway
  • 9,301
  • 17
  • 80
  • 162

1 Answers1

2

You haven't shown us how the class is being populated. My guess is that the time is going on database queries - in particular, depending on what's being fetched, it's just possible that there's one query to get the skeletal entities, and then one query for each of UserCanEdit and UserCanView. That seems unlikely (I'd have expected the entry to have those properties populated on the initial query) but just possible.

Basically, watch how it's interacting with your database.

I suggest you try to write a console app which uses the same class, so you can mess around with it, add timing logs etc more easily than you can from a webapp.

EDIT: Aside from everything else in the comments etc, is there any reason you're not doing this as part of a LINQ query? For example:

var query = db.LogEntries.Where(x => x.UserCanEdit || x.UserCanView);

I appreciate it probably won't be quite as simple as that, as UserCanEdit will depend on the current user etc - but it probably should be done as a database query rather than client-side.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Okay, going to show my ignorance here but I had assumed the class was populated when its constructor was called? That's happening before it ever gets to the foreach loop and seems to be occurring at a satisfactory speed. – Bob Tway Dec 09 '10 at 14:11
  • @Matt: Well you've passed in an `IEnumerable`, but that could be lazily evaluated. In particular, if you're passing in an `IQueryable` then you're effectively passing in the *query* rather than the results of evaluating that query. Aside from anything else, you could change your constructor to call `ToList()` and store that list, which would force the query to be executed. Basically we can't see enough code here to know what's going on. – Jon Skeet Dec 09 '10 at 14:14
  • @Matt: Well maybe... or maybe it's whatever's *calling* the constructor. You're the one who knows what the rest of the system is doing... I go back to my suggestion of writing a short console app which will let you test this in a simple manner. Put logs of logging in, and trace where the database is being hit (and how long that takes). – Jon Skeet Dec 09 '10 at 14:28
  • @Matt: Ah, you've removed/edited the comment I was replying to. Currently we don't know how your "interface class" is being constructed, or what any of the rest of the code is doing. What is the implementation of `_ViewData` here? (If you put a breakpoint in your GetEnumerator method, what does `_ViewData.GetType()` return?) – Jon Skeet Dec 09 '10 at 14:29
  • Sorry, I edited it because I realised I was just further showing my stupidity. The GetType() call returns {Name = "d__3a`1" FullName = "System.Linq.Enumerable+d__3a`1[[ViewData.ApplicationLog.LogEntryViewData, , Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"} System.Type {System.RuntimeType} .. I removed a couple of sensitive namespaces in that ;) – Bob Tway Dec 09 '10 at 14:32
  • @Matt: Okay, so that looks like you're calling `Take(...)` on something higher up... now it depends on what else it's iterating over. If you call `_ViewData.ToList()`, how long does that take, and what does it do to the database? – Jon Skeet Dec 09 '10 at 14:34
  • Aha, yes that's where the hit is. I'll fire up SQL profiler and take a look. – Bob Tway Dec 09 '10 at 14:37
  • Okay at this point you get a tick because you've helped me work toward a solution and I've been behaving like an idiot. I've found out what the problem is now: expanding that Enumerable executes a query that returns 98,000 rows of data ... just not sure how to fix it since it's auto-generated by entity framework :) – Bob Tway Dec 09 '10 at 15:11
  • @Matt: I suspect the problem is that you're calling Take (and possibly other things) on something with a compile-time type of `IEnumerable` instead of `IQueryable`. Anything you call on `IEnumerable` will be performed client-side instead of in SQL. – Jon Skeet Dec 09 '10 at 15:14
  • No, as it turns out the problem is that I'm *not* calling Take(). And in actual fact I can't in order to achieve what I need to do for reasons that are too obscure to explain here. But basically I was misunderstanding how LINQ works - in trying to do "foreach" on what was supposed to only be 20 objects I had neglected to realise that without Take() it was being forced to process the entire collection (of 98,000 objects) in order to get the top 20! D'oh! – Bob Tway Dec 09 '10 at 15:44