How to convert an expression tree to a partial SQL query? - c#

When EF or LINQ to SQL runs a query, it:
Builds an expression tree from the code,
Converts the expression tree into an SQL query,
Executes the query, gets the raw results from the database and converts them to the result to be used by the application.
Looking at the stack trace, I can't figure out where the second part happens.
In general, is it possible to use an existent part of EF or (preferably) LINQ to SQL to convert an Expression object to a partial SQL query (using Transact-SQL syntax), or I have to reinvent the wheel?
Update: a comment asks to provide an example of what I'm trying to do.
Actually, the answer by Ryan Wright below illustrates perfectly what I want to achieve as a result, except the fact that my question is specifically about how can I do it by using existent mechanisms of .NET Framework actually used by EF and LINQ to SQL, instead of having to reinvent the wheel and write thousands of lines of not-so-tested code myself to do the similar thing.
Here is also an example. Again, note that there is no ORM-generated code.
private class Product
{
[DatabaseMapping("ProductId")]
public int Id { get; set; }
[DatabaseMapping("Price")]
public int PriceInCents { get; set; }
}
private string Convert(Expression expression)
{
// Some magic calls to .NET Framework code happen here.
// [...]
}
private void TestConvert()
{
Expression<Func<Product, int, int, bool>> inPriceRange =
(Product product, int from, int to) =>
product.PriceInCents >= from && product.PriceInCents <= to;
string actualQueryPart = this.Convert(inPriceRange);
Assert.AreEqual("[Price] between #from and #to", actualQueryPart);
}
Where does the name Price come from in the expected query?
The name can be obtained through reflection by querying the custom DatabaseMapping attribute of Price property of Product class.
Where do names #from and #to come from in the expected query?
Those names are the actual names of the parameters of the expression.
Where does between … and come from in the expected query?
This is a possible result of a binary expression. Maybe EF or LINQ to SQL would, instead of between … and statement, stick with [Price] >= #from and [Price] <= #to instead. It's ok too, it doesn't really matter since the result is logically the same (I'm not mentioning performance).
Why there is no where in the expected query?
Because nothing indicates in the Expression that there must be a where keyword. Maybe the actual expression is just one of the expressions which would be combined later with binary operators to build a larger query to prepend with a where.

Yes it is possible, you can parse a LINQ expression tree using the visitor pattern. You would need to construct a query translator by subclassing ExpressionVisitor like below. By hooking into the correct points you can use the translator to construct your SQL string from your LINQ expression. Note that the code below only deals with basic where/orderby/skip/take clauses, but you can fill it out with more as needed. Hopefully it serves as a good first step.
public class MyQueryTranslator : ExpressionVisitor
{
private StringBuilder sb;
private string _orderBy = string.Empty;
private int? _skip = null;
private int? _take = null;
private string _whereClause = string.Empty;
public int? Skip
{
get
{
return _skip;
}
}
public int? Take
{
get
{
return _take;
}
}
public string OrderBy
{
get
{
return _orderBy;
}
}
public string WhereClause
{
get
{
return _whereClause;
}
}
public MyQueryTranslator()
{
}
public string Translate(Expression expression)
{
this.sb = new StringBuilder();
this.Visit(expression);
_whereClause = this.sb.ToString();
return _whereClause;
}
private static Expression StripQuotes(Expression e)
{
while (e.NodeType == ExpressionType.Quote)
{
e = ((UnaryExpression)e).Operand;
}
return e;
}
protected override Expression VisitMethodCall(MethodCallExpression m)
{
if (m.Method.DeclaringType == typeof(Queryable) && m.Method.Name == "Where")
{
this.Visit(m.Arguments[0]);
LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
this.Visit(lambda.Body);
return m;
}
else if (m.Method.Name == "Take")
{
if (this.ParseTakeExpression(m))
{
Expression nextExpression = m.Arguments[0];
return this.Visit(nextExpression);
}
}
else if (m.Method.Name == "Skip")
{
if (this.ParseSkipExpression(m))
{
Expression nextExpression = m.Arguments[0];
return this.Visit(nextExpression);
}
}
else if (m.Method.Name == "OrderBy")
{
if (this.ParseOrderByExpression(m, "ASC"))
{
Expression nextExpression = m.Arguments[0];
return this.Visit(nextExpression);
}
}
else if (m.Method.Name == "OrderByDescending")
{
if (this.ParseOrderByExpression(m, "DESC"))
{
Expression nextExpression = m.Arguments[0];
return this.Visit(nextExpression);
}
}
throw new NotSupportedException(string.Format("The method '{0}' is not supported", m.Method.Name));
}
protected override Expression VisitUnary(UnaryExpression u)
{
switch (u.NodeType)
{
case ExpressionType.Not:
sb.Append(" NOT ");
this.Visit(u.Operand);
break;
case ExpressionType.Convert:
this.Visit(u.Operand);
break;
default:
throw new NotSupportedException(string.Format("The unary operator '{0}' is not supported", u.NodeType));
}
return u;
}
/// <summary>
///
/// </summary>
/// <param name="b"></param>
/// <returns></returns>
protected override Expression VisitBinary(BinaryExpression b)
{
sb.Append("(");
this.Visit(b.Left);
switch (b.NodeType)
{
case ExpressionType.And:
sb.Append(" AND ");
break;
case ExpressionType.AndAlso:
sb.Append(" AND ");
break;
case ExpressionType.Or:
sb.Append(" OR ");
break;
case ExpressionType.OrElse:
sb.Append(" OR ");
break;
case ExpressionType.Equal:
if (IsNullConstant(b.Right))
{
sb.Append(" IS ");
}
else
{
sb.Append(" = ");
}
break;
case ExpressionType.NotEqual:
if (IsNullConstant(b.Right))
{
sb.Append(" IS NOT ");
}
else
{
sb.Append(" <> ");
}
break;
case ExpressionType.LessThan:
sb.Append(" < ");
break;
case ExpressionType.LessThanOrEqual:
sb.Append(" <= ");
break;
case ExpressionType.GreaterThan:
sb.Append(" > ");
break;
case ExpressionType.GreaterThanOrEqual:
sb.Append(" >= ");
break;
default:
throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType));
}
this.Visit(b.Right);
sb.Append(")");
return b;
}
protected override Expression VisitConstant(ConstantExpression c)
{
IQueryable q = c.Value as IQueryable;
if (q == null && c.Value == null)
{
sb.Append("NULL");
}
else if (q == null)
{
switch (Type.GetTypeCode(c.Value.GetType()))
{
case TypeCode.Boolean:
sb.Append(((bool)c.Value) ? 1 : 0);
break;
case TypeCode.String:
sb.Append("'");
sb.Append(c.Value);
sb.Append("'");
break;
case TypeCode.DateTime:
sb.Append("'");
sb.Append(c.Value);
sb.Append("'");
break;
case TypeCode.Object:
throw new NotSupportedException(string.Format("The constant for '{0}' is not supported", c.Value));
default:
sb.Append(c.Value);
break;
}
}
return c;
}
protected override Expression VisitMember(MemberExpression m)
{
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter)
{
sb.Append(m.Member.Name);
return m;
}
throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));
}
protected bool IsNullConstant(Expression exp)
{
return (exp.NodeType == ExpressionType.Constant && ((ConstantExpression)exp).Value == null);
}
private bool ParseOrderByExpression(MethodCallExpression expression, string order)
{
UnaryExpression unary = (UnaryExpression)expression.Arguments[1];
LambdaExpression lambdaExpression = (LambdaExpression)unary.Operand;
lambdaExpression = (LambdaExpression)Evaluator.PartialEval(lambdaExpression);
MemberExpression body = lambdaExpression.Body as MemberExpression;
if (body != null)
{
if (string.IsNullOrEmpty(_orderBy))
{
_orderBy = string.Format("{0} {1}", body.Member.Name, order);
}
else
{
_orderBy = string.Format("{0}, {1} {2}", _orderBy, body.Member.Name, order);
}
return true;
}
return false;
}
private bool ParseTakeExpression(MethodCallExpression expression)
{
ConstantExpression sizeExpression = (ConstantExpression)expression.Arguments[1];
int size;
if (int.TryParse(sizeExpression.Value.ToString(), out size))
{
_take = size;
return true;
}
return false;
}
private bool ParseSkipExpression(MethodCallExpression expression)
{
ConstantExpression sizeExpression = (ConstantExpression)expression.Arguments[1];
int size;
if (int.TryParse(sizeExpression.Value.ToString(), out size))
{
_skip = size;
return true;
}
return false;
}
}
Then visit the expression by calling:
var translator = new MyQueryTranslator();
string whereClause = translator.Translate(expression);

