Invoke an Expression in a Select statement - LINQ to Entity Framework - linq

I'm trying to use an already existing Expression building class that I made when trying to do a select clause, but I'm not sure how to attach the expression to the expression tree for the Select, I tried doing the following:
var catalogs = matchingCatalogs.Select(c => new
{
c.CatalogID,
Name = EntitiesExpressionHelper.MakeTranslationExpression<Catalog>("Name", ApplicationContext.Instance.CurrentLanguageID).Compile().Invoke(c),
CategoryName = EntitiesExpressionHelper.MakeTranslationExpression<Category>("Name", ApplicationContext.Instance.CurrentLanguageID).Compile().Invoke(c.Category),
c.CategoryID,
c.StartDateUTC,
c.EndDateUTC
});
But I obviously get the error stating that the Entity Framework can't map Invoke to a SQL method. Is there a way to work around this?
FYI, EntitiesExpressionHelper.MakeTranslationExpression<T>(string name, int languageID) is equivalent to:
x => x.Translations.Count(t => t.LanguageID == languageID) == 0 ? x.Translations.Count() > 0 ? x.Translations.FirstOrDefault().Name : "" : x.Translations.FirstOrDefault(t => t.LanguageID == languageID).Name
EDIT: I realize that I need to use an ExpressionVisitor to accomplish this, but I'm not sure how to use an ExpressionVisitor to alter the MemberInitExpression, so if anyone knows how to accomplish this, let me know.

You need to capture the expressions in vars. You won't be able to use anonymous types. The general idea is that this works:
Expression<Func<Foo, Bar>> exp = GenExpression();
var q = matchingCatalogs.Select(exp);
But this will not:
var q = matchingCatalogs.Select(GenExpression());
The first happily passes the result of GenExpression to L2E. The second tries to pass GenExpression itself to L2E, rather than the result.
So you need a reference to a var of the same type as the expression. Those can't be implicitly typed, so you'll need a real type for your result type.

Related

LINQ Breaking changes in EF Core 3.0. Using expressions

In my app I have some queries that use the same repeated logic:
var someThings = context.table1
.where(SomeLogic)
.ToList();
With EF Core 2.1 I could encapsulate this logic in a layer with all these expressions:
public static Expression<Func<MyObject, bool>> SomeLogic =>
myObject => myObject.CreationDate.Date == DateTime.Now.Date
&& (myObject.Whatever.HasValue || myObject.MoreWhatever);
Now I discovered this was being evaluated in memory, and that's bad.
If I do something like:
var someThings = context.table1
.where(myObject =>
myObject.CreationDate.Date == DateTime.Now.Date
&& (myObject.Whatever.HasValue || myObject.MoreWhatever))
.ToList();
then the query is evaluated in the DB, but I am putting some logic in the wrong layer.
I tried to subsitute Expression with a function or any other tool, but I don't find a way to do it.
Is there a way to encapsulate the logic of a query in a layer as I was doing before, but preserving EF rules so that this query can still be evaluated in the DB?
Thanks.
Why you need a "real" expression and not just a Lambda is explained in this answer. The created Expression can be created anywhere and passed as a parameter to the function that executes the query.
This answer should guide the way you need to go. You only have to replace the two dummy expressions with the whatever.hasvalue...stuff
var param = Expression.Parameter(typeof(MyObject), nameof(MyObject));
// myObject.CreationDate.Date == DateTime.Now.Date
Expression dateExpression = Expression.Equal(Expression.Constant(DateTime.Now),
Expression.PropertyOrField(param, "CreationDate"));
var dummyExpression1 = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
var dummyExpression2 = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
// && (myObject.Whatever.HasValue || myObject.MoreWhatever)
Expression orExpression = Expression.Or(dummyExpression1, dummyExpression2);
Expression allConditions = Expression.And(dateExpression, orExpression);
//myObject =>
Expression myExpression = Expression.Lambda<Func<MyObject, bool>>(allConditions, param);
var someThings = context.table1
.where(myExpression)
.ToList();
I had the most trouble with Expression.PropertyOrField. If you have nested structures you need to loop through the data structure and call Expression.PropertyOrField with the first parameter being the result from the previous call to Expression.PropertyOrField.

