Using an IQueryable in another IQueryable - c#

I've an extension method, which returns an IQueryable, to get company products, I just want to use it in a IQueryable as a subquery,
public static class DBEntitiesCompanyExtensions {
public static IQueryable<Product> GetCompanyProducts(this DBEntities db, int companyId)
{
return db.Products.Where(m => m.CompanyId == companyId);
}
}
And this is how I call it,
using(var db = new DBEntities()) {
var query = db.Companies.Select(m => new {
CompanyName = m.Name,
NumberOfProducts = db.GetCompanyProducts(m.CompanyId).Count()
});
}
I expected it to works beacuse my extension methods returns an IQueryable, so it could be used in a IQueryable, am I wrong?
This is what I get, Is that possible to make it work?
System.NotSupportedException: LINQ to Entities does not recognize the
method 'System.Linq.IQueryable`1[WebProject.Models.Company]
GetCompanyProducts(WebProject.Models.DBEntities, Int32)'
method, and this method cannot be translated into a store expression.

Problem is not IQueryable inside IQueryable, because you can include subqueries just not the way you did.
In your example whole Select is represented as expression tree. In that expression tree there is something like :
CALL method DBEntitiesCompanyExtensions.GetCompanyProducts
Now EF should somehow traslate this into SQL SELECT statement. It cannot do that, because it cannot "look inside" GetCompanyProducts method and see what is going on there. Nor can it execute this method and do anything with it's result. The fact it returns IQueryable does not help and is not related.

Instead of using IQueryable you should create an expression predicate and use inside the IQueryable object that is connected to the data source
the object looks like that:
Expression<Func<Person, bool>> predicate = x => x.Name == "Adi";
var data = await queryable.Where(predicate).ToListAsync();

Related

Using a func parameter returns IEnumerable instead of IQueryable

I've minimised my issue to this:
I have a DbContext with a DbSet<PageBlock> and I get an IQueryable with a simple LINQ "Where" like so:
IQueryable<PageBlock> result = _context.PageBlocks.Where(x => x.PageId == 2);
But if I wrap this line in a method and pass a Func<PageBlock, bool> to the method for the where clause like this:
private IQueryable<PageBlock> GetQueryable(Func<PageBlock, bool> belongsToPage)
{
IQueryable<PageBlock> result = _context.PageBlocks.Where(belongsToPage);
...
}
and call it like this:
var result = GetQueryable(x => x.PageId == 2)
then the .Where now returns an IEnumerable and I get a compile error!? The IDE says I can put an explicit cast on like this:
IQueryable<PageBlock> result =
IQueryable<PageBlock>)_context.PageBlocks.Where(belongsToPage);
but that gives a runtime error
Unable to cast object of type 'WhereEnumerableIterator``1[DAL.Models.PageBlock]' to type 'System.Linq.IQueryable``1[DAL.Models.PageBlock]'.
Why does using the same filter indirectly via the Func param cause my query to return IEnumerable instead of IQueryable ?!
If you want to stay in the IQueryable world then everything in your query has to be executed via EntityFramework in your database. However what the C# compiler sees is that you want to use and call an arbitrary function. The only way it make that work is to enumerate the query so far and call that function on every returned value. It doesn't matter that your specific function could be executed by the database.
If you want to call your method like this var result = GetQueryable(x => x.PageId == 2) you should mimic the signature of Queriable.Where and accept an Expression<Func<TSource,bool>> predicate as your argument.
private IQueryable<PageBlock> GetQueryable(Expression<Func<PageBlock, bool>> belongsToPage)
{
IQueryable<PageBlock> result = _context.PageBlocks.Where(belongsToPage);
...
}
That way the query provider can examin the expression and see if it can be passed to the database

Using Func as parameter for LinqToEntities