The short answer seems to be that you cannot use a part of EF or LINQ to SQL as a shortcut to translation. You need at least a subclass of ObjectContext to get at the internal protected QueryProvider property, and that means all the overhead of creating the context, including all the metadata and so on.
Assuming you are ok with that, to get a partial SQL query, for example, just the WHERE clause you're basically going to need the query provider and call IQueryProvider.CreateQuery() just as LINQ does in its implementation of Queryable.Where. To get a more complete query you can use ObjectQuery.ToTraceString().
As to where this happens, LINQ provider basics states generally that
IQueryProvider returns a reference to IQueryable with the constructed expression-tree passed by the LINQ framework, which is used for further calls. In general terms, each query block is converted to a bunch of method calls. For each method call, there are some expressions involved. While creating our provider - in the method IQueryProvider.CreateQuery - we run through the expressions and fill up a filter object, which is used in the IQueryProvider.Execute method to run a query against the data store
and that
the query can be executed in two ways, either by implementing the GetEnumerator method (defined in the IEnumerable interface) in the Query class, (which inherits from IQueryable); or it can be executed by the LINQ runtime directly
Checking EF under the debugger it's the former.
If you don't want to completely re-invent the wheel and neither EF nor LINQ to SQL are options, perhaps this series of articles would help:
How to: LINQ to SQL Translation
How to: LINQ to SQL Translation - Part II
How to: LINQ to SQL Translation - Part III
Here are some sources for creating a query provider that probably involve much more heavy lifting on your part to implement what you want:
LINQ: Building an IQueryable provider series
Creating custom LINQ provider using LinqExtender

It isn't complete, but here are some thoughts for you to groove on if you come by this later:
private string CreateWhereClause(Expression<Func<T, bool>> predicate)
{
StringBuilder p = new StringBuilder(predicate.Body.ToString());
var pName = predicate.Parameters.First();
p.Replace(pName.Name + ".", "");
p.Replace("==", "=");
p.Replace("AndAlso", "and");
p.Replace("OrElse", "or");
p.Replace("\"", "\'");
return p.ToString();
}
private string AddWhereToSelectCommand(Expression<Func<T, bool>> predicate, int maxCount = 0)
{
string command = string.Format("{0} where {1}", CreateSelectCommand(maxCount), CreateWhereClause(predicate));
return command;
}
private string CreateSelectCommand(int maxCount = 0)
{
string selectMax = maxCount > 0 ? "TOP " + maxCount.ToString() + " * " : "*";
string command = string.Format("Select {0} from {1}", selectMax, _tableName);
return command;
}

In Linq2SQL you can use:
var cmd = DataContext.GetCommand(expression);
var sqlQuery = cmd.CommandText;

