I'm using an ObjectSet's methods to do various queries in a table, namely, selecting a few records from it as such:
var results = Repository.Find(c => c.Enabled == 1).ToList();
Here is the Find method of my repository:
public IEnumerable<T> Find(Func<T, bool> predicate)
{
try
{
return _objectSet.Where<T>(predicate);
}
catch
{
throw;
}
}
Now, if there is about 1,000,000 records in the targeted table, I can see the process's memory usage grow quite a lot even if the Find call I'm doing should return a few records at most.
It seems all the records are pulled client side, then filtered. This is obviously not what I want LINQ to do.
Do you see anything obviously wrong with what I'm doing ?
Thanks in advance.
I think you should use Expression<Func<T, bool>> instead of a plain Func<T, bool>:
public IEnumerable<T> Find(Expression<Func<T, bool>> predicate) {
// your code
}
Where is overloaded (see ObjectSet Class), and the Func<T>-overload is defined by IEnumerable<T>, whereas Expression<TDelegate> is used by IQueryable<T>. Because predicate is a Func<T> the compiler invokes the extension method defined for IEnumerable<T>, which in turn fetches all the records and does LINQ to objects.
Related
I am trying to make an extension method more generic to avoid redundancy (Here is an example of some real code, the code below is just to demonstrate the issue - I had the idea to make the method available for IQueryable<T> as well).
The following works fine:
public static class Extensions
{
public static IEnumerable<T> MySelect1<T, V>(this IEnumerable<T> query, Func<T, V> f)
{
// do something, then return IEnumerable<T>
var result=query.AsEnumerable<T>();
return result;
}
public static IQueryable<T> MySelect1<T, V>(this IQueryable<T> query, Func<T, V> f)
{
// do something, then return IQueryable<T>
var result = query.AsQueryable<T>();
return result;
}
}
I can use it in LinqPad like (when connected with the Northwind sample database):
var myQuery=(from x in Customers select x);
myQuery.AsEnumerable().MySelect1(d => d.CustomerID).Dump();
myQuery.AsQueryable().MySelect1(d => d.CustomerID).Dump();
Now I wanted to get rid of the duplicate implementation of MySelect1, so I refactored it as:
public static class Extensions
{
public static E MySelect2<E, T, V>(this E query, Func<T, V> f)
where E : System.Linq.IQueryable<T>, System.Collections.Generic.IEnumerable<T>
{
return (E)query.Select(f);
}
}
This compiles too, but I cannot use MySelect2 the same way as I did above, consider the following:
// CS0411 The type arguments for method 'Extensions.MySelect2<E, T, V>(E, Func<T, V>)'
// cannot be inferred from the usage. Try specifying the type arguments explicitly.
myQuery.AsEnumerable().MySelect2(d => d.CustomerID).Dump();
myQuery.AsQueryable().MySelect2(d => d.CustomerID).Dump();
Ok, doing what the error asks for works for this code line:
myQuery.AsQueryable()
.MySelect2<IQueryable<Customers>, Customers, String>(d => d.CustomerID).Dump();
but not for that one:
myQuery.AsEnumerable<Customers>()
.MySelect2<IEnumerable<Customers>, Customers, String>(d => d.CustomerID).Dump();
Here, I am getting
CS0311 The type 'System.Collections.Generic.IEnumerable<LINQPad.User.Customers>' cannot be used as type parameter 'E' in the generic type or method 'Extensions.MySelect2<E, T, V>(E, Func<T, V>)'. There is no implicit reference conversion from 'System.Collections.Generic.IEnumerable<LINQPad.User.Customers>' to 'System.Linq.IQueryable<LINQPad.User.Customers>'.
Why? And how can it be fixed? Please help.
Why?
For exactly the reason stated in the error message: you're trying to use IEnumerable<Customers> as the type argument for E, but E has this constraint:
where E : System.Linq.IQueryable<T>
And how can it be fixed?
It can't, assuming I understand what you're trying to achieve.
There's a fundamental problem with the "simplification" you're trying to achieve: you don't actually have full duplication in your original MySelect1 methods. The first calls AsEnumerable() and the second calls AsQueryable(). You're trying to replace those with a cast, and that's just not going to work.
There's a further problem, even with your original methods: you're accepting Func<T, V> f as a parameter for your queryable-based method, which means any time you call Select or similar and passing in f, you'll be calling Enumerable.Select instead of Queryable.Select. To really use IQueryable<> properly, you should accept Expression<Func<T, V>> f instead. At that point, you won't need to call AsQueryable anyway.
Your two methods "should" take radically different paths based on whether you're using LINQ to Objects or a different LINQ provider (e.g. LINQ to SQL), and that can't be hidden as a pure implementation detail without significant changes that would probably make it less useful than you want anyway.
I have this method in my repository exposing EF6 DbContext.
public IList<T> GetEntities<T>(Func<T, bool> predicate) where T : class
{
return db.Set<T>().Where(predicate).ToList<T>();
}
When I watch this method execute in SQL Profiler, the predicate is executed in memory. The SQL statement contains no where clause.
Any ideas?
.Where accepts one of two things, either Func<T, bool> or Expression<Func<T, bool>>. If you pass in Expression<Func<T, bool>>, then your EF query should work properly.
public IList<T> GetEntities<T>(Expression<Func<T, bool>> predicate) where T : class
You'd call it the same way:
GetEntities(x => x.Id == 34)
When you pass in Func<T, bool>, the IEnumerable<T> implementation executes, which uses Linq-to-Objects rather than Linq-to-Entities.
Your predicate should be an Expression so that Entity Framework can actually use it to generate SQL instead of just executing it. If you pass in a Func you are actually calling the Enumerable.Where method instead of Queryable.Where:
public IList<T> GetEntities<T>(Expression<Func<T, bool>> predicate) where T : class
{
return db.Set<T>().Where(predicate).ToList<T>();
}
I have a RepositoryBase class where I define basic crud methods for my Entity Framework Context. I have these two overloads of the All() method:
public virtual IQueryable<T> All<TKey>(Expression<Func<T, bool>> predicate)
{
return All().Where(predicate);
}
public virtual PagedResult<T> All<TKey>(int startRowIndex, int maximumRows,
Expression<Func<T, TKey>> orderingKey, Expression<Func<T, bool>> predicate,
bool sortDescending = false)
{
var subset = All().Where(predicate);
IEnumerable<T> result = sortDescending
? subset.OrderByDescending(orderingKey).Skip(startRowIndex).Take(maximumRows)
: subset.OrderBy(orderingKey).Skip(startRowIndex).Take(maximumRows);
//More code ommited
}
The first method always needs me to explicitly specify the entity type, but the second doesn't. Why is this?
Example, this doesn't compile:
return All(s => s.LoanApplicationId == loanApplicationId)
And instead I must call it like this:
return All<LoanApplication>(s => s.LoanApplicationId == loanApplicationId)
But this DOES compile:
return All(0,10, s => s.Name, s => s.LoanApplicationId == loanApplicationId, false)
TKey is in the parameter list of the second (via Expression<Func<T, TKey>> orderingKey) and not the first. That supplies enough for the second to successfully infer the type when you use it with your supplied arguments (s => s.Name). You don't give yourself that luxury in the first version, so the compiler forces you to fill in the details by supplying the type parameter explicitly.
And from the looks of it, you don't need TKey in the first anyway, so possibly get rid of it (unless there is more code visible than that relatively simple implementation). And I don't think it means what your sample invocation thinks it means. TKey in the second is likely string (whatever the type of s.Name is), for example.
I'm using NHibernate in a repository pattern with DI to keep things flexible. I don't want the associated interfaces to expose anything vendor specific like ICriterion. But, I want my query class to accept a delegate that I can put into the .Where clause.
I need something like:
public IEnumerable<MyClass> Execute(Func<MyClass, bool> selector)
{
return session
.QueryOver<MyClass>()
.Where(selector)
.....
}
Is there a way to achieve this or am I going about it in the wrong way?
You are not going the wrong way. I think your code works ; your problem being that you retrieve all your table in memory where filtering is done.
I guess this should enable filtering at DB level for criteria which can be translated to SQL :
public IEnumerable<MyClass> Execute(Expression<Func<MyClass, bool>> selector)
{
return session.Query<MyClass>().Where(selector).ToList();
}
and I bet this should work too :
public IEnumerable<MyClass> Execute(Expression<Func<MyClass, bool>> selector)
{
return session.QueryOver<MyClass>().Where(selector).ToList();
}
Hope this will help
I have two very similar methods:
public IQueryable<User> Find(Func<User, bool> exp)
{
return db.Users.Where(exp);
}
public IQueryable<User> All()
{
return db.Users.Where(x => !x.deleted);
}
The top one, will not compile, saying it returns IEnumerable rather than IQueryable.
Why is this?
Also, I am aware I can add "AsQueryable()" on the end and it will work. What difference does that make though? Any performance hits? I understand that IQueryable has deferred execution and such, will I still get this benefit?
Enumerable.Where takes Func<T, bool>.
Queryable.Where takes Expression<Func<T, bool>>.
You're calling Where with a Func<T, bool>, therefore only the Enumerable.Where call is applicable, and that returns IEnumerable<T>.
Change your method to:
public IQueryable<User> Find(Expression<Func<User, bool>> exp)
{
return db.Users.Where(exp);
}
and it should be okay. Basically you want to pass in an expression tree instead of a delegate, so that the expression can be converted to SQL.