Expression tree library ignores short-circuit evaluation concept - c#

Please, take a look at following proof of concept:
private class Model
{
public string Data { get; set; }
public bool NonEmpty() { return Data.Length > 0; }
}
private static Func<Model, bool> Compile()
{
var type = typeof(Model);
var expr = Expression.Parameter(typeof(Model));
var subarg1 = Expression.Property(expr, type.GetProperty("Data"));
var subarg2 = Expression.Constant(null);
var arg1 = Expression.NotEqual(subarg1, subarg2);
var arg2 = Expression.Call(expr, type.GetMethod("NonEmpty"));
var tree = Expression.And(arg1, arg2); // Data != null && NonEmpty()
var func = Expression.Lambda<Func<Model, bool>>(tree, expr).Compile();
return func;
}
var model = new Model {Data = null};
var standardTest = model.Data != null && model.NonEmpty(); // returns false
var exprTreeTest = Compile().Invoke(model); // throws null ref exception
Because the first operand evaluates to false, the result of the AND operation is false no matter what the value of second one may be. That's why the second operand shouldn't be computed. While C# compiler does it correctly, expression library does not.
How to fix my code to respect the short-circuit evaluation?

Expression.And represents the non-short circuiting AND operator (&).
Expression.AndAlso represents the short circuiting AND operator (&&).

Related

How do i append a simple expression onto IQueryable

I have the following method:
public List<Customer> SearchTest(string city, int skip, int take)
{
EcomContext db = new EcomContext();
var results = db.Customers.Where(n => n.City == city).OrdeyBy(n => n.Name).Skip(skip).Take(10);
results = AddDeleteCheck<Customer>(results);
return results.ToList()
}
And this reusable method:
private IQueryable<T> AddArchivedCheck<T>(IQueryable<T> data)
{
var parameter = Expression.Parameter(typeof(T));
var e1 = Expression.Equal(Expression.Property(parameter, "Archived"), Expression.Constant(false));
var e2 = data.Expression;
var e3 = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(e1, e2), parameter);
return data.Where(e3);
}
I want to be able to call this method from a number of different functions so i have made it generic. It should take the expression from the IQueryable object and add a check onto this (Archived == false).
I am getting this error:
The binary operator AndAlso is not defined for the types 'System.Boolean' and 'System.Linq.IQueryable` [Ecom.Customer]
It is easier than what you wrote:
private static IQueryable<T> AddArchivedCheck<T>(IQueryable<T> data)
{
var parameter = Expression.Parameter(typeof(T));
var e1 = Expression.Equal(Expression.Property(parameter, "Archived"), Expression.Constant(false));
var lambda = Expression.Lambda<Func<T, bool>>(e1, parameter);
return data.Where(lambda);
}
Remember that in Linq:
var result = query.Where(condition1).Where(condition2);
is equivalent to:
var result = query.Where(condition1 && condition2);

Create Expression for List.Any clause

