Why is generated query from expression using case instead of where statements? - entity-framework

I am working on a tool, that translates filter strings into linq/efcore expressions. Say following filter string is given:
{
'condition': 'OR',
'rules': [
{ 'column': 'Foo', 'value': 'Bar' },
{ 'column': 'Foo', 'value': 'Baz' },
]
}
Using an efcore extension I can filter a model like so:
_dbContext.MyModel
.Filter(filterString)
.ToList();
The issue is, that the generated query uses case instead of where statements:
SELECT
*
FROM
[MyModel] AS [c]
WHERE
(
CASE
WHEN [c].[Foo] = "Bar" THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END | CASE
WHEN [c].[Foo] = "Baz" THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END
) = CAST(1 AS bit)
instead of:
SELECT
*
FROM
[MyModel] AS [c]
WHERE ([c].[Foo] = "Bar" OR [c].[Foo] = "Baz")
The last query takes much less time. Why is case used instead of where? Is it possible to instruct the parser to use where?
Extension:
public static IQueryable<T> Filter<T>(this IQueryable<T> query, string? filter)
{
if (string.IsNullOrEmpty(filter))
{
return query;
}
Expression<Func<T, bool>>? predicate;
try
{
predicate = new FilterExpressionParser().ParseExpressionOf<T>(JsonDocument.Parse(filter));
}
catch (Exception ex)
{
throw new FilterException($"Filter \"{filter}\" could not be parsed into a predicate.", ex);
}
return query.Where(predicate);
}
The extension tries to parse the filter string as a json document and create an linq expression from it. The logic for that is inside the FilterExpressionParser:
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
public enum FilterConditionType
{
AND,
OR
}
public class FilterExpressionParser
{
public Expression<Func<T, bool>> ParseExpressionOf<T>(JsonDocument json)
{
var param = Expression.Parameter(typeof(T));
var conditions = ParseTree<T>(json.RootElement, param)!;
if (conditions.CanReduce)
{
conditions = conditions.ReduceAndCheck();
}
return Expression.Lambda<Func<T, bool>>(conditions, param);
}
private delegate Expression Binder(Expression? left, Expression? right);
private Expression? ParseTree<T>(JsonElement condition, ParameterExpression parameterExpression)
{
Expression? left = null;
var conditionString = condition.GetProperty(nameof(condition)).GetString()?.ToUpper();
if (!Enum.TryParse(conditionString, out FilterConditionType gate))
{
throw new ArgumentOutOfRangeException(nameof(condition), $"Not expected condition type: {condition}.");
}
JsonElement rules = condition.GetProperty(nameof(rules));
Binder binder = gate == FilterConditionType.AND ? Expression.And! : Expression.Or!;
Expression? bind(Expression? left, Expression? right) => left == null ? right : binder(left, right);
foreach (var rule in rules.EnumerateArray())
{
string? column = rule.GetProperty(nameof(column)).GetString();
object? toCompare = value.GetString().GetProperty(nameof(value));
Expression property = Expression.Property(parameterExpression, column);
BinaryExpression? right = Expression.Equal(property, Expression.Constant(toCompare, property.Type))
left = bind(left, right);
}
return left;
}
}

For combining predicates you have used bitwise operators Expression.And and Expression.Or Bitwise and shift operators
In C# generated result looks like
e => (e.Some > 1) & (e.Some < 10) | (e.Some == -1)
So, EF is also trying to convert bit operations to the SQL.
Instead of them use Expression.AndAlso and Expression.OrElse which are Boolean logical operators
e => (e.Some > 1) && (e.Some < 10) || (e.Some == -1)
For analysis generated expressions, I would suggest to use ReadableExpressions.Visualizers and probably you will find mistake by yourself.

Related

Inline T-SQL OR subquery