I have a Linq-Query to get my EF-Data. My query joins 4 tables and selects the result to a denormalized type. I will need this query very often but with different predicates. The List-ExtensionMethods (e.g. .Where() are working with a Func<T,bool> as a parameter and I wanted to do it the same - but I don´t find a way to access my predicate in my Method.
public DenormalizedType GetData(Func<Thing, bool> predicate)
{
using (var dbContext = new MyDbContext())
{
var myData = (from some in dbContext.Thing
join other in dbContext.OtherThing
on some.OtherId equals other.Id
// => HowToWhere ???
select new DenormalizedType()
{
SomeEntry = some.Entry
SomeOtherId = some.OtherId
OtherValue = other.Value
}).ToList();
}
}
I have 3 questions regarding this issue.
First (obviously): how to invoke my predicate to use a dynamic where-clause?
Second: If my initial idea doesn´t work (because teovankots answer indicates, that my approach isn´t valid for LinqToEntities) is it somehow possible, to make a method of the join only?
Third: What is the best performing approach to return my results to another software-component?
EF query provider needs to translate the LINQ query expression tree to SQL, which is not possible when you pass a Func<...> (and more generally, invocation expression like delegate, unknown method etc.).
Shortly, what you ask is not possible with Func<...> type parameters.
The first thing to consider when working with Queryable methods is to use Expression<Func<...>> whenever you would use Func<..> in Enumerable methods. So change your method argument like this:
public DenormalizedType GetData(Expression<Func<Thing, bool>> predicate)
Unfortunately using the supplied expression inside LINQ query syntax is not supported out of the box. To do that, you need some expression tree processing library.
The problem and the possible solution is explained in the LINQKit package page. The solution provided by the package is through AsExpandable and Invoke custom extension methods.
Install the nuget package, add
using LinqKit;
to the source code file, and now you can use something like this to achieve the goal:
public DenormalizedType GetData(Expression<Func<Thing, bool>> predicate)
{
using (var dbContext = new MyDbContext())
{
var myData = (from some in dbContext.Thing.AsExpandable() // <=
join other in dbContext.OtherThing
on some.OtherId equals other.Id
where predicate.Invoke(some) // <=
select new DenormalizedType()
{
SomeEntry = some.Entry
SomeOtherId = some.OtherId
OtherValue = other.Value
}).ToList();
}
}
At least I´ve found a way to solve my issue - but I´d highly appreciate hints, and or ways to make it better, because I can´t imagine that this is the holy grail or even close to...
However, the first step is my join, which I return as IQueryable. Important: No using here, because otherwise the dbContext will be disposed, which is not so nice, while working with the IQueryable:
private static MyDbContext _dbContext;
private static IQueryable<DenormalizedType> SelectType()
{
_dbContext = new MyDbContext();
var myData = (from some in dbContext.Thing
join other in dbContext.OtherThing
on some.OtherId equals other.Id
select new DenormalizedType()
{
SomeEntry = some.Entry
SomeOtherId = some.OtherId
OtherValue = other.Value
};
return myData;
}
I´ve learned a lot today. For example: IEnumerable and IQueryable have both an extension method .Where(). But only IEnumerable.Where() has a Func<T,bool> as a parameter. IQueryable takes a Expression<Func<T,bool>> for its Where(). If I want my query to be executed, with all of my conditions I need to work with the IQueryable-type, as long as all my wheres aren´t executed. So I needed to take a closer look to the Expression-Type. I didn´t understand what all this actually does, but it works ;)
The first thing I had to do, was writing my Where-Methods.. That was pretty easy after I´ve read this one: Entity Framework Filter "Expression<Func<T, bool>>". The Method looks like this:
public static IQueryable<DenormalizedType> SelectWhereCriteria(IQueryable<DenormalizedType> data, Expression<Func<DenormalizedType, bool>> predicate)
{
return data.Where(predicate);
}
The Expression itself was a little more complicated, because I have a Selection-Enum which should select specified filters. The Expression looks like:
Expression<Func<DenormalizedType, bool>> FilterBySelection(Selection selection)
{
switch(selection)
{
case Selection.Active:
return x => x.IsActive == true;
case Selection.InActive:
return x => x.IsActive == false;
case Selection.SomeOtherSelection:
return x => x.SomeOther == "Criteria"
default:
return x => true;
}
}
This Expression works fine on my IQueryable:
var selectedQuery = DataHandler.SelectWhereCriteria(query, FilterBySelection(selection));
The only thing I needed now, was ordering. I found some very cool stuff from MarcGravell (what a genius btw) Dynamic LINQ OrderBy on IEnumerable<T> where he posted some code as an answer, which you can use, to OrderBy PropertyName. His first piece of code takes an IQueryable, orders it by PropertyName (he provides Extensions for Descending OrderyBy as well) and returns an IOrderedQueryable. The ToList()-Operation is the very last operation I execute.
And one more thing: Don´t forget to Dispose the DbContext:
public static void Dispose()
{
_dbContext.Dispose();
}
You can call it easily. Install this package, add:
public DenormalizedType GetData(Expression<Func<Thing, bool>> predicate)
{
using (var dbContext = new MyDbContext())
{
var myData = (from some in dbContext.Thing
join other in dbContext.OtherThing
on some.OtherId equals other.Id
where predicate.Invoke(some) //check this
select new DenormalizedType()
{
SomeEntry = some.Entry
SomeOtherId = some.OtherId
OtherValue = other.Value
}).ToList();
}
}
You just should know that Func<T1,T2> is a method. With this signature:
T2 predicate(T1 parameter) { /*...*/ }
Your second question depends on how you connect your conponent. But as long as you get DenormalizedType not DbEntity your example looks ok.

How to call a method with no parameters which returns Expression<Func<Model, String>>?

I have a method like this (very simplified version):
public static Expression<Func<MyModel, String>> GetSomeStatus()
{
return myModel => myModel.IsTrue ? "That's true" : "That's false";
}
So, how can I call it in statement like this:
var efRequest = db.Table1.Where(...)
.Select(x => new MyAnotherModel
{
Status = ""; // call GetSomeStatus() here; x is of MyModel type
})
Note: My initial issue was to call some helper method which returns String inside Select method, but of course I've got exception like Linq to entities doesn't recognize this method..., so I've tried to rewrite it (see example above), but now I just don't understand how to call it (I'm relatively new in EF). I know about simple AsEnumerable call before Select which solves my initial issue, but I want to keep this query as IQueryable for later purposes.
It's not possible out of the box. But you can use for instance LINQKit AsExpandable and Invoke extension methods like this:
First you need to store the expression in a variable, otherwise you'll get the famous Linq to entities doesn't recognize this method... exception:
var getSomeStatus = GetSomeStatus();
and then use it inside the query (after calling AsExpandable, so the resulting query expression tree is correctly postprocessed):
var efRequest = db.Table1.AsExpandable()
.Where(...)
.Select(x => new MyAnotherModel
{
Status = getSomeStatus.Invoke(x)
});

Can't Translate Extension Method Into Store Expression

I have an extension method as follows:
public static bool SatisfiesSomeCondition(this Post post, SomeObj someObj)
{
return post.SomeObjId == someObj.SomeObjId;
}
And i'm trying to use it like this:
var query = ctx.Posts.Where(p => p.SatisfiesSomeCondition(someObj)).ToList();
But i get the error:
LINQ to Entities does not recognize the method 'Boolean SatisfiesSomeCondition(xx.xx.xx.Post, xx.xx.xx.SomeObj)' method, and this method cannot be translated into a store expression.
If i change the query to:
var query = ctx.Posts.Where(p => p.SomeObjId == someObj.SomeObjId).ToList();
Which is identical to the method.
It works fine, and executes the expected T-SQL.
Why doesn't my first query work? It's a static method, can't it figure out how to create the expression tree? (e.g a WHERE filter). Surely i don't have to materialize the query first? (which means the records i don't want come back over the wire, and i'm doing paging/ordering here, so that's not an option).
Of course, i can just go with what works (e.g the above), but the method SatisfiesSomeCondition is an existing method used across the domain and i want to re-use that functionality, not duplicate it.
Any ideas?
Change it to:
public static IQueryable<Post> SatisfiesSomeCondition(this IQueryable<Post> query, SomeObj someObj)
{
int id = someObj.SomeObjId;
return query.Where(post => post.SomeObjId == id);
}
and use it like:
var query = ctx.Posts.SatisfiesSomeCondition(someObj)).ToList();
This way it should work. You can combine multiple Where conditions in single query so it should offer you at least basic reusablity.
The LINQ to Entities engine has no way of knowing what your static method does.
LINQ queries can only be translated from expression trees.

LINQ Dynamic Where - Not adding clause

I have the following code:
public OTestTable GetTestCode(Func<TestTable, bool> whereClause)
{
return CoreContext.TestTables.Where(whereClause).Select(TestTableMap.DataToObject).FirstOrDefault();
}
CoreContext is my data context (which is initialized in a base class)
My TestTableMap is as follows:
public class TestTableMap
{
public static readonly Func<TestTable, OTestTable> DataToObject = mapper =>
new OTestTable
{
Code = mapper.mycode
};
}
Then in my business method i have the following:
public OTestTable GetTestCode(string code)
{
return QueryEngine.GetTestCode(id => id.mycode == code);
}
From my main program, i am calling GetTestCode with a string value.
When I watch SQL profiler, I get the following:
SELECT [t0].[mycode]
FROM [dbo].[TestTable] AS [t0]
It does not have the where clause appended to the SQL query. If i add the where clause to the LINQ as var query = from c in DataContext.TestTable where c.mycode == '' select c;
It will add the where clause.
However, when I run my code, it will return the correct record, but it seems like I am pulling back all records from the database and filtering in my code (which should not happen).
Any thoughts with what I am doing wrong?
Thanks
In order to construct SQL statements, LINQ to SQL requires an expression tree. Func<TestTable, bool> does not represent an expression tree, it is a "black box" function pointer. LINQ cannot do anything intelligent with this apart from blindly execute it on an in-memory collection.
You need to do this instead:
public OTestTable GetTestCode(Expression<Func<TestTable, bool>> whereClause) {
return CoreContext.TestTables.Where(whereClause).Select(TestTableMap.DataToObject).FirstOrDefault();
}
This code compiles using the Queryable.Where extension method, which does accept an expression tree, rather than the Enumerable.Where extension method, which only accepts a raw delegate.
Try creating your where clause as:
Expression<Func<T, bool>> whereClause
Where the T parameter is your source type Table<T> source
Also see the PredicateBuilder here: http://www.albahari.com/nutshell/predicatebuilder.aspx
It provides you convenient extension methods to predicate IQueryable<T>. like this:
var predicate = PredicateBuilder.True<Family>();
predicate = predicate.And(o => o.Birthday < new DateTime(1980, 1, 1));
.Or(o => o.Name.Contains("ke"));
var result = Source.Where(predicate).ToList();

Categories

Resources