views:

98

answers:

1

I have been using these common EntityObjectFilters as a "pipes and filters" way to query from a collection a particular item with an ID:

public static class EntityObjectFilters
{
    public static T WithID<T>(this IQueryable<T> qry,
       int ID) where T : IEntityObject
    {
        return qry.SingleOrDefault<T>(item => item.ID == ID);
    }

    public static T WithID<T>(this IList<T> list,
        int ID) where T : IEntityObject
    {
        return list.SingleOrDefault<T>(item => item.ID == ID);
    }
}

..but i wondered to myself: "can i make this simpler by just creating an extension for all IEnumerable<T> types"?
So i came up with this:

public static class EntityObjectFilters
{
    public static T WithID<T>(this IEnumerable<T> qry,
       int ID) where T : IEntityObject
    {
        return qry.SingleOrDefault<T>(item => item.ID == ID);
    }
}

Now while this appears to yield the same result, i want to know that when applied to IQueryable<T>s will the expression tree be passed to LinqToSql for evaluating as SQL code or will my qry be evaluated in it's entirety first, then iterated with Funcs?

I'm suspecting that (as per Richard's answer) the latter will be true which is obviously what i don't want. I want the same result, but the added benefit of the delayed SQL execution for IQueryable<T>s. Can someone confirm for me what will actually happen and provide simple explanation as to how it would work?

EDIT:

Solution i went with

    public static T WithID<T>(this IEnumerable<T> qry,
        int ID) where T : DomainBase
    {
        if (qry is IQueryable<T>)
            return ((IQueryable<T>)qry).SingleOrDefault<T>(item => item.ID == ID);
        else
            return qry.SingleOrDefault<T>(item => item.ID == ID);
    }
+2  A: 

No, when qry is typed as IEnumerable<T> it will call Enumerable.SingleOrDefault rather than Queryable.SingleOrDefault. The lambda expression will be converted into a delegate instead of an expression tree, and it won't use SQL.

Note that SingleOrDefault doesn't use deferred execution in the first place - it's always immediate - but the difference is where the querying is performed. You almost certainly want the database to do it. If you have a look at your logs with the simplified version, you'll see that all the results are being fetched - whereas with the IQueryable<T> overload the SQL will contain the relevant filtering.

Jon Skeet
Are you saying that in the first function in the first example the `SingleOrDefault` won't even use an SQL `WHERE`?
cottsak
Yes, that is exactly what Jon meens. Doing this will possibly retrieve all data rows from a table and filter client side. This would be very bad for performance.
Steven
Hold on: he says `SingleOrDefault` does not use deferred execution but it can query in SQL if it's `Queryable.SingleOrDefault`. This would seem to suggest that the first function in the first example the would use an SQL `WHERE`. Agreed?
cottsak
@cottsak: The first function should use a WHERE in the SQL, yes. Using your second snippet, over `IEnumerable<T>` *won't* use a WHERE.
Jon Skeet
@Jon: refer to the edit: Is this an acceptable compromise meeting the requirements in a single extension method?
cottsak
@cottsak: Yes - it's a bit ugly, but it makes it somewhat simpler for the caller. One issue is that you then *can't* easily force it to be done in-process; usually you'd consider using `AsEnumerable` to force the rest of a query to be performed in-process, but that won't work in this case.
Jon Skeet
Yeh it's not that attractive with the casting garb. I'm not sure the option of forcing the use of delegates is any advantage in the case of selecting a single item. I can understand the case for a range - good example is when the expression logic can't be converted to SQL. Why might the 'in-process' option be an advantage to me in this case?
cottsak
@cottsak: I don't think it would, to be honest. It was more of a general concern when you start doing this sort of thing - it's something to watch out for if you do anything similar in the future.
Jon Skeet
Good advice. Thanks
cottsak
@Jon I think i just found a bug. By extending `IEnumerable<T>` like i have in my solution example, am i not forcing `qry` to always be cast to `IEnumerable<T>` within the method? And by doing so (casting to a less derived type), never getting to the first part of the `if` condition?
cottsak
@cottsak: No, because "is" deals with the *execution time* type of the object, not the *compile time* type.
Jon Skeet
@Jon: What is your view of pre-compilation (like this: http://lanitdev.wordpress.com/2010/01/06/unexpected-benefits-of-precompilation-of-linq/) in this case? I can't seem to tell if there is a benefit.
cottsak
@cottsak: I don't have enough experience to say, to be honest.
Jon Skeet