Trying to figure out what would be the most efficient approach to use a subquery in an inline SQL statement. Inline SQL is not something I have much experience with, but I have no choice at my organization alas.
SELECT *
FROM dbo.VW_RMISPayment
WHERE ProcDate BETWEEN '7/2/2018' AND '3/8/2019'
AND Status = 'P'
AND Fund = '359'
AND Amount > 0
AND (BatchNotate = 'B' OR BatchNotate IS NULL)
ORDER BY ProcDate, Amount
How could I parameterized the (BatchNotate = 'B' OR BatchNotate IS NULL) part?
My variable passed in as a List<string>, but I could change it to be anything. I'm just not sure how I can create this subquery from my variable
if (BatchNotate.Count() > 0)
{
query += " AND BatchNotate= #BatchNotate";
}
cmd.Parameters.AddWithValue("#BatchNotate", batchNotate);
Use this:
BatchNotate = COALESCE(#inVariable,'B')
If the variable (#inVariable) is null then it will "default to B.
If it is something else it will compare against that.
Could you do something like this?
SELECT *
FROM dbo.VW_RMISPayment
WHERE ProcDate BETWEEN '7/2/2018' AND '3/8/2019'
AND Status = 'P'
AND Fund = '359'
AND Amount > 0
AND BatchNotate = COALESCE(#BatchNotate, BatchNotate)
ORDER BY ProcDate, Amount
Neither of those worked for what I'm after. Here is what I ended up doing. It's kinda hacky, but it works.
public static string AddParametersOR<T>(SqlParameterCollection parameters,
string fieldName,
string pattern,
SqlDbType parameterType,
int length,
IEnumerable<T> values)
{
if (parameters == null)
throw new ArgumentNullException("parameters");
if (pattern == null)
throw new ArgumentNullException("pattern");
if (values == null)
throw new ArgumentNullException("values");
if (!pattern.StartsWith("#", StringComparison.CurrentCultureIgnoreCase))
throw new ArgumentException("Pattern must start with '#'");
var parameterNames = new List<string>();
foreach (var item in values)
{
var parameterName = parameterNames.Count.ToString(pattern, CultureInfo.InvariantCulture);
string parameterWithFieldName = string.Empty;
if (item.ToString().ToUpper() == "NULL")
{
parameterWithFieldName = string.Format("{0} IS NULL", fieldName);
}
else if (item.ToString().ToUpper() == "NOTNULL")
{
parameterWithFieldName = string.Format("{0} IS NOT", fieldName);
}
else
{
parameterWithFieldName = string.Format("{0}= {1}", fieldName, parameterName);
}
parameterNames.Add(parameterWithFieldName);
parameters.Add(parameterName, parameterType, length).Value = item;
}
return string.Join(" OR ", parameterNames.ToArray());
}
Usage:
if (batchNotate.Count() > 0)
{
query += " AND ({#BatchNotate})";
}
string batchNotateParamNames = SqlHelper.AddParametersOR(cmd.Parameters, "BatchNotate", "#B0", SqlDbType.VarChar, 1, batchNotate);
cmd.CommandText = query.Replace("{#BatchNotate}", batchNotateParamNames);
Depending on how many items are in your list the output will look like this:
(BatchNotate= 'B' OR BatchNotate= 'N' OR BatchNotate IS NULL)
If it finds "NULL" or "NOTNULL" it will replace these with IS NULL or IS NOT NULL

Generic Func needed to perform sorting of Entity Framework collection

I have a grid with columns. When the grid column header is selected I post/ajax to server with header selected to return x rows.
In the following code, RefNo is integer while ProposalSectionNumber is a string.
How to make a generic function taking a string but return a Func to be used in the linq statement?
if (sort.dir == SortDirection.Asc)
{
switch (sort.field)
{
case "RefNo":
qry = qry.OrderBy(x => x.RefNo);
break;
case "ProposalSectionNumber":
qry = qry.OrderBy(x => x.ProposalSectionNumber);
break;
}
}
else
{
switch (sort.field)
{
case "RefNo":
qry = qry.OrderByDescending(x => x.RefNo);
break;
case "ProposalSectionNumber":
qry = qry.OrderByDescending(x => x.ProposalSectionNumber);
break;
}
}
I would like to do something like
string sortOrder = "RefNo"
var sortfunc = SortFunc(sortOrder)
if (sort.dir == SortDirection.Asc)
{
qry = qry.OrderBy(sortFunc)
}
else
{
qry = qry.OrderByDesc(sortFunc)
}
I have struggled creating the function SortFunc (which returns based on string or integer)
What is the best way to achieve this?
The problem with declaring a type for sortFunc is that it depends on the type of the field by which you sort. If all fields were of the same type, say, all strings, you could use the type of Expression<Func<MyEntity,string>> for your sortFunc variable.
There is another way of removing code duplication when sort fields do not share a common type. Introduce a generic helper method that takes sort order as a parameter, and call it instead of OrderBy/OrderByDescending:
private static IOrderedQueryable<T> AddOrderBy<T,TKey>(
IQueryable<T> orig
, Expression<Func<T,TKey>> selector
, bool isAscending
) {
return isAscending ? orig.OrderBy(selector) : orig.OrderByDescending(selector);
}
Now you can rewrite your code as follows:
var isAscending = (sort.dir == SortDirection.Asc);
switch (sort.field) {
case "RefNo":
qry = qry.AddOrderBy(x => x.RefNo, isAscending);
break;
case "ProposalSectionNumber":
qry = qry.AddOrderBy(x => x.ProposalSectionNumber, isAscending);
break;
}

Sort using Expression builder

I am trying to build generic sort method using Expressions.
I came up with the following method. For some reasons the code is breaking at the statement when a nested property is used in the sort expression.
var sortExpression = Expression.Lambda<Func<T, object>>
(Expression.Convert(Expression.Property(sortParam, sortColumn), typeof(object)), sortParam);
private static IQueryable<T> SortQuery<T>(IQueryable<T> query, string sortColumn)
{
if (!string.IsNullOrEmpty(sortColumn))
{
var sortParam = Expression.Parameter(typeof(T), "x");
Expression expr = sortParam;
foreach (var prop in sortColumn.Split('.'))
{
expr = Expression.PropertyOrField(expr, prop);
}
var sortExpression = Expression.Lambda<Func<T, object>>
(Expression.Convert(Expression.Property(sortParam, sortColumn), typeof(object)), sortParam);
return query.OrderBy(sortExpression);
}
return null;
}
Any idea where I am doing it wrong ?
Try:
private static readonly MethodInfo OrderBy = (from x in typeof(Queryable).GetMethods()
where x.Name == "OrderBy"
let pars = x.GetParameters()
where pars.Length == 2
select x).Single();
private static IQueryable<T> SortQuery<T>(IQueryable<T> query, string sortColumn)
{
if (!string.IsNullOrEmpty(sortColumn))
{
var sortParam = Expression.Parameter(typeof(T), "x");
Expression expr = sortParam;
foreach (var prop in sortColumn.Split('.'))
{
expr = Expression.PropertyOrField(expr, prop);
}
var lambda = Expression.Lambda(expr, sortParam);
var orderBy = OrderBy.MakeGenericMethod(typeof(T), expr.Type);
return (IQueryable<T>)orderBy.Invoke(null, new object[] { query, lambda });
}
return null;
}
You have to use reflection to invoke the Queryable.OrderBy. In general, casting to object doesn't work very well with Entity Framework/LINQ to SQL, so you can try it, but there is only a very little chance it will work.

best practice on conditional queries with linq to entities

I often find myself writing querys like this:
var voyages = db.VoyageRequests.Include("Carrier")
.Where(u => (fromDate.HasValue ? u.date >= fromDate.Value : true) &&
(toDate.HasValue ? u.date <= toDate.Value : true) &&
u.Carrier != null &&
u.status == (int)VoyageStatus.State.InProgress)
.OrderBy(u => u.date);
return voyages;
With conditionals inside the where statement:
fromDate.HasValue ? u.date >= fromDate.Value : true
I know the other way to do it'll be like:
var voyages = db.VoyageRequests.Include("Carrier").Where(u => u.Carrier != null &&
u.status == (int)VoyageStatus.State.InProgress);
if (fromDate.HasValue)
{
voyages = voyages.Where(u => u.date >= fromDate.Value);
}
if (toDate.HasValue)
{
voyages = voyages.Where(u => u.date <= toDate.Value);
}
return voyages.OrderBy(u => u.date);
Is there any real difference that may affect performance when this 2 approaches get transform to SQL expression?
The second query will create the simpler SQL because the evaluation of fromDate.HasValue and toDate.HasValue happens on the client. In the first query the ternary operators get evaluated on the database server as part of the SQL query. Both fromDate and toDate will be transmitted as constants to the server while in the second query only then if .HasValue is true.
We are talking about a few bytes more in length of the SQL statement and I don't believe that the server-side evaluation of the ternaries has any significant effect on query performance.
I would choose what you find more readable. Personally I would decide for the second query.
If you want the simple SQL and the more readable C# you can create an Extension as suggested by Viktor Mitev http://mentormate.com/blog/improving-linq-to-entities-queries/
public static class WhereExtensions
{
// Where extension for filters of any nullable type
public static IQueryable<TSource> Where<Tsource, TFilter>
(
this IQueryable <TSource> source,
Nullable <TFilter> filter,
Expression<Func<TSource, bool>> predicate
) where TFilter : struct
{
if (filter.HasValue)
{
source = source.Where(predicate);
}
return source;
}
// Where extension for string filters
public static IQueryable<TSource> Where<TSource>
(
this IQueryable<TSource> source,
string filter,
Expression<Func<TSource, bool>> predicate
)
{
if (!string.IsNullOrWhiteSpace(filter))
{
source = source.Where(predicate);
}
return source;
}
// Where extension for collection filters
public static IQueryable<TSource> Where<TSource, TFilter>
(
this IQueryable<TSource> source,
IEnumerable<TFilter> filter,
Expression<Func<TSource, bool>> predicate
)
{
if (filter != null && filter.Any())
{
source = source.Where(predicate);
}
return source;
}
Then your "secound query" will look like this:
var voyages = db.VoyageRequests.Include("Carrier")
.Where(u => u.Carrier != null && u.status == (int)VoyageStatus.State.InProgress)
.Where(u => u.date >= fromDate)
.Where(u => u.date <= toDate)
.OrderBy(u => u.date);
I don't know if it is more readable or if it will be confusing for some developers because it is more difficult to read directly form the code what part of the filtering is in use.
Maybe it will be more readable if you name the extensions function something like WhereIfFilterNotNull (or something meaningful :-)
var voyages = db.VoyageRequests.Include("Carrier")
.Where(u => u.Carrier != null && u.status == (int)VoyageStatus.State.InProgress)
.WhereIfFilterNotNull(u => u.date >= fromDate)
.WhereIfFilterNotNull(u => u.date <= toDate)
.OrderBy(u => u.date);

How to custom the translation from ESQL to T-SQL in Entity Framework v1.0

As EF v1 does not support "IN", I wonder if whether there is a way to extend the entity famework, so that we could support "IN" operator in EF v1.0.
Here's the solution provided in MSDN forums.
static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
{
if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
if (null == values) { throw new ArgumentNullException("values"); }
ParameterExpression p = valueSelector.Parameters.Single();
// p => valueSelector(p) == values[0] || valueSelector(p) == ...
if (!values.Any())
{
return e => false;
}
var equals = values.Select(value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));
return Expression.Lambda<Func<TElement, bool>>(body, p);
}
Please go through the thread for detailed answer