Linq where clause invalid

var advocacy = (from c in svcContext.CreateQuery("lead")
join a in svcContext.CreateQuery("product")
on c["transactioncurrencyid"] equals a["transactioncurrencyid"]
where a["capg_billingtimeframe"].Equals(126350000)
select new
{
dues = c["capg_calculatedduesbilling"],
product = c["capg_primaryproduct"],
listprice = a["price"],
eligibility = c.FormattedValues["capg_eligibility"]
});
That is my linq query and it is giving me the error: Invalid 'where' condition. An entity member is invoking an invalid property or method.
I have looked online everywhere and done their suggestions. I am not using Xrm.cs because late binding can be faster. I have tried using the == operand and I have tried doing (int) and Convert.ToInt32(a["capg_billingtimeframe"]) and even converting everything to a string. I will say that a["capg_billingtimeframe"] I know is an object (that's why I did those conversions.
I'm guessing by that integer that capg_billingtimeframe is an optionset. If it is, you need to cast it like this:
where ((OptionSetValue)a["capg_billingtimeframe"]).Value == 126350000
I used early bound and for getting the local I wrote:
OptionSetValue branch = this.InputTargetEntity.Attributes.Contains("capg_calculatorutilized") ? (OptionSetValue)this.InputTargetEntity["capg_calculatorutilized"] : (OptionSetValue)this.TargetPreImage["capg_calculatorutilized"];
I then had to get the Optionsets.cs using crmsvcutil and writing:
if (branch.Value == (int)capg_calculatorrequired.SectionA)
Works like a charm.

linq select clause syntax

Linq Select method takes Func as input parameter. This means I can have multiple statements in selector for Select, such as
var myresult = sources.Select(s =>
{int x; if (s.val = high) {x=1} else if (s.val = med) {x=2} else {x=3}; return x;
}
)
How can I do this using Linq query syntax
var myresult = from s in sources
select ...
Here, the code in Func part (if ... else if .. else) is artificial. What I really want to know is the syntax of select clause, which may be described as
select select-expression
What is the syntax of
select-expression
I wouldn't want to see your first version in my code. If you need to have what is basically a full function in the lambda, I would rather see the lambda simply invoke a full function! In other words...
theQuery.Select(s => GetX(s)); // just define a GetX function
And that would also be a straightforward translation to query expression syntax
from s in sources
select GetX(s);
You would not be able to put your full code block into the query expression syntax. You could translate your given logic to something usable (yet messy), however I'm quite sure your snippet is just a general example. On the offhand change it isn't, you might try
select s.val == high ? 1 : (s.val == med ? 2 : 3); // totally messy
Instead of special-casing values, with the if/else equivalent of a switch statement, it is more Linq-friendly to group and filter your values:
var myResult = from s in sources
group by s.val into g
select new { Val = g.Key, Sources = g };
var groupHigh = myResult.Where(i => i.Val == high);
var groupMedium = myResult.Where(i => i.Val == medium);
var groupOther = myResult.Except(groupHigh.Concat(groupMedium));
Note that the code I've provided is just a starting place, and isn't the best way to achieve your specific goal. I'd address this in one of these ways:
Change how group by is used (use SomeFunction(s.Val) instead of directly using s.Val)
Change the code around this query to flow better with the natural groupings, so I didn't require the groups to be transformed
This is not possible.
If you really want to, you could create an Func<T> from an anonymous method and invoke it, but that would be horrible.
MSDN indicates select is a contextual keyword of C# 4.0. So I checked the C# Language Specififcation 4.0. Its Select clauses section (7.16.2.5) specifies that
A query expression of the form
from x in e select v
is translated into
( e ) . Select ( x => v )
except when v is the identifier x, the translation is simply
( e )
As the result, the syntax for
select select-expresion
select-expression should be anything that can be used as TResult in Select Method. So the functionality can be done using anonymous Func in Select method may not be able to achieved using select clause.
Conclusion is that you should stick with Method syntax as this is how the code really runs behind the scene.

Linq expression subsonic 3.0.0.3

I want to 'build' a combined query for Subsonic 3.0.0.3, what is the best way for this?
I tried;
Expression<Func<Person, bool>> exp = p => true;
Expression<Func<Person, bool>> fContinent = p => p.ContinentID == 1;
Expression<Func<Person, bool>> fType = p => p.TypeID == 1;
exp = Expression.Lambda<Func<Person, bool>>(Expression.AndAlso(exp, fContinent), exp.Parameters);
exp = Expression.Lambda<Func<Person, bool>>(Expression.AndAlso(exp, fType), exp.Parameters);
var personList = Person.Find(exp);
But that will give the exception "The binary operator AndAlso is not defined ..."
I also tried using predicates but that will throw exceptions as well (Expression.Invoke is not supported).
In subsonic 2 I would have used the SqlQuery object, but I would like to know the proper way to do this in version 3 using linq / expressions.
Have you tried And instead of AndAlso?
The right way to do this is to combine the lambda expression bodies, like this:
exp = Expression.Lambda<Func<Person, bool>>(
Expression.And(exp.Body, fContinent.Body), exp.Parameters);
Even if And is supported by your query provider, you'll also need to replace the parameter references in fContinent's Body with references to the parameter defined in exp--as is, your two expression bodies (combined with And) reference two distinct parameters, each named p.
See my answer to this question for the cleanest method to replace expression parameters.
I asked this question, but I am using the combined query in subsonic just like you.
In short, you want to use a PredicateBuilder to build up the query. When you want to execute it in your subsonic object (assuming ActiveRecord), use code like this:
var predicate = /* Build up predicate with PredicateBuilder */;
var recs = SubsonicClass.All().Where(predicate.Compile()).ToList();

LINQ Dynamic Expression API, predicate with DBNull.Value comparison

I have an issue using the Dynamic Expression API. I cannot seem to compare a DataTable field against DBNull.Value. The API is supposed to be able to "support static field or static property access. Any public field or property can be accessed.". However given the following query:
var whatever = table1.AsEnumerable()
.Join(table2.AsEnumerable(),
(x) => x.Field<int>("Table1_ID"),
(y) => y.Field<int>("Table2_ID"),
(x, y) => new { x, y})
.AsQueryable()
.Where("x[\"NullableIntColumnName\"] == DBNull.Value");
I end up getting the error: "No property or field 'DBNull' exists in type '<>f__AnonymousType0`2'"
Anyone have ideas on how to get around this? I can't use Submission.Field("NullableIntColumnName") in the string passed to the Where method either, btw, or else I would be able to compare against null instead of DBNull.Value.
Well, I finally got it. cptScarlet almost had it.
var values = new object[] { DBNull.Value };
...
.Where("x[\"NullableIntColumnName\"] == #0", values);
or
.Where("x[\"NullableIntColumnName\"] == #0", DBNull.Value);
What happens when you replace your current .Where with something like
.Where(string.format("x[\"NullableIntColumnName\"] == {0}",DBNull.Value));
If you change x.Field<int>("Table1_ID") to x.Field<int?>("Table1_ID") then you'll get nullable integers instead of regular integers, and any DBNull values will be converted to simple C# null values. Based simply on your code snippet, I'm not even sure you'd need dynamic expressions - a simple .Where(foo => foo.x == null) ought to work.
In general, you can also try:
.Where("NullableColumnName.HasValue");
Sorry to non-answer with a USL but...
Have you looked in the source? There's not a lot of it. My guess is that DBNull is not in the list of registered root objects.
I dont have the source to hand right now, but it is also likely to tell you what any other constants one might compare against might be.
.Where(a => a.IntColName == null);
Edit:
Sorry, I did't see this dynamic requirement... Dynamic would be: (at least in Framework 4)
var intColName = "...";
.Where(string.Format("it.{0} is null", intColName));

Resources