Consider this hierarchy of classes.
class Event
{
public Attendees[] AttendeesList;
}
class Attendees
{
public ComplexProperty Property;
public object Value;
}
class ComplexProperty
{
}
class Program
{
static void Main(string[] args)
{
// There are constants.
ComplexProperty constproperty = new ComplexProperty();
object constValue = 5;
// consider this linq query:
Event evnt = new Event();
var result = evnt.AttendeesList.Any((attnds) => attnds.Property == constproperty && attnds.Value == constValue);
// I want to create an Expression tree for above linq query. I need something like this:
ParameterExpression parameter = Expression.Parameter(typeof(Attendees));
Expression left = Expression.Property(parameter, typeof(ComplexProperty).GetProperty("Property"));
// complete this piece.......
}
}
I want to create a Linq.Expressions.Expression for evnt.AttendeesList.Any((attnds) => attnds.Property == constproperty && attnds.Value == constValue);
this linq query. How to query collection with Linq.Expressions looks similar, but I have Any in my linq expression.
Please help.
This will give you a start:
ParameterExpression parameter = Expression.Parameter(typeof(Attendees));
Expression left = Expression.Equal(Expression.Property(parameter, "Property"), Expression.Constant(constproperty));
var objType = constValue == null ? typeof(object) : constValue.GetType();
var convertLeft = Expression.Convert(Expression.Property(parameter, "Value"), objType);
var convertRight = Expression.Convert(Expression.Constant(constValue), objType);
Expression right = Expression.Equal(convertLeft, convertRight);
Expression joined = Expression.And(left, right);
var anyMethod = typeof(Queryable).GetMethods().Where(m => m.Name=="Any" && m.GetParameters().Count() == 2).First().MakeGenericMethod(typeof(Attendees));
var call = Expression.Call(anyMethod, Expression.Constant(evnt.AttendeesList, typeof(IQueryable<Attendees>)), Expression.Lambda(joined, parameter));
I changed Value to type int for ease of use. If you want to keep it as an object, you will need to add a Expression.Convert() call in the right Expression.

Dynamic LINQ aggregates on IQueryable as a single query