After searching for hours for an implementation of an Expression tree to SQL converter, I did not found anything usefull or free or somehow working with .NET Core.
Then I found this. Thank you Ryan Wright.
I took his code and modified it a bit to fit my needs. Now I am giving it back to the community.
Current version can do the following:
Bulk update
int rowCount = context
.Users
.Where(x => x.Status == UserStatus.Banned)
.Update(x => new
{
DisplayName = "Bad Guy"
});
This will produce the following sql
DECLARE #p0 NVarChar
DECLARE #p1 Int
SET #p0 = 'Bad Guy'
SET #p1 = 3
UPDATE [Users]
SET [DisplayName] = #p0
WHERE ( [Status] = #p1 )
Bulk delete
int rowCount = context
.Users
.Where(x => x.UniqueName.EndsWith("012"))
.Delete();
The produced sql
DECLARE #p0 NVarChar
SET #p0 = '%012'
DELETE
FROM [Users]
WHERE [UniqueName] LIKE #p0
Outputing SQL Statements
string sql = context
.Users
.Where(x => x.Status == UserStatus.LockedOut)
.OrderBy(x => x.UniqueName)
.ThenByDescending(x => x.LastLogin)
.Select(x => new
{
x.UniqueName,
x.Email
})
.ToSqlString();
This produces the sql
DECLARE #p0 Int
SET #p0 = 4
SELECT [UniqueName], [Email]
FROM [Users]
WHERE ( [Status] = #p0 )
ORDER BY [LastLogin] DESC, [UniqueName] ASC
Another example
string sql = context
.Users
.Where(x => x.Status == UserStatus.LockedOut)
.OrderBy(x => x.UniqueName)
.ThenByDescending(x => x.LastLogin)
.Select(x => new
{
x.UniqueName,
x.Email,
x.LastLogin
})
.Take(4)
.Skip(3)
.Distinct()
.ToSqlString();
The sql
DECLARE #p0 Int
SET #p0 = 4
SELECT DISTINCT [UniqueName], [Email], [LastLogin]
FROM [Users]
WHERE ( [Status] = #p0 )
ORDER BY [LastLogin] DESC, [UniqueName] ASC OFFSET 3 ROWS FETCH NEXT 4 ROWS ONLY
Another example with a local variable
string name ="venom";
string sql = context
.Users
.Where(x => x.LastLogin == DateTime.UtcNow && x.UniqueName.Contains(name))
.Select(x => x.Email)
.ToSqlString();
The produced sql
DECLARE #p0 DateTime
DECLARE #p1 NVarChar
SET #p0 = '20.06.2020 19:23:46'
SET #p1 = '%venom%'
SELECT [Email]
FROM [Users]
WHERE ( ( [LastLogin] = #p0 ) AND [UniqueName] LIKE #p1 )
The SimpleExpressionToSQL class itself can be used directly
var simpleExpressionToSQL = new SimpleExpressionToSQL(queryable);
simpleExpressionToSQL.ExecuteNonQuery(IsolationLevel.Snapshot);
The code
The evaluator used here come from here
SimpleExpressionToSQL
public class SimpleExpressionToSQL : ExpressionVisitor
{
/*
* Original By Ryan Wright: https://stackoverflow.com/questions/7731905/how-to-convert-an-expression-tree-to-a-partial-sql-query
*/
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly List<string> _groupBy = new List<string>();
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly List<string> _orderBy = new List<string>();
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly List<SqlParameter> _parameters = new List<SqlParameter>();
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly List<string> _select = new List<string>();
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly List<string> _update = new List<string>();
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly List<string> _where = new List<string>();
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private int? _skip;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private int? _take;
public SimpleExpressionToSQL(IQueryable queryable)
{
if (queryable is null)
{
throw new ArgumentNullException(nameof(queryable));
}
Expression expression = queryable.Expression;
Visit(expression);
Type entityType = (GetEntityType(expression) as IQueryable).ElementType;
TableName = queryable.GetTableName(entityType);
DbContext = queryable.GetDbContext();
}
public string CommandText => BuildSqlStatement().Join(Environment.NewLine);
public DbContext DbContext { get; private set; }
public string From => $"FROM [{TableName}]";
public string GroupBy => _groupBy.Count == 0 ? null : "GROUP BY " + _groupBy.Join(", ");
public bool IsDelete { get; private set; } = false;
public bool IsDistinct { get; private set; }
public string OrderBy => BuildOrderByStatement().Join(" ");
public SqlParameter[] Parameters => _parameters.ToArray();
public string Select => BuildSelectStatement().Join(" ");
public int? Skip => _skip;
public string TableName { get; private set; }
public int? Take => _take;
public string Update => "SET " + _update.Join(", ");
public string Where => _where.Count == 0 ? null : "WHERE " + _where.Join(" ");
public static implicit operator string(SimpleExpressionToSQL simpleExpression) => simpleExpression.ToString();
public int ExecuteNonQuery(IsolationLevel isolationLevel = IsolationLevel.RepeatableRead)
{
DbConnection connection = DbContext.Database.GetDbConnection();
using (DbCommand command = connection.CreateCommand())
{
command.CommandText = CommandText;
command.CommandType = CommandType.Text;
command.Parameters.AddRange(Parameters);
#if DEBUG
Debug.WriteLine(ToString());
#endif
if (command.Connection.State != ConnectionState.Open)
command.Connection.Open();
using (DbTransaction transaction = connection.BeginTransaction(isolationLevel))
{
command.Transaction = transaction;
int result = command.ExecuteNonQuery();
transaction.Commit();
return result;
}
}
}
public async Task<int> ExecuteNonQueryAsync(IsolationLevel isolationLevel = IsolationLevel.RepeatableRead)
{
DbConnection connection = DbContext.Database.GetDbConnection();
using (DbCommand command = connection.CreateCommand())
{
command.CommandText = CommandText;
command.CommandType = CommandType.Text;
command.Parameters.AddRange(Parameters);
#if DEBUG
Debug.WriteLine(ToString());
#endif
if (command.Connection.State != ConnectionState.Open)
await command.Connection.OpenAsync();
using (DbTransaction transaction = connection.BeginTransaction(isolationLevel))
{
command.Transaction = transaction;
int result = await command.ExecuteNonQueryAsync();
transaction.Commit();
return result;
}
}
}
public override string ToString() =>
BuildDeclaration()
.Union(BuildSqlStatement())
.Join(Environment.NewLine);
protected override Expression VisitBinary(BinaryExpression binaryExpression)
{
_where.Add("(");
Visit(binaryExpression.Left);
switch (binaryExpression.NodeType)
{
case ExpressionType.And:
_where.Add("AND");
break;
case ExpressionType.AndAlso:
_where.Add("AND");
break;
case ExpressionType.Or:
case ExpressionType.OrElse:
_where.Add("OR");
break;
case ExpressionType.Equal:
if (IsNullConstant(binaryExpression.Right))
{
_where.Add("IS");
}
else
{
_where.Add("=");
}
break;
case ExpressionType.NotEqual:
if (IsNullConstant(binaryExpression.Right))
{
_where.Add("IS NOT");
}
else
{
_where.Add("<>");
}
break;
case ExpressionType.LessThan:
_where.Add("<");
break;
case ExpressionType.LessThanOrEqual:
_where.Add("<=");
break;
case ExpressionType.GreaterThan:
_where.Add(">");
break;
case ExpressionType.GreaterThanOrEqual:
_where.Add(">=");
break;
default:
throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", binaryExpression.NodeType));
}
Visit(binaryExpression.Right);
_where.Add(")");
return binaryExpression;
}
protected override Expression VisitConstant(ConstantExpression constantExpression)
{
switch (constantExpression.Value)
{
case null when constantExpression.Value == null:
_where.Add("NULL");
break;
default:
if (constantExpression.Type.CanConvertToSqlDbType())
{
_where.Add(CreateParameter(constantExpression.Value).ParameterName);
}
break;
}
return constantExpression;
}
protected override Expression VisitMember(MemberExpression memberExpression)
{
Expression VisitMemberLocal(Expression expression)
{
switch (expression.NodeType)
{
case ExpressionType.Parameter:
_where.Add($"[{memberExpression.Member.Name}]");
return memberExpression;
case ExpressionType.Constant:
_where.Add(CreateParameter(GetValue(memberExpression)).ParameterName);
return memberExpression;
case ExpressionType.MemberAccess:
_where.Add(CreateParameter(GetValue(memberExpression)).ParameterName);
return memberExpression;
}
throw new NotSupportedException(string.Format("The member '{0}' is not supported", memberExpression.Member.Name));
}
if (memberExpression.Expression == null)
{
return VisitMemberLocal(memberExpression);
}
return VisitMemberLocal(memberExpression.Expression);
}
protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
{
switch (methodCallExpression.Method.Name)
{
case nameof(Queryable.Where) when methodCallExpression.Method.DeclaringType == typeof(Queryable):
Visit(methodCallExpression.Arguments[0]);
var lambda = (LambdaExpression)StripQuotes(methodCallExpression.Arguments[1]);
Visit(lambda.Body);
return methodCallExpression;
case nameof(Queryable.Select):
return ParseExpression(methodCallExpression, _select);
case nameof(Queryable.GroupBy):
return ParseExpression(methodCallExpression, _groupBy);
case nameof(Queryable.Take):
return ParseExpression(methodCallExpression, ref _take);
case nameof(Queryable.Skip):
return ParseExpression(methodCallExpression, ref _skip);
case nameof(Queryable.OrderBy):
case nameof(Queryable.ThenBy):
return ParseExpression(methodCallExpression, _orderBy, "ASC");
case nameof(Queryable.OrderByDescending):
case nameof(Queryable.ThenByDescending):
return ParseExpression(methodCallExpression, _orderBy, "DESC");
case nameof(Queryable.Distinct):
IsDistinct = true;
return Visit(methodCallExpression.Arguments[0]);
case nameof(string.StartsWith):
_where.AddRange(ParseExpression(methodCallExpression, methodCallExpression.Object));
_where.Add("LIKE");
_where.Add(CreateParameter(GetValue(methodCallExpression.Arguments[0]).ToString() + "%").ParameterName);
return methodCallExpression.Arguments[0];
case nameof(string.EndsWith):
_where.AddRange(ParseExpression(methodCallExpression, methodCallExpression.Object));
_where.Add("LIKE");
_where.Add(CreateParameter("%" + GetValue(methodCallExpression.Arguments[0]).ToString()).ParameterName);
return methodCallExpression.Arguments[0];
case nameof(string.Contains):
_where.AddRange(ParseExpression(methodCallExpression, methodCallExpression.Object));
_where.Add("LIKE");
_where.Add(CreateParameter("%" + GetValue(methodCallExpression.Arguments[0]).ToString() + "%").ParameterName);
return methodCallExpression.Arguments[0];
case nameof(Extensions.ToSqlString):
return Visit(methodCallExpression.Arguments[0]);
case nameof(Extensions.Delete):
case nameof(Extensions.DeleteAsync):
IsDelete = true;
return Visit(methodCallExpression.Arguments[0]);
case nameof(Extensions.Update):
return ParseExpression(methodCallExpression, _update);
default:
if (methodCallExpression.Object != null)
{
_where.Add(CreateParameter(GetValue(methodCallExpression)).ParameterName);
return methodCallExpression;
}
break;
}
throw new NotSupportedException($"The method '{methodCallExpression.Method.Name}' is not supported");
}
protected override Expression VisitUnary(UnaryExpression unaryExpression)
{
switch (unaryExpression.NodeType)
{
case ExpressionType.Not:
_where.Add("NOT");
Visit(unaryExpression.Operand);
break;
case ExpressionType.Convert:
Visit(unaryExpression.Operand);
break;
default:
throw new NotSupportedException($"The unary operator '{unaryExpression.NodeType}' is not supported");
}
return unaryExpression;
}
private static Expression StripQuotes(Expression expression)
{
while (expression.NodeType == ExpressionType.Quote)
{
expression = ((UnaryExpression)expression).Operand;
}
return expression;
}
[SuppressMessage("Style", "IDE0011:Add braces", Justification = "Easier to read")]
private IEnumerable<string> BuildDeclaration()
{
if (Parameters.Length == 0) /**/ yield break;
foreach (SqlParameter parameter in Parameters) /**/ yield return $"DECLARE {parameter.ParameterName} {parameter.SqlDbType}";
foreach (SqlParameter parameter in Parameters) /**/
if (parameter.SqlDbType.RequiresQuotes()) /**/ yield return $"SET {parameter.ParameterName} = '{parameter.SqlValue?.ToString().Replace("'", "''") ?? "NULL"}'";
else /**/ yield return $"SET {parameter.ParameterName} = {parameter.SqlValue}";
}
[SuppressMessage("Style", "IDE0011:Add braces", Justification = "Easier to read")]
private IEnumerable<string> BuildOrderByStatement()
{
if (Skip.HasValue && _orderBy.Count == 0) /**/ yield return "ORDER BY (SELECT NULL)";
else if (_orderBy.Count == 0) /**/ yield break;
else if (_groupBy.Count > 0 && _orderBy[0].StartsWith("[Key]")) /**/ yield return "ORDER BY " + _groupBy.Join(", ");
else /**/ yield return "ORDER BY " + _orderBy.Join(", ");
if (Skip.HasValue && Take.HasValue) /**/ yield return $"OFFSET {Skip} ROWS FETCH NEXT {Take} ROWS ONLY";
else if (Skip.HasValue && !Take.HasValue) /**/ yield return $"OFFSET {Skip} ROWS";
}
[SuppressMessage("Style", "IDE0011:Add braces", Justification = "Easier to read")]
private IEnumerable<string> BuildSelectStatement()
{
yield return "SELECT";
if (IsDistinct) /**/ yield return "DISTINCT";
if (Take.HasValue && !Skip.HasValue) /**/ yield return $"TOP ({Take.Value})";
if (_select.Count == 0 && _groupBy.Count > 0) /**/ yield return _groupBy.Select(x => $"MAX({x})").Join(", ");
else if (_select.Count == 0) /**/ yield return "*";
else /**/ yield return _select.Join(", ");
}
[SuppressMessage("Style", "IDE0011:Add braces", Justification = "Easier to read")]
private IEnumerable<string> BuildSqlStatement()
{
if (IsDelete) /**/ yield return "DELETE";
else if (_update.Count > 0) /**/ yield return $"UPDATE [{TableName}]";
else /**/ yield return Select;
if (_update.Count == 0) /**/ yield return From;
else if (_update.Count > 0) /**/ yield return Update;
if (Where != null) /**/ yield return Where;
if (GroupBy != null) /**/ yield return GroupBy;
if (OrderBy != null) /**/ yield return OrderBy;
}
private SqlParameter CreateParameter(object value)
{
string parameterName = $"#p{_parameters.Count}";
var parameter = new SqlParameter()
{
ParameterName = parameterName,
Value = value
};
_parameters.Add(parameter);
return parameter;
}
private object GetEntityType(Expression expression)
{
while (true)
{
switch (expression)
{
case ConstantExpression constantExpression:
return constantExpression.Value;
case MethodCallExpression methodCallExpression:
expression = methodCallExpression.Arguments[0];
continue;
default:
return null;
}
}
}
private IEnumerable<string> GetNewExpressionString(NewExpression newExpression, string appendString = null)
{
for (int i = 0; i < newExpression.Members.Count; i++)
{
if (newExpression.Arguments[i].NodeType == ExpressionType.MemberAccess)
{
yield return
appendString == null ?
$"[{newExpression.Members[i].Name}]" :
$"[{newExpression.Members[i].Name}] {appendString}";
}
else
{
yield return
appendString == null ?
$"[{newExpression.Members[i].Name}] = {CreateParameter(GetValue(newExpression.Arguments[i])).ParameterName}" :
$"[{newExpression.Members[i].Name}] = {CreateParameter(GetValue(newExpression.Arguments[i])).ParameterName}";
}
}
}
private object GetValue(Expression expression)
{
object GetMemberValue(MemberInfo memberInfo, object container = null)
{
switch (memberInfo)
{
case FieldInfo fieldInfo:
return fieldInfo.GetValue(container);
case PropertyInfo propertyInfo:
return propertyInfo.GetValue(container);
default: return null;
}
}
switch (expression)
{
case ConstantExpression constantExpression:
return constantExpression.Value;
case MemberExpression memberExpression when memberExpression.Expression is ConstantExpression constantExpression:
return GetMemberValue(memberExpression.Member, constantExpression.Value);
case MemberExpression memberExpression when memberExpression.Expression is null: // static
return GetMemberValue(memberExpression.Member);
case MethodCallExpression methodCallExpression:
return Expression.Lambda(methodCallExpression).Compile().DynamicInvoke();
case null:
return null;
}
throw new NotSupportedException();
}
private bool IsNullConstant(Expression expression) => expression.NodeType == ExpressionType.Constant && ((ConstantExpression)expression).Value == null;
private IEnumerable<string> ParseExpression(Expression parent, Expression body, string appendString = null)
{
switch (body)
{
case MemberExpression memberExpression:
return appendString == null ?
new string[] { $"[{memberExpression.Member.Name}]" } :
new string[] { $"[{memberExpression.Member.Name}] {appendString}" };
case NewExpression newExpression:
return GetNewExpressionString(newExpression, appendString);
case ParameterExpression parameterExpression when parent is LambdaExpression lambdaExpression && lambdaExpression.ReturnType == parameterExpression.Type:
return new string[0];
case ConstantExpression constantExpression:
return constantExpression
.Type
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Select(x => $"[{x.Name}] = {CreateParameter(x.GetValue(constantExpression.Value)).ParameterName}");
}
throw new NotSupportedException();
}
private Expression ParseExpression(MethodCallExpression expression, List<string> commandList, string appendString = null)
{
var unary = (UnaryExpression)expression.Arguments[1];
var lambdaExpression = (LambdaExpression)unary.Operand;
lambdaExpression = (LambdaExpression)Evaluator.PartialEval(lambdaExpression);
commandList.AddRange(ParseExpression(lambdaExpression, lambdaExpression.Body, appendString));
return Visit(expression.Arguments[0]);
}
private Expression ParseExpression(MethodCallExpression expression, ref int? size)
{
var sizeExpression = (ConstantExpression)expression.Arguments[1];
if (int.TryParse(sizeExpression.Value.ToString(), out int value))
{
size = value;
return Visit(expression.Arguments[0]);
}
throw new NotSupportedException();
}
}
I will post the extension in the comments
Edit: too long for the comment... I'll add another answer.
Use it with caution on production
Feel free to make a Nuget package out of it :)

You basically have to re-invent the wheel. The QueryProvider is the thing that does the translation from expression trees to it's store native syntax. It's the thing that's going to handle special situations as well like string.Contains(), string.StartsWith(), and all the specialty functions that handle it. It's also going to handle metadata lookups in the various layers of your ORM (*.edml in the case of database-first or model-first Entity Framework). There are already examples and frameworks for building out SQL commands. But what you're looking for sounds like a partial solution.
Also understand that table/view metadata is required to correctly determine what is legal. The query providers are quite complex and do a lot of work for you beyond making simple expression tree conversions into SQL.
In response to your where does the second part happen. The second part happens during enumeration of the IQueryable. IQueryables are also IEnumerables and ultimately when GetEnumerator is called it in turn is going to call the query provider with the expression tree which is going to use its metadata to produce a sql command. It's not exactly what happens, but it should get the idea accross.

You can use the following code:
var query = from c in Customers
select c;
string sql = ((ObjectQuery)query).ToTraceString();
Have a look at the following information: Retrieving the SQL generated by the Entity Provider.

Not sure if this is exactly what you need, but it looks like it might be close:
string[] companies = { "Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power & Light",
"Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works",
"Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders",
"Blue Yonder Airlines", "Trey Research", "The Phone Company",
"Wingtip Toys", "Lucerne Publishing", "Fourth Coffee" };
// The IQueryable data to query.
IQueryable<String> queryableData = companies.AsQueryable<string>();
// Compose the expression tree that represents the parameter to the predicate.
ParameterExpression pe = Expression.Parameter(typeof(string), "company");
// ***** Where(company => (company.ToLower() == "coho winery" || company.Length > 16)) *****
// Create an expression tree that represents the expression 'company.ToLower() == "coho winery"'.
Expression left = Expression.Call(pe, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Expression right = Expression.Constant("coho winery");
Expression e1 = Expression.Equal(left, right);
// Create an expression tree that represents the expression 'company.Length > 16'.
left = Expression.Property(pe, typeof(string).GetProperty("Length"));
right = Expression.Constant(16, typeof(int));
Expression e2 = Expression.GreaterThan(left, right);
// Combine the expression trees to create an expression tree that represents the
// expression '(company.ToLower() == "coho winery" || company.Length > 16)'.
Expression predicateBody = Expression.OrElse(e1, e2);
// Create an expression tree that represents the expression
// 'queryableData.Where(company => (company.ToLower() == "coho winery" || company.Length > 16))'
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<string, bool>>(predicateBody, new ParameterExpression[] { pe }));
// ***** End Where *****
// ***** OrderBy(company => company) *****
// Create an expression tree that represents the expression
// 'whereCallExpression.OrderBy(company => company)'
MethodCallExpression orderByCallExpression = Expression.Call(
typeof(Queryable),
"OrderBy",
new Type[] { queryableData.ElementType, queryableData.ElementType },
whereCallExpression,
Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
// ***** End OrderBy *****
// Create an executable query from the expression tree.
IQueryable<string> results = queryableData.Provider.CreateQuery<string>(orderByCallExpression);
// Enumerate the results.
foreach (string company in results)
Console.WriteLine(company);

Extensions for the SimpleExpressionToSQL class
public static class Extensions
{
private static readonly MethodInfo _deleteMethod;
private static readonly MethodInfo _deleteMethodAsync;
private static readonly MethodInfo _toSqlStringMethod;
private static readonly MethodInfo _updateMethod;
private static readonly MethodInfo _updateMethodAsync;
static Extensions()
{
Type extensionType = typeof(Extensions);
_deleteMethod = extensionType.GetMethod(nameof(Extensions.Delete), BindingFlags.Static | BindingFlags.Public);
_updateMethod = extensionType.GetMethod(nameof(Extensions.Update), BindingFlags.Static | BindingFlags.Public);
_deleteMethodAsync = extensionType.GetMethod(nameof(Extensions.DeleteAsync), BindingFlags.Static | BindingFlags.Public);
_updateMethodAsync = extensionType.GetMethod(nameof(Extensions.Update), BindingFlags.Static | BindingFlags.Public);
_toSqlStringMethod = extensionType.GetMethod(nameof(Extensions.ToSqlString), BindingFlags.Static | BindingFlags.Public);
}
public static bool CanConvertToSqlDbType(this Type type) => type.ToSqlDbTypeInternal().HasValue;
public static int Delete<T>(this IQueryable<T> queryable)
{
var simpleExpressionToSQL = new SimpleExpressionToSQL(queryable.AppendCall(_deleteMethod));
return simpleExpressionToSQL.ExecuteNonQuery();
}
public static async Task<int> DeleteAsync<T>(this IQueryable<T> queryable)
{
var simpleExpressionToSQL = new SimpleExpressionToSQL(queryable.AppendCall(_deleteMethodAsync));
return await simpleExpressionToSQL.ExecuteNonQueryAsync();
}
public static string GetTableName<TEntity>(this DbSet<TEntity> dbSet) where TEntity : class
{
DbContext context = dbSet.GetService<ICurrentDbContext>().Context;
IModel model = context.Model;
IEntityType entityTypeOfFooBar = model
.GetEntityTypes()
.First(t => t.ClrType == typeof(TEntity));
IAnnotation tableNameAnnotation = entityTypeOfFooBar.GetAnnotation("Relational:TableName");
return tableNameAnnotation.Value.ToString();
}
public static string GetTableName(this IQueryable query, Type entity)
{
QueryCompiler compiler = query.Provider.GetValueOfField<QueryCompiler>("_queryCompiler");
IModel model = compiler.GetValueOfField<IModel>("_model");
IEntityType entityTypeOfFooBar = model
.GetEntityTypes()
.First(t => t.ClrType == entity);
IAnnotation tableNameAnnotation = entityTypeOfFooBar.GetAnnotation("Relational:TableName");
return tableNameAnnotation.Value.ToString();
}
public static SqlDbType ToSqlDbType(this Type type) =>
type.ToSqlDbTypeInternal() ?? throw new InvalidCastException($"Unable to cast from '{type}' to '{typeof(DbType)}'.");
public static string ToSqlString<T>(this IQueryable<T> queryable) => new SimpleExpressionToSQL(queryable.AppendCall(_toSqlStringMethod));
public static int Update<TSource, TResult>(this IQueryable<TSource> queryable, Expression<Func<TSource, TResult>> selector)
{
var simpleExpressionToSQL = new SimpleExpressionToSQL(queryable.AppendCall(_updateMethod, selector));
return simpleExpressionToSQL.ExecuteNonQuery();
}
public static async Task<int> UpdateAsync<TSource, TResult>(this IQueryable<TSource> queryable, Expression<Func<TSource, TResult>> selector)
{
var simpleExpressionToSQL = new SimpleExpressionToSQL(queryable.AppendCall(_updateMethodAsync, selector));
return await simpleExpressionToSQL.ExecuteNonQueryAsync();
}
internal static DbContext GetDbContext(this IQueryable query)
{
QueryCompiler compiler = query.Provider.GetValueOfField<QueryCompiler>("_queryCompiler");
RelationalQueryContextFactory queryContextFactory = compiler.GetValueOfField<RelationalQueryContextFactory>("_queryContextFactory");
QueryContextDependencies dependencies = queryContextFactory.GetValueOfField<QueryContextDependencies>("_dependencies");
return dependencies.CurrentContext.Context;
}
internal static string Join(this IEnumerable<string> values, string separator) => string.Join(separator, values);
internal static bool RequiresQuotes(this SqlDbType sqlDbType)
{
switch (sqlDbType)
{
case SqlDbType.Char:
case SqlDbType.Date:
case SqlDbType.DateTime:
case SqlDbType.DateTime2:
case SqlDbType.DateTimeOffset:
case SqlDbType.NChar:
case SqlDbType.NText:
case SqlDbType.Time:
case SqlDbType.SmallDateTime:
case SqlDbType.Text:
case SqlDbType.UniqueIdentifier:
case SqlDbType.Timestamp:
case SqlDbType.VarChar:
case SqlDbType.Xml:
case SqlDbType.Variant:
case SqlDbType.NVarChar:
return true;
default:
return false;
}
}
internal static unsafe string ToCamelCase(this string value)
{
if (value == null || value.Length == 0)
{
return value;
}
string result = string.Copy(value);
fixed (char* chr = result)
{
char valueChar = *chr;
*chr = char.ToLowerInvariant(valueChar);
}
return result;
}
private static IQueryable<TResult> AppendCall<TSource, TResult>(this IQueryable<TSource> queryable, MethodInfo methodInfo, Expression<Func<TSource, TResult>> selector)
{
MethodInfo methodInfoGeneric = methodInfo.MakeGenericMethod(typeof(TSource), typeof(TResult));
MethodCallExpression methodCallExpression = Expression.Call(methodInfoGeneric, queryable.Expression, selector);
return new EntityQueryable<TResult>(queryable.Provider as IAsyncQueryProvider, methodCallExpression);
}
private static IQueryable<T> AppendCall<T>(this IQueryable<T> queryable, MethodInfo methodInfo)
{
MethodInfo methodInfoGeneric = methodInfo.MakeGenericMethod(typeof(T));
MethodCallExpression methodCallExpression = Expression.Call(methodInfoGeneric, queryable.Expression);
return new EntityQueryable<T>(queryable.Provider as IAsyncQueryProvider, methodCallExpression);
}
private static T GetValueOfField<T>(this object obj, string name)
{
FieldInfo field = obj
.GetType()
.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance);
return (T)field.GetValue(obj);
}
[SuppressMessage("Style", "IDE0011:Add braces", Justification = "Easier to read than with Allman braces")]
private static SqlDbType? ToSqlDbTypeInternal(this Type type)
{
if (Nullable.GetUnderlyingType(type) is Type nullableType)
return nullableType.ToSqlDbTypeInternal();
if (type.IsEnum)
return Enum.GetUnderlyingType(type).ToSqlDbTypeInternal();
if (type == typeof(long)) /**/ return SqlDbType.BigInt;
if (type == typeof(byte[])) /**/ return SqlDbType.VarBinary;
if (type == typeof(bool)) /**/ return SqlDbType.Bit;
if (type == typeof(string)) /**/ return SqlDbType.NVarChar;
if (type == typeof(DateTime)) /**/ return SqlDbType.DateTime2;
if (type == typeof(decimal)) /**/ return SqlDbType.Decimal;
if (type == typeof(double)) /**/ return SqlDbType.Float;
if (type == typeof(int)) /**/ return SqlDbType.Int;
if (type == typeof(float)) /**/ return SqlDbType.Real;
if (type == typeof(Guid)) /**/ return SqlDbType.UniqueIdentifier;
if (type == typeof(short)) /**/ return SqlDbType.SmallInt;
if (type == typeof(object)) /**/ return SqlDbType.Variant;
if (type == typeof(DateTimeOffset)) /**/ return SqlDbType.DateTimeOffset;
if (type == typeof(TimeSpan)) /**/ return SqlDbType.Time;
if (type == typeof(byte)) /**/ return SqlDbType.TinyInt;
return null;
}
}

Related

How to build dynamic query with Where and OR using Expression

I hope somebody can guide and help me with this. We have an inherited project that uses ExpressionHelper class. Basically, this Expression Helper will return an IQueryable that build a dynamic query base on the search term that the user provided.
For example, I have the below code where I pass 2 search terms.
IQueryable<UserEntity> modifiedQuery = _uow.UserRepository.GetAll();;
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "FirstName", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "FirstName", Operator = "eq", Value = "John" }
};
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var obj = ExpressionHelper.Parameter<TEntity>();
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider
.GetComparison(left, searchTerm.Operator, right);
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper
.GetLambda<TEntity, bool>(obj, comparisonExpression);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
With the code above and using the below ExpressionHelper class, this generate the below SQL query when I check using SQLProfiler. Please notice the AND in the query. What I actually what is OR.
Constructed QUERY in SQL Profiler
SELECT
[Extent1].[FirstName] AS [FirstName],
FROM [dbo].[tblUser] AS [Extent1]
WHERE ([Extent1].[Conatact1] = N'Bob') AND ([Extent1].[Contact2] = N'John')
ExpressionHelper.cs
public static class ExpressionHelper
{
private static readonly MethodInfo LambdaMethod = typeof(Expression)
.GetMethods()
.First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2);
private static MethodInfo[] QueryableMethods = typeof(Queryable)
.GetMethods()
.ToArray();
private static MethodInfo GetLambdaFuncBuilder(Type source, Type dest)
{
var predicateType = typeof(Func<,>).MakeGenericType(source, dest);
return LambdaMethod.MakeGenericMethod(predicateType);
}
public static PropertyInfo GetPropertyInfo<T>(string name)
=> typeof(T).GetProperties()
.Single(p => p.Name == name);
public static ParameterExpression Parameter<T>()
=> Expression.Parameter(typeof(T));
public static MemberExpression GetPropertyExpression(ParameterExpression obj, PropertyInfo property)
=> Expression.Property(obj, property);
public static LambdaExpression GetLambda<TSource, TDest>(ParameterExpression obj, Expression arg)
=> GetLambda(typeof(TSource), typeof(TDest), obj, arg);
public static LambdaExpression GetLambda(Type source, Type dest, ParameterExpression obj, Expression arg)
{
var lambdaBuilder = GetLambdaFuncBuilder(source, dest);
return (LambdaExpression)lambdaBuilder.Invoke(null, new object[] { arg, new[] { obj } });
}
public static IQueryable<T> CallWhere<T>(IQueryable<T> query, LambdaExpression predicate)
{
var whereMethodBuilder = QueryableMethods
.First(x => x.Name == "Where" && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { typeof(T) });
return (IQueryable<T>)whereMethodBuilder
.Invoke(null, new object[] { query, predicate });
}
public static IQueryable<TEntity> CallOrderByOrThenBy<TEntity>(
IQueryable<TEntity> modifiedQuery,
bool useThenBy,
bool descending,
Type propertyType,
LambdaExpression keySelector)
{
var methodName = "OrderBy";
if (useThenBy) methodName = "ThenBy";
if (descending) methodName += "Descending";
var method = QueryableMethods
.First(x => x.Name == methodName && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { typeof(TEntity), propertyType });
return (IQueryable<TEntity>)method.Invoke(null, new object[] { modifiedQuery, keySelector });
}
}
I have hard time understanding on how the query was created and how do I change it to become OR in the created query.
Hope someone can guide me and point to the right direction. Thank you!
Add to SearchTerm a new property (C# 6.0 syntax here):
// This is quite wrong. We should have an enum here, but Operator is
// done as a string, so I'm maintaining the "style"
// Supported LogicalConnector: and, or
public string LogicalConnector { get; set; } = "and";
}
Then:
private static IQueryable<TEntity> BuildQuery<TEntity>(IQueryable<TEntity> modifiedQuery, List<SearchTerm> searchTerms)
{
Expression comparisonExpressions = null;
var obj = ExpressionHelper.Parameter<TEntity>();
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider.GetComparison(left, searchTerm.Operator, right);
if (comparisonExpressions == null)
{
comparisonExpressions = comparisonExpression;
}
else if (searchTerm.LogicalConnector == "and")
{
comparisonExpressions = Expression.AndAlso(comparisonExpressions, comparisonExpression);
}
else if (searchTerm.LogicalConnector == "or")
{
comparisonExpressions = Expression.OrElse(comparisonExpressions, comparisonExpression);
}
else
{
throw new NotSupportedException(searchTerm.LogicalConnector);
}
}
if (comparisonExpressions != null)
{
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper.GetLambda<TEntity, bool>(obj, comparisonExpressions);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
return modifiedQuery;
}
Use it like:
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "SecondaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "John", LogicalConnector = "or", }
};
IQueryable<UserEntity> query = BuildQuery<UserEntity>(modifiedQuery, searchTerms);
Note that there is no way in this code to explicitly set brackets, that will be implicitly set as:
(((A opB b) opC C) opD D)
Where A, B, C, D are the SearchTerm[0], SearchTerm[1], SearchTerm[2], SearchTerm[3] and opB, opC, opD are the operators defined in SearchTerm[1].LogicalConnector, SearchTerm[2].LogicalConnector, SearchTerm[3].LogicalConnector.
While putting brackets is easy, choosing how to "describe" them is complex, unless you change significantly your SearchTerm collection (it couldn't be a "linear" array but it would need to be a tree).
P.S. I was wrong, you don't need an ExpressionVisitor. You need an ExpressionVisitor when you are trying to "merge" multiple LambdaExpressions that have distinct ParameterExpression. In this code we are able to have a single var obj = ExpressionHelper.Parameter<TEntity>() for all the query, so no problems merging the conditions. To make it clear: if you want to "merge" x1 => x1.Foo == "Foo1" with x2 => x2.Foo == "Foo2" then you need an ExpressionVisitor that replaces x2 with x1, otherwise you would get a wrong query like x1 => x1.Foo == "Foo1" || x2.Foo == "Foo2". In the code given we have only x1 (that is var obj = ExpressionHelper.Parameter<TEntity>()), so no problem.

LINQ Use "like" instead of "(( NVL(INSTR(x, y), 0) ) = 1)"

When using .Contains()/.StartsWith()/.EndsWith() the generated SQL looks like this :
(( NVL(INSTR(x, y), 0) ) = 1)
Is there a way of using this instead :
LIKE 'x%' or '%x%' or '%x'
Because there is a huge cost difference between those two in the execution plan of the query (44 000 vs 30).
When i look around abit i found LIKE operator in LINQ which were a which have a few good examples of how you could do this. I have tested the one below that was from the link above
Here is a extension for using Like with lambda that was posted by adobrzyc
public static class LinqEx
{
private static readonly MethodInfo ContainsMethod = typeof(string).GetMethod("Contains");
private static readonly MethodInfo StartsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
private static readonly MethodInfo EndsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
public static Expression<Func<TSource, bool>> LikeExpression<TSource, TMember>(Expression<Func<TSource, TMember>> property, string value)
{
var param = Expression.Parameter(typeof(TSource), "t");
var propertyInfo = GetPropertyInfo(property);
var member = Expression.Property(param, propertyInfo.Name);
var startWith = value.StartsWith("%");
var endsWith = value.EndsWith("%");
if (startWith)
value = value.Remove(0, 1);
if (endsWith)
value = value.Remove(value.Length - 1, 1);
var constant = Expression.Constant(value);
Expression exp;
if (endsWith && startWith)
{
exp = Expression.Call(member, ContainsMethod, constant);
}
else if (startWith)
{
exp = Expression.Call(member, EndsWithMethod, constant);
}
else if (endsWith)
{
exp = Expression.Call(member, StartsWithMethod, constant);
}
else
{
exp = Expression.Equal(member, constant);
}
return Expression.Lambda<Func<TSource, bool>>(exp, param);
}
public static IQueryable<TSource> Like<TSource, TMember>(this IQueryable<TSource> source, Expression<Func<TSource, TMember>> parameter, string value)
{
return source.Where(LikeExpression(parameter, value));
}
private static PropertyInfo GetPropertyInfo(Expression expression)
{
var lambda = expression as LambdaExpression;
if (lambda == null)
throw new ArgumentNullException("expression");
MemberExpression memberExpr = null;
switch (lambda.Body.NodeType)
{
case ExpressionType.Convert:
memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
break;
case ExpressionType.MemberAccess:
memberExpr = lambda.Body as MemberExpression;
break;
}
if (memberExpr == null)
throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");
var output = memberExpr.Member as PropertyInfo;
if (output == null)
throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");
return output;
}
}
To use it you you just simply add Like function where you would put the Contains functions. You can see below for a example
using (CustomerEntities customerContext = new CustomerEntities())
{
IQueryable<Customer> customer = customerContext.Customer.Like(x => x.psn, "%1%");
}
This will create a sql query that looks something like this.
SELECT
[Extent1].[psn] AS [psn]
FROM [dbo].[Customer] AS [Extent1]
WHERE [Extent1].[psn] LIKE '%1%'

How to change Parameters Expression to Constant Expression in Binary Expression

I have my custom Visitor which looks to right and left and changes parameters to constant.
I know that just change node is not possible.
I should return new lambda expression which contains constants instead parameters.
But I can not create an expression myself :(
I have this code:
public class ParametersTransformToConstantVisitor : ExpressionVisitor
{
private Dictionary<string, ConstantExpression> parameters = new Dictionary<string, ConstantExpression>();
public ParametersTransformToConstantVisitor(Dictionary<string, ConstantExpression> parameters)
{
this.parameters = parameters;
}
protected override Expression VisitBinary(BinaryExpression node)
{
var constExprLeftName = new Lazy<string>(() => ((ParameterExpression) node.Left)?.Name);
var constExprRightName = new Lazy<string>(() => ((ParameterExpression) node.Right)?.Name);
var constExprName = new Lazy<string>(() => ((ParameterExpression) node.Reduce())?.Name);
ParameterExpression leftParam = null;
ParameterExpression rightParam = null;
if (node.NodeType == ExpressionType.Parameter && parameters.ContainsKey(constExprName.Value))
{
return parameters[constExprName.Value];
}
if (node.Left.NodeType == ExpressionType.Parameter && parameters.ContainsKey(constExprLeftName.Value))
{
leftParam = (ParameterExpression) node.Left;
}
if (node.Right.NodeType == ExpressionType.Parameter && parameters.ContainsKey(constExprLeftName.Value))
{
rightParam = (ParameterExpression) node.Right;
}
if (leftParam != null || rightParam != null)
{
//return Expression.Lambda();
}
return base.VisitBinary(node);
}
}
Help me to build lambda expression, please
It feels like all you actually need here is:
protected override Expression VisitParameter(ParameterExpression node)
=> parameters.TryGetValue(node.Name, out var ce) ? (Expression)ce : node;
protected override Expression VisitLambda<T>(Expression<T> node)
=> Expression.Lambda(Visit(node.Body), node.Parameters); // don't visit the parameters
i.e. whenever the visitor sees a ParameterExpression, if there is a corresponding item in the parameters map, use that value.
The override on VisitLambda is because VisitLambda still needs to return a lambda of the same shape, and the default implementation would also visit (and thus swap) out the parameters from the declaration.
It is the visitor's job to worry about reassembling the tree around your changes.
Note, however, that if you are trying to create a parameterless lambda, you might also need to rewrite the root. Or you could just use the .Body and forget about the parameters.
Example:
Expression<Func<int, int, string>> add = (x, y) => ((2 * x) + y).ToString();
Console.WriteLine(add);
var args = new Dictionary<string, ConstantExpression>
{
["x"] = Expression.Constant(4),
["y"] = Expression.Constant(1),
};
var visitor = new ParametersTransformToConstantVisitor(args);
var result = (LambdaExpression)visitor.Visit(add);
Console.WriteLine(result);
which gives:
(x, y) => ((2 * x) + y).ToString()
(x, y) => ((2 * 4) + 1).ToString()
You can make this into a parameterless lambda via:
var withoutArgs = Expression.Lambda<Func<string>>(result.Body);
Console.WriteLine(withoutArgs);
which gives:
() => ((2 * 4) + 1).ToString()
minor addition: you might also want to simplify in the visitor:
protected override Expression VisitBinary(BinaryExpression node)
{
var visited = base.VisitBinary(node);
if(visited is BinaryExpression be
&& be.Method == null && be.Conversion == null
&& !be.IsLifted
&& be.Left is ConstantExpression left
&& be.Right is ConstantExpression right)
{
object val;
switch(be.NodeType)
{
case ExpressionType.Add:
val = (dynamic)left.Value + (dynamic)right.Value;
break;
case ExpressionType.Multiply:
val = (dynamic)left.Value * (dynamic)right.Value;
break;
case ExpressionType.Subtract:
val = (dynamic)left.Value - (dynamic)right.Value;
break;
case ExpressionType.Divide:
val = (dynamic)left.Value / (dynamic)right.Value;
break;
default:
return visited; // unknown
}
return Expression.Constant(
Convert.ChangeType(val, visited.Type), visited.Type);
}
return visited;
}
This changes the outputs to:
(x, y) => ((2 * x) + y).ToString()
(x, y) => 9.ToString()
() => 9.ToString()
and we could possibly also even hoist the ToString()!
protected override Expression VisitMethodCall(MethodCallExpression node)
{
var visited = base.VisitMethodCall(node);
if (visited is MethodCallExpression mce)
{
if ((mce.Object == null || mce.Object is ConstantExpression)
&& mce.Arguments.All(x => x is ConstantExpression))
{
var obj = (mce.Object as ConstantExpression)?.Value;
var args = mce.Arguments.Select(x => ((ConstantExpression)x).Value).ToArray();
var result = mce.Method.Invoke(obj, args);
return Expression.Constant(result, mce.Type);
}
}
return visited;
}
which now gives us:
(x, y) => ((2 * x) + y).ToString()
(x, y) => "9"
() => "9"

Building dynamic linq query with checking null for collection

I have a function who generate the lambda expression like this
d => d.Defendeurs.Any(DossierTiers => DossierTiers.Tiers.TiersLiesEnfantsActifs.Any(TiersLie => TiersLie.TiersEnfant.AdressePrincipale.Adresse.CodePostal.Contains("45")))
All work but sometimes I got NullExceptionReference on some class.
So I need to check if some property is null.
I think, I must add some code in the BuildLambda function, but all my test doesn't work.
Some help are welcome
public static Expression GetExpression(Expression parameter, object Operator, object value, params string[] properties)
{
Expression resultExpression = null;
Expression childParameter, navigationPropertyPredicate;
Type childType = null;
if (properties.Count() > 1)
{
parameter = Expression.Property(parameter, properties[0]);
var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
if (isCollection)
{
childType = parameter.Type.GetGenericArguments()[0];
childParameter = Expression.Parameter(childType, childType.Name);
}
else
{
childParameter = parameter;
}
var innerProperties = properties.Skip(1).ToArray();
navigationPropertyPredicate = GetExpression(childParameter, Operator, value, innerProperties);
if (isCollection)
{
var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(childType);
navigationPropertyPredicate = Expression.Call(anyMethod, parameter, navigationPropertyPredicate);
resultExpression = BuildLambda(parameter, navigationPropertyPredicate);
}
else
{
resultExpression = navigationPropertyPredicate;
}
}
else
{
ConstantExpression right = null;
var childProperty = parameter.Type.GetProperty(properties[0]);
var left = Expression.Property(parameter, childProperty);
right = (value != null) ? right = Expression.Constant(value, value.GetType()) : Expression.Constant(string.Empty);
navigationPropertyPredicate = GetExpression(left, right, Operator);
resultExpression = BuildLambda(parameter, navigationPropertyPredicate);
}
return resultExpression;
}
private static Expression BuildLambda(Expression parameter, Expression predicate)
{
var resultParameterVisitor = new ParameterVisitor();
resultParameterVisitor.Visit(parameter);
var resultParameter = resultParameterVisitor.Parameter;
return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
}
private static Expression GetExpression(MemberExpression left, ConstantExpression right, Object p)
{
MethodInfo c = null;
if (p is OperatorUsedWithString)
{
switch ((OperatorUsedWithString)p)
{
case OperatorUsedWithString.CommencePar:
c = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
return Expression.Call(left, c, right);
case OperatorUsedWithString.Contient:
c = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
return Expression.Call(left, c, right);
case OperatorUsedWithString.TerminePar:
c = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) });
return Expression.Call(left, c, right);
case OperatorUsedWithString.Egal:
c = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
return Expression.Call(left, c, right);
}
}
if (p is OperatorUsedWithNumber)
{
switch ((OperatorUsedWithNumber)p)
{
case OperatorUsedWithNumber.Egal:
return Expression.Equal(left, right);
case OperatorUsedWithNumber.Inferieur:
return Expression.LessThan(left, right);
case OperatorUsedWithNumber.InferieurEgal:
return Expression.LessThanOrEqual(left, right);
case OperatorUsedWithNumber.Superieur:
return Expression.GreaterThan(left, right);
case OperatorUsedWithNumber.SuperieurEgal:
return Expression.GreaterThanOrEqual(left, right);
}
}
if (p is OperatorUsedWithDateTime)
{
switch ((OperatorUsedWithDateTime)p)
{
case OperatorUsedWithDateTime.PasPresent:
return Expression.Equal(left, Expression.Constant(null));
case OperatorUsedWithDateTime.Present:
return Expression.NotEqual(left, Expression.Constant(null));
}
}
throw new NotImplementedException();
}
}

