Reputation: 1080
I'm writing a collection of LINQ 2 SQL extension methods. Right now, I have a bunch of duplicate methods, one working on an IEnumerable collection and the other on an IQueryable collection. For example
public static IQueryable<X> LimitVisible(this IQueryable<X> items) {
return items.Where(x => !x.Hidden && !x.Parent.Hidden);
}
public static IEnumerable<X> LimitVisible(this IEnumerable<X> items) {
return items.Where(x => !x.Hidden && !x.Parent.Hidden);
}
I think I need this duplication because I want to be able to retain the IQueryable behavior in some cases, so that execution is deferred to the database. Is this a correct assumption or would a single IEnumerable method (plus casting the output to IQueryable) work? If I do need both, I don't like the duplication, so I want to combine the two using the generic method
public static T LimitVisible<T>(this T items) where T : IEnumerable<X> {
return (T)items.Where(x => !x.Hidden && !x.Parent.Hidden);
}
This works except that I have other types than X (say Y and Z) that I also want to write LimitVisible functions for. But I can't write
public static T LimitVisible<T>(this T items) where T : IEnumerable<Y> {
return (T)items.Where(y => !y.Hidden && !y.Parent.Hidden);
}
because you can overload based on generics. I could put these methods in different classes, but that doesn't seem like the right solution.
Any suggestions? Maybe I'm making incorrect assumptions and there's no need for an IQueryable specific version in the first place.
Edit: Another Option
Here's another pattern I've used in the past to avoid duplication
private static readonly Expression<Func<X, bool>> F = x => !x.Hidden && !x.Parent.Hidden;
public static IEnumerable<X> LimitVisible(this IEnumerable<X> e) {
return e.Select(W.Compile());
}
public static IQueryable<X> LimitVisible(this IQueryable<X> q) {
return q.Select(W);
}
Are there any dangers with this?
Upvotes: 3
Views: 1014
Reputation: 203811
Really there's no good way around having two methods. The IQueryable
Where
takes an Expression<Func<T,bool>>
as a parameter while the IEnumerable
takes a Func<T,bool>
as a parameter. Thanks to the magic of lambdas the code looks identical for the caller, but the compiler actually does vastly different things with the two different bits of code.
Upvotes: 2
Reputation: 726559
The answer depends on your usage: if you do not mind bringing the data set into memory in order to perform the query, then keeping only the IEnumerable<T>
override will be fine.
However, the inevitable consequence of this decision is that your queries will be forced into memory the moment that you use LimitVisible<T>
. This may or may not be what you want, and might force you into coding patterns that you would rather avoid. For example,
var firstHundred = ctx
.SalesOrders
.LimitVisible() // Happens in memory
.Where(ord => ord.UserId == myUser) // Also happens in memory
.Take(100);
will perform a lot worse with a single IEnumerable<T>
than with an IQueryable<T>
overide availalbe, because all sales orders will be retrieved into memory one-by-one before determining their visibility, rather than checking the condition on the server side. If filtering by user ID could eliminate the majority of sales orders on the server side, the performance difference of this equivalent query could be orders of magnitude better:
var firstHundred = ctx
.SalesOrders
.Where(ord => ord.UserId == myUser) // Happens in RDBMS
.LimitVisible() // Happens in memory
.Take(100);
If you plan to use your code only by yourself and you do not mind being watchful for situations when there is bad performance for no obvious reason, you can keep one overload. If you plan to let others use your library, I strongly suggest keeping both overrides.
EDIT : To speed up your alternative implementation, pre-compile your expression, like this:
private static readonly Expression<Func<X, bool>> F = x => !x.Hidden && !x.Parent.Hidden;
private static readonly Predicate<X> CompiledF = (Predicate<X>)F.Compile();
public static IEnumerable<X> LimitVisible(this IEnumerable<X> e) {
return e.Select(CompiledF);
}
public static IQueryable<X> LimitVisible(this IQueryable<X> q) {
return q.Select(F);
}
Upvotes: 1