I'm building a datagrid library that works on generic IQueryable data sources. At the bottom selected columns will have aggregates: sum, average, count etc.
I can compute the sum/average/count individually using the code from this article How to do a Sum using Dynamic LINQ
I don't want to run them individually for a datasource, as this would cause multiple queries on the database, I would rather create a single expression tree an execute this as a single query.
In static LINQ you'd do all the .Sum, .Average and .Count methods and return a new anonymous type with the values. I don't need an anonymous type (unless this is the only way): a list or array of the aggregates would be fine.
I assume from the other article I would need to string together a series of MethodCallExpression objects somehow. Can anyone help?
I found an alternative approach which uses the Dynamic LINQ library and avoids having to construct convoluted expression trees.
The solution is in the unit test below for anyone who is interested. I have a random dataset called TestQueryableDataset. The generic type of this IQueryable datasource has a Total property (decimal), a Discount property (nullable decimal) and an ID property (int).
The unit test gets the expected results first, using static LINQ queries.
It then constructs a select statement that uses the groupby variable 'it' to compute the sum, average and count. The property names are passed in by string to demonstrate this is stringly-typed.
The group-by method .GroupBy(x=> 1) is a dummy grouping to enable the aggregates to apply to the whole dataset.
Note that this returns a single dynamic result with properties t0, t1 and t2. However, the groupby/select operation still returns an IQueryable but with a single result. We have to use the t.Cast().First(); to convert to an array of object, then get the first result.
We can then use reflection to get the properties of each result (t0, t1, t2) as the actual values and assert that they match the static result we got earlier.
[TestMethod()]
[TestProperty("Anvil.DataSets", "QueryableExtensions")]
public void DynamicAggregate_test()
{
var source = new Anvil.Test.DataSets.TestQueryableDataset();
var data = source.GetData();
var expectedTotal = (from d in data select d.Total).Sum();
var expectedDiscount = (from d in data select d.Discount).Average();
var expectedCount = (from d in data select d.ID).Count();
const string prop0 = "Total";
const string prop1 = "Discount";
const string prop2 = "ID";
string sumExpr = string.Format("new ( Sum(it.{0}) as t0, Average(it.{1}) as t1 , Count() as t2)", prop0,prop1, prop2);
var t = data.GroupBy(x => 1).Select(sumExpr);
var firstItem = t.Cast<object>().First();
var ttype = firstItem.GetType();
var p0 = ttype.GetProperty("t0");
var p1 = ttype.GetProperty("t1");
var p2 = ttype.GetProperty("t2");
decimal actualTotal = (decimal)(p0.GetValue(firstItem));
decimal actualDiscount = (decimal)(p1.GetValue(firstItem));
int actualCount = (int)(p2.GetValue(firstItem));
Assert.AreEqual(expectedTotal, actualTotal);
Assert.AreEqual(expectedDiscount, actualDiscount);
Assert.AreEqual(expectedCount, actualCount);
}
See also:
Dynamic Linq GroupBy
System.LINQ.Dynamic: Select(" new (...)") into a List<T> (or any other enumerable collection of <T>)
http://developergems.blogspot.co.uk/2012/02/group-by-with-dynamic-linq.html
You don't need anonymous type. You just need a type with the 3 properties Sum, Count and Average. Sum and Average type aren't known at design time. So, use Object type for these 2 properties. Count is always an int.
public class Aggregation
{
public Aggregation(object sum, object average, int count)
{
Sum = sum;
Average = average;
Count = count;
}
public object Sum { get; private set; }
public object Average { get; private set; }
public int Count { get; private set; }
}
Like the Sum extension method described in the article How to do a Sum using Dynamic LINQ, you can write an Aggregate extension method which compute an Aggregation class instance from a IQueryable collection and a property name.
The real difficulty is about determining the Average overload method which match the property type. Overload can't be determined from the return type but from the return type of the lambda expression used as second argument.
For example, if the property type is an int, code has to select the public static double Average<TSource>(
this IQueryable<TSource> source,
Expression<Func<TSource, int>> selector
) overload.
public static Aggregation Aggregate(this IQueryable source, string member)
{
if (source == null)
throw new ArgumentNullException("source");
if (member == null)
throw new ArgumentNullException("member");
// Properties
PropertyInfo property = source.ElementType.GetProperty(member);
ParameterExpression parameter = Expression.Parameter(source.ElementType, "s");
Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, property), parameter);
// We've tried to find an expression of the type Expression<Func<TSource, TAcc>>,
// which is expressed as ( (TSource s) => s.Price );
// Methods
MethodInfo sumMethod = typeof(Queryable).GetMethods().First(
m => m.Name == "Sum"
&& m.ReturnType == property.PropertyType // should match the type of the property
&& m.IsGenericMethod);
MethodInfo averageMethod = typeof(Queryable).GetMethods().First(
m => m.Name == "Average"
&& m.IsGenericMethod
&& m.GetParameters()[1]
.ParameterType
.GetGenericArguments()[0]
.GetGenericArguments()[1] == property.PropertyType);
MethodInfo countMethod = typeof(Queryable).GetMethods().First(
m => m.Name == "Count"
&& m.IsGenericMethod);
return new Aggregation(
source.Provider.Execute(
Expression.Call(
null,
sumMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) })),
source.Provider.Execute(
Expression.Call(
null,
averageMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) })),
(int)source.Provider.Execute(
Expression.Call(
null,
countMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression })));
}
here is my solution for sum, average and min, max .. this is what i have used in one of the projects.
public static object AggregateFunc(this IQueryable source, string function, string member)
{
if (source == null) throw new ArgumentNullException("source");
if (member == null) throw new ArgumentNullException("member");
// Properties
PropertyInfo property = source.ElementType.GetProperty(member);
ParameterExpression parameter = Expression.Parameter(source.ElementType, "s");
// We've tried to find an expression of the type Expression<Func<TSource, TAcc>>,
// which is expressed as ( (TSource s) => s.Price );
Type propertyType = property.PropertyType;
Type convertPropType = property.PropertyType;
if (function == "Sum")//convert int to bigint
{
if (propertyType == typeof(Int32))
convertPropType = typeof(Int64);
else if (propertyType == typeof(Int32?))
convertPropType = typeof(Int64?);
}
Expression selector = Expression.Lambda(Expression.Convert(Expression.MakeMemberAccess(parameter, property), convertPropType), parameter);
//var methods = typeof(Queryable).GetMethods().Where(x => x.Name == function);
// Method
MethodInfo aggregateMethod = typeof(Queryable).GetMethods().SingleOrDefault(
m => m.Name == function
&& m.IsGenericMethod
&& m.GetParameters().Length == 2 && m.GetParameters()[1].ParameterType.GenericTypeArguments[0].GenericTypeArguments[1] == convertPropType);// very hacky but works :)
MethodCallExpression callExpr;
// Sum, Average
if (aggregateMethod != null)
{
callExpr = Expression.Call(
null,
aggregateMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) });
return source.Provider.Execute(callExpr);
}
// Min, Max
else
{
aggregateMethod = typeof(Queryable).GetMethods().SingleOrDefault(
m => m.Name == function
&& m.GetGenericArguments().Length == 2
&& m.IsGenericMethod);
if (aggregateMethod != null)
{
callExpr = Expression.Call(
null,
aggregateMethod.MakeGenericMethod(new[] { source.ElementType, propertyType }),
new[] { source.Expression, Expression.Quote(selector) });
return source.Provider.Execute(callExpr);
}
}
return null;
}

