Use a saved expression inside another expression - c#

Is it possible to do something like this:
public static Expression<Func<EntityA, bool>> IsAGood =
x => x.A != null &&
x.A.valid;
public static Expression<Func<EntityB, bool>> IsBGood=
x => IsAGood (x.EntityA);
var res = context.EntitiesB
.Where(x => x.count > 0)
.Where(IsBGood)
I know I can compile IsAGood and run it on x.EntityA but it loads the data to memory and I don't want to do this yet.
Is there a way to do this without loading to memory?

Yes, you can do it with some expression trees processing. Maybe there are easier ways to do this, but the one I know looks something like this (you can place this code in static constructor):
Expression<Func<EntityB, EntityA>> exp = b => b.EntityA;
var param = exp.Parameters.First();
var expression = new ReplacingExpressionVisitor(new[]{IsAGood.Parameters.First()}, new []{exp.Body}).Visit(IsAGood.Body);
IsBGood = Expression.Lambda<Func<EntityB, bool>>(expression, param);
ReplacingExpressionVisitor is available since EF Core 3.0, if you are using an older version you can write you own one, it should not be that hard.
Also you can try using Expression.Inkove (but I think I had some issues with it being translated previously):
Expression<Func<EntityB, EntityA>> exp = b => b.EntityA;
var param = exp.Parameters.First();
IsBGood = Expression.Lambda<Func<EntityB, bool>>(Expression.Invoke(IsAGood, exp.Body), param);

Note that you cannot call Compile on a lambda expression in this context, because this creates a delegate. But EF needs an Expression because the o/r-mapper needs the syntax information it contains to convert it into a SQL command. An executable delegate cannot be convert to SQL and cannot be executed by the DB.
Now to your concern about loading data into memory.
Neither IsAGood nor IsBGood nor your combined query loads any data into memory until you actually enumerate the query (e.g. with .ToList() or foreach).
You can even add parts dynamically as shown here:
var query = context.EntitiesB
.Where(x => x.count > 0); // No data access happens here.
if (useCondition) {
query = query.Where(IsBGood); // No data access happens here.
}
var result = query.ToList(); // The DB will be accessed here once.
Note that all the parts of your code only set up a query. They don't execute any query. Therefore I changed the name of the var from res to query.
Okay I see the problem. IsAGood cannot be called like this, since it is an Expression<>, not a delegate (and a delegate does not work anyway as I have explained earlier).
You can solve this by rewriting the expression as #GuruStron explains or change the test to
public static Expression<Func<EntityB, bool>> IsAinBGood =
b => b.EntityA.A != null &&
b.EntityA.A.valid;

Related

NHibernate Exception: Unable to cast object of type 'NHibernate.Hql.Ast.HqlParameter' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'

I'm trying to query my address location to find out addresses which are within the specific radius. Below is my where clause and results is an IEnumerable<Properties>
Func<Address, bool> predicate = l =>
{
if (l.Location == null && l.Longitude.HasValue && l.Latitude.HasValue)
{
var point = new Point(l.Longitude.Value, l.Latitude.Value);
return point.Distance(p) <= 300;
}
return false;
};
results = results.Where(x => predicate(x.Address));
However, I'm getting an exception from NHibernate as
"Unable to cast object of type 'NHibernate.Hql.Ast.HqlParameter' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'.
How can I fix this?
Any help is highly appreciated.
You need to use LINQ expressions, not lambdas!
Try this instead:
Expression<Func<Address, bool>> predicate = ...
Your filtering expression cannot be translated to SQL. It is a multi-line expression. linq-to-nhibernate, as any other orm LINQ provider, requires simple one liner lambda that can be translated to SQL.
Furthermore, your filtering expression use a class performing some computation (Distance() in your case). The LINQ provider will have no knowledge of how to convert that to SQL, unless you extend it. Better rewrite your computing in the lambda, if it can be written in a SQL translatable way.
If you need your predicate in many places, you may write a function to get it in a local var before using it.
private Expression<Func<Address, bool>> GetFilterByMaxDistance(double maxDistance)
{
// replace ??? with your computation, without block of code (no `{}`) nor call to
// functions not known for being handled by the LINQ provider
return a => ??? <= maxDistance;
}
...
var predicate = GetFilterByMaxDistance(300);
results = results.Where(predicate);
Otherwise, if your results local variable is already 'sufficiently filtered' for having reasonable performances, call .AsEnumerable() before applying your filter, this time only as a Func<Address, bool>, not as an Expression<Func<Address, bool>>.
Calling .AsENumerable() before applying .Where(predicate) will cause your predicate to get executed in memory by .Net runtime, without the need to convert it to SQL.
But of course this will cause the filtering to occur in your app rather than in the DB.