How to sort a user Created List<UserClass> Collection in C#

I wish to sort the collection List<UserClass>
on the basis of one of the property among various of that UserClass which is User Defined.
Is it valid
List<Function> objFunctionsList = new List<Function>();
// populating data in objFunctionsList
objFunctionsList = objFunctionsList.OrderBy(x => x.Name).ToList();
Just use the linq extension method orderby
var sorted=list.OrderBy(x=>x.MyProperty);
If you need a list as a result then add ToList() eg
var sortedList=list.OrderBy(x=>x.MyProperty).ToList();
Alternatively you could use this Extension class
public static class SortExtension {
public static Comparison<T> GetComparer<T, TP>(Expression<Func<T, IComparable<TP>>> propertyExpression) {
if (propertyExpression == null) throw new ArgumentNullException();
var member = ((propertyExpression.Body is UnaryExpression) ? ((UnaryExpression)propertyExpression.Body).Operand : propertyExpression.Body) as MemberExpression;
if (member == null) throw new ArgumentException();
var parameterA = Expression.Parameter(typeof(T), "a");
var parameterB = Expression.Parameter(typeof(T), "b");
var nullExpr = Expression.Constant(null);
var valueA = Expression.Property(parameterA, member.Member.Name);
var valueB = Expression.Property(parameterB, member.Member.Name);
var compare = Expression.Call(valueA, typeof(TP).GetMethod("CompareTo", new[] { typeof(TP) }), valueB);
var checkBPropNull = Expression.Condition(Expression.Equal(valueB, nullExpr), Expression.Constant(0), Expression.Constant(-1));
var checkAPropertyNull = Expression.Condition(Expression.Equal(valueA, nullExpr), checkBPropNull, compare);
var checkBNullANot = Expression.Condition(Expression.Equal(parameterB, nullExpr), Expression.Constant(1), checkAPropertyNull);
var checkBNullANull = Expression.Condition(Expression.Equal(parameterB, nullExpr), Expression.Constant(0), Expression.Constant(-1));
var checkANull = Expression.Condition(Expression.Equal(parameterA, nullExpr), checkBNullANull, checkBNullANot);
return (a, b) => Expression.Lambda<Func<T, T, int>>(checkANull, parameterA, parameterB).Compile()(a, b);
}
public static void SortBy<T, TP>(this List<T> source, Expression<Func<T, IComparable<TP>>> propertyExpression) {
if (source == null) throw new ArgumentNullException();
source.Sort(GetComparer(propertyExpression));
}
}
Then you can just do
list.SortBy(x=>x.MyProperty);
The Expression building produces a comparitor that is functionally equivalent to
list.Sort((a,b) => {
if (a == null) return (b==null) ? 0 :-1;
if (b==null) return 1;
if (a.MyProperty == null) return (b.MyProperty==null) ? 0 : -1;
return a.T1.CompareTo(b.T1);
});
users.Sort((u1, u2) => {
return u1.Age.CompareTo(u2.Age);
});
This will sort by Age for example.
If your list is list, then:
list.Sort((a, b) => {
if (a==null) return (b==null) ? 0 : -1;
if (b==null) return 1;
if (a.Property==null) return (b.Property==null) ? 0 : -1;
return a.Property.CompareTo(b.Property);
});

Categories

Resources