Dynamic predicates for Linq-to-Entity queries

The following Linq-to-Entities query works fine:
var query = repository.Where(r => r.YearProp1.HasValue &&
r.YearProp1 >= minYear &&
r.YearProp1 <= maxYear);
My database has a dozen or so columns that all report year-related information (short? data type). I want to reuse the same Linq-to-Entities logic for all these columns. Something like:
Func<RepoEntity, short?> fx = GetYearPropertyFunction();
var query = repository.Where(r => fx(r).HasValue &&
fx(r) >= minYear &&
fx(r) <= maxYear);
This results in the error:
LINQ to Entities does not recognize the method
'System.Nullable`1[System.Int16] fx(RepoEntity)' method, and this
method cannot be translated into a store expression.
I understand why I am getting the error, but am wondering if there is a workaround that doesn't involve duplicating code a dozen times just to change the property on which the SQL query is operating.
I would be reusing the function in more than one query, so I guess the general version of my question is: Is there a way to convert a simple property-getter lambda function to an Expression that can be consumed by Linq-to-Entities?
Building off of Raphaël Althaus' answer, but adding the generic selector you were originally looking for:
public static class Examples
{
public static Expression<Func<MyEntity, short?>> SelectPropertyOne()
{
return x => x.PropertyOne;
}
public static Expression<Func<MyEntity, short?>> SelectPropertyTwo()
{
return x => x.PropertyTwo;
}
public static Expression<Func<TEntity, bool>> BetweenNullable<TEntity, TNull>(Expression<Func<TEntity, Nullable<TNull>>> selector, Nullable<TNull> minRange, Nullable<TNull> maxRange) where TNull : struct
{
var param = Expression.Parameter(typeof(TEntity), "entity");
var member = Expression.Invoke(selector, param);
Expression hasValue = Expression.Property(member, "HasValue");
Expression greaterThanMinRange = Expression.GreaterThanOrEqual(member,
Expression.Convert(Expression.Constant(minRange), typeof(Nullable<TNull>)));
Expression lessThanMaxRange = Expression.LessThanOrEqual(member,
Expression.Convert(Expression.Constant(maxRange), typeof(Nullable<TNull>)));
Expression body = Expression.AndAlso(hasValue,
Expression.AndAlso(greaterThanMinRange, lessThanMaxRange));
return Expression.Lambda<Func<TEntity, bool>>(body, param);
}
}
Could be used somewhat like the original query you were looking for:
Expression<Func<MyEntity, short?>> whatToSelect = Examples.SelectPropertyOne;
var query = Context
.MyEntities
.Where(Examples.BetweenNullable<MyEntity, short>(whatToSelect, 0, 30));
A predicate is a filter in itself that should evaluate to bool (for whether or not to include it in the results). You can rework your method to look like this and it should work:
public static Expression<Func<RepoEntity, bool>> FitsWithinRange(int minYear, int maxYear)
{
return w => w.HasValue && w >= minYear && w <= maxYear;
}
Edit: Oh and to use it:
var query = repository.Where(Repository.FitsWithinRange(minYear, maxYear));
You could do something like that (not sure if it will work "as is" in linq2 entities, but if you have a problem... just tell)
usage
var query = <your IQueryable<T> entity>.NullableShortBetween(1, 3).ToList();
function
public static IQueryable<T> NullableShortBetween<T>(this IQueryable<T> queryable, short? minValue, short? maxValue) where T: class
{
//item (= left part of the lambda)
var parameterExpression = Expression.Parameter(typeof (T), "item");
//retrieve all nullable short properties of your entity, to change if you have other criterias to get these "year" properties
var shortProperties = typeof (T).GetProperties().Where(m => m.CanRead && m.CanWrite && m.PropertyType == typeof(short?));
foreach (var shortProperty in shortProperties)
{
//item (right part of the lambda)
Expression memberExpression = parameterExpression;
//item.<PropertyName>
memberExpression = Expression.Property(memberExpression, shortProperty);
//item.<PropertyName>.HasValue
Expression firstPart = Expression.Property(memberExpression, "HasValue");
//item.<PropertyName> >= minValue
Expression secondPart = Expression.GreaterThanOrEqual(memberExpression, Expression.Convert(Expression.Constant(minValue), typeof (short?)));
//item.<PropertyName> <= maxValue
var thirdPart = Expression.LessThanOrEqual(memberExpression, Expression.Convert(Expression.Constant(maxValue), typeof (short?)));
//item.<PropertyName>.HasValue && item.<PropertyName> >= minValue
var result = Expression.And(firstPart, secondPart);
//item.<PropertyName>.HasValue && item.<PropertyName> >= minValue && item.<PropertyName> <= maxValue
result = Expression.AndAlso(result, thirdPart);
//pass the predicate to the queryable
queryable = queryable.Where(Expression.Lambda<Func<T, bool>>(result, new[] {parameterExpression}));
}
return queryable;
}
EDIT : another solution, based on "simple" reflection, which "looks" as the one you want
public static short? GetYearValue<T>(this T instance)
{
var propertyInfo = typeof(T).GetProperties().FirstOrDefault(m => m.CanRead && m.CanWrite && m.PropertyType == typeof(short?));
return propertyInfo.GetValue(instance, null) as short?;
}
usage
var result = list.Where(item => item.GetYearValue() != null && item.GetYearValue() >= 1 && item.GetYearValue() <= 3).ToList();