Lambda expression - For a Select New

I am trying to create a custom collection from an IQueryable object, where i am trying to perform a select statement but getting an error cannot convert to store expression. I am new to Lambda Expression. Kindly help me how to fix this problem.
Getting error at line c.Event.FirstUpper()
public static string FirstCharToUpper(string input)
{
if (String.IsNullOrEmpty(input))
return string.Empty;
var trimmed = input.Trim();
return trimmed.First().ToString().ToUpper() + trimmed.Substring(1);
}
public static Expression<Func<string, string>> GetFirstCaseToUpperExpression()
{
var expression = NJection.LambdaConverter.Fluent.Lambda.TransformMethodTo<Func<string, string>>()
.From(() => StringFormatter.FirstCharToUpper)
.ToLambda();
return expression;
}
Calling the Expression
return new List<LoggerModel>(
logDB.PELoggers
.Where(c => (c.SubscriberCode == SubscriberCode)).OrderByField(sortBy, ascendingOrder).Select(c => new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = c.Event.FirstUpper()
})
I suppose you are using Entity Framework or a smiliar O/R mapper.
Think about what you are doing here: you are writing a LINQ query that should be executed against your database. To do this, it will translate your LINQ query into a SQL query which will then be executed against your database.
But FirstCharToUpper() is a custom method in your code. Your database does not know anything about it, so your O/R mapper's LINQ provider cannot translate it into anything meaningful in SQL, hence you get the error.
So what you need to do is to first "finish" the query against your database to have the results in-memory and after that, apply any further processing that can only be done within the boundaries of your code on that in-memory collection.
You can do this simply by inserting .AsEnumerable() in your LINQ query before you do the select with your custom expression:
logDB.PELoggers
.Where(c => (c.SubscriberCode == SubscriberCode))
.OrderByField(sortBy, ascendingOrder)
.AsEnumerable()
.Select(c => new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = c.Event.FirstUpper()
})
When calling AsEnumerable(), the query against your database will be executed and the results are copied into an IEnumerable in memory. The Select() afterwards will now already be executed against the in-memory collection and not against the database anymore, thus it can use your custom FirstCharToUpper() method.
Edit based on your comments below:
Everything above is still valid, but in the comments you said your function needs to return IQueryable. In your case, what your FirstCharToUpper() method is doing is pretty simple and the LINQ-to-Entities provider does support methods like ToUpper and Substring. So I'd recommend to simply get rid of your helper method and instead write your LINQ query to do just that with methods that Entity Framework can translate to valid SQL:
logDB.PELoggers
.Where(c => (c.SubscriberCode == SubscriberCode))
.OrderByField(sortBy, ascendingOrder)
.Select(c => new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = c.Event.Substring(0, 1).ToUpper()
+ c.Event.Substring(1)
})
This will result in a SQL query that will already return the content in Event with an uppercase first letter right from the database.
To also support the IsNullOrEmpty check and the Trim you are doing (both also supported by LINQ-to-Entities) I recommend to change the lambda syntax to the LINQ query syntax so you can use the let statement for the trimming, which makes the code cleaner:
from c in logDB.PELoggers
let trimmedEvent = c.Event.Trim()
where c.SubscriberCode == SubscriberCode
select new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = !string.IsNullOrEmpty(trimmedEvent)
? trimmedEvent.Substring(0, 1).ToUpper()
+ trimmedEvent.Substring(1)
: string.Empty
};
In case you do not want to have this done in the LINQ query, you would need to do the uppercasing at some point later when your query against the DB has been executed, for example right in the View that will show your data. Or one option could be to apply the uppercasing in the Event property setter of your LoggerModel:
public class LoggerModel
{
// ...
private string event;
public string Event
{
get { return event; }
set { event = FirstCharToUpper(value); }
}
// ...
}
But there is no way to make custom functions work inside LINQ-to-Entities queries.

Method Chaining in EF6 Doesn't Output the correct SQL

I have the following methods:
private IEnumerable<CTNTransactionsView> RetrieveCTNTransactionsNotInTLS() {
IQueryable<int> talismanIdCollection = this._cc.TLSTransactionView.Select(x => x.kSECSYSTrans);
return this._cc.CTNTransactionView
.Where(x => !talismanIdCollection.Contains(x.kSECSYSTrans));
}
public IEnumerable<CTNTransactionsView> RetrieveCTNTransactionsNotInTLSPast24Hours() {
DateTime previousDate = DateTime.Now.Date.AddDays(-1.0);
return this.RetrieveCTNTransactionsNotInTLS()
.Where(x => x.dSECSYSTimeStamp >= previousDate);
}
public IEnumerable<CTNTransactionsView> RetrieveCTNTransactionsNotInTLSPast24HoursVersionTwo() {
DateTime previousDate = DateTime.Now.Date.AddDays(-1.0);
IQueryable<int> talismanIdCollection = this._cc.TLSTransactionView
.Select(x => x.kSECSYSTrans);
return this._cc.CTNTransactionView
.Where(x => !talismanIdCollection.Contains(x.kSECSYSTrans))
.Where(x=> x.dSECSYSTimeStamp >= previousDate);
}
For some reason, the SQL output generated by Entity Framework 6 does not match the results.
The RetrieveCTNTransactionsNotInTLSPast24HoursVersionTwo() method will properly give a SQL Output Statement that has the following:
select ...... from ... where ... AND ([Extent1].[dSECSYSTimeStamp] >= #p__linq__0)}
The other one does not have the filter for the dSECSYSTimeStamp when I View the SQL Statement Output.
The methods I am comparing are the RetrieveCTNTransactionsNotInTLSPast24Hours() and the RetrieveCTNTransactionsNotInTLSPast24HoursVersionTwo().
I have compared the SQL using VS as well as attaching a Debug.Writeline() to the Database.Log in the context.
From debugging and looking at the SQL output, one seems to contain the date filter whereas the other doesn't and yet they both provide the correct result.
I have tried looking at the SQL (by breakpointing and seeing the output) from using the following:
System.Diagnostics.Debug.WriteLine("Running first method");
var result = this.repo.RetrieveCTNTransactionsNotInTLSPast24Hours();
var count = result.Count();
System.Diagnostics.Debug.WriteLine("Running Second method");
var resultTwo = this.repo.RetrieveCTNTransactionsNotInTLSPast24HoursVersionTwo();
var count2 = resultTwo.Count();
I am using EF 6.0.
Note: The results are the same as both do exactly the same thing and output the same result. However, I am curious and would like to understand why the SQL Generated isn't the same?
The issue is you are returning an IEnumerable from your method. If you do this, you force SQL to run the query (unfiltered) and then use C# to then run the second query. Alter your internal query to return an IQueryable. This will allow the unexecuted expression tree to be passed into the second query, which will then be evaluated when your run it.
i.e.
private IQueryable<CTNTransactionsView> RetrieveCTNTransactionsNotInTLS()
You should then get the same SQL.
I think, you just define a query expression, but not use it immediately, in this time, I will add .ToList() in the Linq expression's end, but you have to change method's return type into a right type like List<kSECSYSTrans> before this operation. I just seldom use the type IEnumerable.

C# PredicateBuilder Entities: The parameter 'f' was not bound in the specified LINQ to Entities query expression

I needed to build a dynamic filter and I wanted to keep using entities. Because of this reason I wanted to use the PredicateBuilder from albahari.
I created the following code:
var invoerDatums = PredicateBuilder.True<OnderzoeksVragen>();
var inner = PredicateBuilder.False<OnderzoeksVragen>();
foreach (var filter in set.RapportInvoerFilter.ToList())
{
if(filter.IsDate)
{
var date = DateTime.Parse(filter.Waarde);
invoerDatums = invoerDatums.Or(o => o.Van >= date && o.Tot <= date);
}
else
{
string temp = filter.Waarde;
inner = inner.Or(o => o.OnderzoekType == temp);
}
}
invoerDatums = invoerDatums.And(inner);
var onderzoeksVragen = entities.OnderzoeksVragen
.AsExpandable()
.Where(invoerDatums)
.ToList();
When I ran the code there was only 1 filter which wasn't a date filter. So only the inner predicate was filled. When the predicate was executed I got the following error.
The parameter 'f' was not bound in the
specified LINQ to Entities query
expression.
While searching for an answer I found the following page. But this is already implemented in the LINQKit.
Does anyone else experienced this error and know how to solve it?
I ran across the same error, the issue seemed to be when I had predicates made with PredicateBuilder that were in turn made up of other predicates made with PredicateBuilder
e.g. (A OR B) AND (X OR Y) where one builder creates A OR B, one creates X OR Y and a third ANDs them together.
With just one level of predicates AsExpandable worked fine, when more than one level was introduced I got the same error.
I wasn't able to find any help but through some trial and error I was able to get things to work.
Every time I called a predicate I followed it with the Expand extension method.
Here is a bit of the code, cut down for simplicity:
public static IQueryable<Submission> AddOptionFilter(
this IQueryable<Submission> query,
IEnumerable<IGrouping<int, int>> options)
{
var predicate = options.Aggregate(
PredicateBuilder.False<Submission>(),
(accumulator, optionIds) => accumulator.Or(ConstructOptionMatchPredicate(optionIds).Expand()));
query = query.Where(predicate.Expand());
return query;
}
Query is an IQueryable which has already had AsExpandable called, ConstructOptionNotMatchPredicate returns an Expression.
Once we got past the error we were certainly able to build up complicated filters at runtime against the entity framework.
Edit:
Since people are still commenting on and up voting this I assume it is still useful so I am sharing another fix. Basically I have stopped using LinqKit and it's predicate builder in favour of this Universal Predicate Builder that has the same API but doesn't need Expand calls, well worth checking out.
I got this error and Mant101's explanation got me the answer, but you might be looking for a simpler example that causes the problem:
// This predicate is the 1st predicate builder
var predicate = PredicateBuilder.True<Widget>();
// and I am adding more predicates to it (all no problem here)
predicate = predicate.And(c => c.ColumnA == 1);
predicate = predicate.And(c => c.ColumnB > 32);
predicate = predicate.And(c => c.ColumnC == 73);
// Now I want to add another "AND" predicate which actually comprises
// of a whole list of sub-"OR" predicates
if(keywords.Length > 0)
{
// NOTICE: Here I am starting off a brand new 2nd predicate builder....
// (I'm not "AND"ing it to the existing one (yet))
var subpredicate = PredicateBuilder.False<Widget>();
foreach(string s in keywords)
{
string t = s; // s is part of enumerable so need to make a copy of it
subpredicate = subpredicate.Or(c => c.Name.Contains(t));
}
// This is the "gotcha" bit... ANDing the independent
// sub-predicate to the 1st one....
// If done like this, you will FAIL!
// predicate = predicate.And(subpredicate); // FAIL at runtime!
// To correct it, you must do this...
predicate = predicate.And(subpredicate.Expand()); // OK at runtime!
}
Hope this helps! :-)