Using nullable types in Linq expressions

var quantSubset =
from userAns in userAnalysis.AllUserAnswers
join ques in userAnalysis.AllSeenQuestions on userAns.QID equals ques.QID
where (ques.QuestionType == "QT")
select new {
QuestionLevel = ques.LevelID,
TimeTaken = userAns.TimeTaken,
Points = userAns.Points,
UsedWeapon = (userAns.UsedBy2 && userAns.UsedHint),
WasCorrect = userAns.WasCorrect.HasValue ? userAns.WasCorrect.Value : null
};
In my select expression I want to select a nullable type WasCorrect (last part of the expression) but apparently I cannot do it the way I am currently trying.
How can I get WasCorrect as nullable type
I tried ?WasCorrect but that also doesnt gives error in Visual Studio.
You need to cast the null value to the nullable type explicitly:
WasCorrect = userAns.WasCorrect.HasValue ?
userAns.WasCorrect.Value : (TheTypeName?)null
Otherwise C# won’t know which type the conditional expression should be.
Apart from that, the code is completely redundant. You can simply write:
WasCorrect = userAns.WasCorrect
You absolutely must be able to write
select new { WasCorrect = userAns.WasCorrect }
if userAns.WasCorrect is Nullable<bool>.
This code executes without a problem:
class Test {
public bool? NullableBool { get; set;}
}
class MainClass
{
public static void Main ()
{
Test t1 = new Test { NullableBool = true };
var a1 = new { NB = t1.NullableBool };
Test t2 = new Test { NullableBool = null };
var a2 = new { NB = t2.NullableBool };
}
}

Categories

Resources