Use Optional OR Clause in Linq.Table.Where()

Is there a way to make the ProjectID check below part of an optional block? I'm a recent .Net convert from Java EE and I'm looking for something similar to the Hibernate Criteria API. I'd like to simplify the block below and only have to call Where() once. I'm also not sure of the performance implications of doing a Where() with lambdas as I just started working with .Net a week ago.
public IQueryable<Project> FindByIdOrDescription(string search)
{
int projectID;
bool isID = int.TryParse(search, out projectID);
IQueryable<Project> projects;
if (isID)
{
projects = dataContext.Projects.Where(p => p.ProjectDescription.Contains(search) || p.ProjectID == projectID);
}
else
{
projects = dataContext.Projects.Where(p => p.ProjectDescription.Contains(search));
}
return projects;
}
If you're looking for optionally adding AND xyz, you could simply chain Where calls:
var projects = dataContext.Projects.Where(p => p.ProjectDescription.Contains(search));
if (isID)
projects = projects.Where(p => p.ProjectID == projectID);
Unfortunately things are not so easy when you'd like to do an OR xyz. For this to work, you'll need to build a predicate expression by hand. It's not pretty. One way to do this is
Expression<Func<Project, bool>> predicate = p => p.ProjectDescription.Contains(search);
if (isID)
{
ParameterExpression param = expr.Body.Parameters[0];
predicate = Expression.Lambda<Func<Project, bool>>(
Expression.Or(
expr.Body,
Expression.Equal(
Expression.Property(param, "ProjectID"),
Expression.Constant(projectID))),
param);
}
var projects = dataContext.Projects.Where(predicate);
Note that adding a condition to the existing predicate is a lot more work than creating the initial expression, because we need to completely rebuild the expression. (A predicate must always use a single parameter; declaring two expressions using lambda syntax will create two separate parameter expression objects, one for each predicate.) Note that the C# compiler does roughly the same behind the scenes when you use lambda syntax for the initial predicate.
Note that this might look familiar if you're used to the criteria API for Hibernate, just a little more verbose.
Note, however, that some LINQ implementations are pretty smart, so the following may also work:
var projects = dataContext.Projects.Where(
p => p.ProjectDescription.Contains(search)
|| (isID && p.ProjectID == projectID));
YMMV, though, so do check the generated SQL.
The query isn't parsed and executed until the first time you actually access the elements of the IQueryable. Therefore, no matter how many where's you keep appending (which you're not even doing here anyway) you don't need to worry about hitting the DB too many times.
Delegates / expressions can be "chained", like this (untested pseudo-code):
Expression<Predicate<Project>> predicate = p => p.ProjectDescription.Contains(search);
if ( isID )
predicate = p => predicate(p) || p.ProjectID == projectId;
return dataContext.Where(predicate);

Categories