Related
I learn how to build simple expression which like
c=>c.code=='XXX';
I create these expression by method below:
public static Expression<Func<T, bool>> BuildStringEqualLambda(string propertyName, string propertyValue)
{
ParameterExpression parameterExp = Expression.Parameter(typeof(T), "type");
Expression propertyExp = parameterExp;
foreach (var property in propertyName.Split('.'))
{
propertyExp = Expression.PropertyOrField(propertyExp, property);
}
Expression right = Expression.Constant(propertyValue);
Expression e1 = Expression.Equal(propertyExp, right);
return Expression.Lambda<Func<T, bool>>(e1, new ParameterExpression[] { parameterExp });
}
I think I can build expression
o=>o.code=='XXX' || c.name=='XXX'
But I don't know how can I build method Any.
Thanks
It should be something like:
public static Expression<Func<T, bool>> BuildStringEqualLambda<T>(params Tuple<string, string>[] propertyNameValues)
{
if (propertyNameValues == null || propertyNameValues.Length == 0)
{
throw new ArgumentException(nameof(propertyNameValues));
}
ParameterExpression parameterExp = Expression.Parameter(typeof(T), "type");
Expression body = null;
foreach (var propertyNameValue in propertyNameValues)
{
Expression propertyExp = parameterExp;
foreach (var property in propertyNameValue.Item1.Split('.'))
{
propertyExp = Expression.PropertyOrField(propertyExp, property);
}
Expression right = Expression.Constant(propertyNameValue.Item2);
Expression eq = Expression.Equal(propertyExp, right);
body = body == null ? eq : Expression.OrElse(body, eq);
}
return Expression.Lambda<Func<T, bool>>(body, new ParameterExpression[] { parameterExp });
}
You can probably use some funny LINQ with Aggregate to reduce the number of lines, but it would be undebuggable.
In the end you use the Expression.OrElse (not the Expression.Or that is |!) and you handle the first element case.
Use it like:
var exp = BuildStringEqualLambda(
Tuple.Create("prop1", "value1"),
Tuple.Create("prop2", "value2"),
Tuple.Create("prop3", "value3")
);
Using some LINQ and Aggregate (for those who can't live without LINQing everything) (note that while I wouldn't ever use the LINQed version of the code... It is quite unreadable... Enumerable.Aggregate is "terrible" 😊):
public static Expression<Func<T, bool>> BuildStringEqualLambda<T>(params Tuple<string, string>[] propertyNameValues)
{
if (propertyNameValues == null || propertyNameValues.Length == 0)
{
throw new ArgumentException(nameof(propertyNameValues));
}
ParameterExpression parameterExp = Expression.Parameter(typeof(T), "type");
Expression body = propertyNameValues
.Select(x => BuildEqualityExpression<T>(parameterExp, x.Item1, x.Item2))
.Aggregate((acc, x) => Expression.OrElse(acc, x));
return Expression.Lambda<Func<T, bool>>(body, new ParameterExpression[] { parameterExp });
}
private static Expression BuildEqualityExpression<T>(ParameterExpression parameterExp, string propertyName, string propertyValue)
{
Expression propertyExp = propertyName
.Split('.')
.Aggregate((Expression)parameterExp, (acc, x) => Expression.PropertyOrField(acc, x));
Expression right = Expression.Constant(propertyValue);
Expression eq = Expression.Equal(propertyExp, right);
return eq;
}
This is My Expression Class
public static class ExpressionBuilder
{
private static MethodInfo containsMethod = typeof(string).GetMethod("Contains");
private static MethodInfo startsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
private static MethodInfo endsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) });
public static Expression<Func<T,
bool>> GetExpression<T>(IList<Filter> filters)
{
if (filters.Count == 0)
return null;
ParameterExpression param = Expression.Parameter(typeof(T), "t");
Expression exp = null;
if (filters.Count == 1)
exp = GetExpression<T>(param, filters[0]);
else if (filters.Count == 2)
exp = GetExpression<T>(param, filters[0], filters[1]);
else
{
while (filters.Count > 0)
{
var f1 = filters[0];
var f2 = filters[1];
if (exp == null)
exp = GetExpression<T>(param, filters[0], filters[1]);
else
exp = Expression.AndAlso(exp, GetExpression<T>(param, filters[0], filters[1]));
filters.Remove(f1);
filters.Remove(f2);
if (filters.Count == 1)
{
exp = Expression.AndAlso(exp, GetExpression<T>(param, filters[0]));
filters.RemoveAt(0);
}
}
}
return Expression.Lambda<Func<T, bool>>(exp, param);
}
private static Expression GetExpression<T>(ParameterExpression param, Filter filter)
{
MemberExpression member = Expression.Property(param, filter.PropertyName);
ConstantExpression constant = Expression.Constant(filter.Value);
switch (filter.Operation)
{
case Op.Equals:
return Expression.Equal(member, Expression.Call(Expression.Convert(Expression.Constant(search.RetrieveGuid), typeof(object)), typeof(object).GetMethod("ToString"))), constant);
case Op.GreaterThan:
return Expression.GreaterThan(member, constant);
case Op.GreaterThanOrEqual:
return Expression.GreaterThanOrEqual(member, constant);
case Op.LessThan:
return Expression.LessThan(member, constant);
case Op.LessThanOrEqual:
return Expression.LessThanOrEqual(member, constant);
case Op.Contains:
return Expression.Call(member, containsMethod, constant);
case Op.StartsWith:
return Expression.Call(member, startsWithMethod, constant);
case Op.EndsWith:
return Expression.Call(member, endsWithMethod, constant);
}
return null;
}
private static BinaryExpression GetExpression<T>
(ParameterExpression param, Filter filter1, Filter filter2)
{
Expression bin1 = GetExpression<T>(param, filter1);
Expression bin2 = GetExpression<T>(param, filter2);
return Expression.AndAlso(bin1, bin2);
}
}
when i generate Expression by this code
delegExpFilters = EntityExpression.ExpressionBuilder.GetExpression<Contact>(domainFilter).Compile();
my domainFilter Contains a list records with
Property field name ,operator, and its value and my field is GUID
when i call GetExpression it gives me error
The binary operator Equal is not defined for the types 'System.Guid' and 'System.String'
You are not converting filter value (which is string) to appropriate type:
ConstantExpression constant = Expression.Constant(filter.Value);
Consider you have filter for integer property named Amount:
var filter = new Filter {
PropertyName = "Amount",
Operation = Op.GreaterThan,
Value = "42"
};
Your code will generate expression which equivalent of following code
contract.Amount > "42"
Such integer comparison with string is not allowed.
You should get property type and convert filter value to that type. Here are the steps:
Get type converter for property type
Check whether it can convert string to property type
Do conversion (that will return property value as object
Create cast expression to convert property value from object to property type
Here is code of GetExpression method
var member = Expression.Property(param, filter.PropertyName);
var propertyType = ((PropertyInfo)member.Member).PropertyType;
var converter = TypeDescriptor.GetConverter(propertyType); // 1
if (!converter.CanConvertFrom(typeof(string))) // 2
throw new NotSupportedException();
var propertyValue = converter.ConvertFromInvariantString(filter.Value); // 3
var constant = Expression.Constant(propertyValue);
var valueExpression = Expression.Convert(constant, propertyType); // 4
You should use this value expression instead of your constant expression in binary expressions which you are returning. E.g.:
case Op.LessThan:
return Expression.LessThan(member, valueExpression);
case Op.Equal:
return Expression.Equal(member, valueExpression);
// etc
For equality you should use binary expression as well. And now filter for Amount will be translated into
contract.Amount > (int)42
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();
}
}
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;
}
}
I'm using the Entity Framework and I developed this extension method:
public static IQueryable<TResult> Like<TResult>(this IQueryable<TResult> query, Expression<Func<TResult, string>> field, string value)
{
var expression = Expression.Lambda<Func<TResult, bool>>(
Expression.Call(field.Body, typeof(string).GetMethod("Contains"),
Expression.Constant(value)), field.Parameters);
return query.Where(expression);
}
this code work correctly if I use it like this:
var result = from e in context.es.Like(r => r.Field, "xxx")
select e
Now I need to call this extension method programmatically:
public static IQueryable<TSource> SearchInText<TSource>(this IQueryable<TSource> source, string textToFind)
{
// Collect fields
PropertyInfo[] propertiesInfo = source.ElementType.GetProperties();
List<string> fields = new List<string>();
foreach (PropertyInfo propertyInfo in propertiesInfo)
{
if (
(propertyInfo.PropertyType == typeof(string)) ||
(propertyInfo.PropertyType == typeof(int)) ||
(propertyInfo.PropertyType == typeof(long)) ||
(propertyInfo.PropertyType == typeof(byte)) ||
(propertyInfo.PropertyType == typeof(short))
)
{
fields.Add(propertyInfo.Name);
}
}
ParameterExpression parameter = Expression.Parameter(typeof(TSource), source.ElementType.Name);
Expression expression = Expression.Lambda(Expression.Property(parameter, typeof(TSource).GetProperty(fields[0])), parameter);
Expression<Func<TSource, string>> field = Expression.Lambda<Func<TSource, string>>(expression, parameter);
return source.Like(field, textToFind);
}
Now this code doesn't work!
I need to understand how to declare the "field" of the Like extended methods.
Expression<Func<TSource, string>> field = Expression.Lambda<Func<TSource, string>>(expression, parameter);
At runtime I receive this error: Impossibile utilizzare un'espressione di tipo 'System.Func`2[TestMdf.Equipment,System.String]' per un tipo restituito 'System.String'
I assume your second code snippet is just a truncated example - if you really did that, then the results would be unpredictable, because you're taking the first property returned by reflection, which can change between runs of your program.
You'll get better answers if you say "This worked" followed by a description of what happened, and "This didn't work" followed by a description of how you could tell that it didn't work (compiler error? runtime error? exception message?)
Firstly, are you aware of Dynamic Linq? It allows you to delay decisions about how a query should be structured until runtime and may solve many of your problems for you.
But assuming this is a learning exercise...
Your Like extension method takes an expression (which a caller ought to usually write out as a lambda, as that's the whole point of these things). That expression will convert a "record" from a query result set and return a string value (presumably selecting it from the data stored in the record). The method also takes a value string.
But it then constructs (by hand) its own predicate that calls the Contains method on the body of the field lambda.
I guess this ought to work, because the result of that lambda is a string. However, I can't see why you're doing this the hard way. What's wrong with:
var result = from e in context.es
where e.Field.Contains("xxx"))
select e
Now I found a partial solution to my problem:
public static IQueryable<TSource> SearchInText<TSource>(this IQueryable<TSource> source, string textToFind)
{
// Collect fields
PropertyInfo[] propertiesInfo = source.ElementType.GetProperties();
List<string> fields = new List<string>();
foreach (PropertyInfo propertyInfo in propertiesInfo)
{
if (
(propertyInfo.PropertyType == typeof(string)) ||
(propertyInfo.PropertyType == typeof(int)) ||
(propertyInfo.PropertyType == typeof(long)) ||
(propertyInfo.PropertyType == typeof(byte)) ||
(propertyInfo.PropertyType == typeof(short))
)
{
fields.Add(propertyInfo.Name);
}
}
ParameterExpression parameter = Expression.Parameter(typeof(TSource), source.ElementType.Name);
var property = typeof(TSource).GetProperty(fields[0]);
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var constantValue = Expression.Constant(textToFind);
var equality = Expression.Call(Expression.Call(Expression.Property(parameter, property), "ToUpper", null, null), typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), Expression.Constant(textToFind.ToUpper()));
return source.Where(Expression.Lambda<Func<TSource, bool>>(equality, parameter));
}
Now the next step is to concatenate all the field list:
" " + fields[0] + " " + ... fields[n]
Some ideas?
This is my first release:
public static IQueryable<TSource> SearchInText<TSource>(this IQueryable<TSource> source, string textToFind)
{
if (textToFind.Trim() == "")
{
return source;
}
string[] textToFindList = textToFind.Replace("'", "''").Split(' ');
// Collect fields
PropertyInfo[] propertiesInfo = source.ElementType.GetProperties();
List<string> fieldList = new List<string>();
foreach (PropertyInfo propertyInfo in propertiesInfo)
{
if (
(propertyInfo.PropertyType == typeof(string)) ||
(propertyInfo.PropertyType == typeof(int)) ||
(propertyInfo.PropertyType == typeof(long)) ||
(propertyInfo.PropertyType == typeof(byte)) ||
(propertyInfo.PropertyType == typeof(short))
)
{
fieldList.Add(propertyInfo.Name);
}
}
ParameterExpression parameter = Expression.Parameter(typeof(TSource), source.ElementType.Name);
MethodInfo concatMethod = typeof(String).GetMethod("Concat", new Type[] { typeof(string), typeof(string) });
var spaceExpression = Expression.Constant(" ");
var concatenatedField = BinaryExpression.Add(spaceExpression, Expression.MakeMemberAccess(parameter, typeof(TSource).GetProperty(fieldList[0])), concatMethod);
for (int i = 1; i < fieldList.Count; i++)
{
concatenatedField = BinaryExpression.Add(concatenatedField, spaceExpression, concatMethod);
concatenatedField = BinaryExpression.Add(concatenatedField, Expression.MakeMemberAccess(parameter, typeof(TSource).GetProperty(fieldList[i])), concatMethod);
}
concatenatedField = BinaryExpression.Add(concatenatedField, spaceExpression, concatMethod);
var fieldsExpression = Expression.Call(concatenatedField, "ToUpper", null, null);
var clauseExpression = Expression.Call(
fieldsExpression, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }),
Expression.Constant(textToFindList[0].ToUpper())
);
if (textToFindList.Length == 1)
{
return source.Where(Expression.Lambda<Func<TSource, bool>>(clauseExpression, parameter));
}
BinaryExpression expression = Expression.And(Expression.Call(
fieldsExpression, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }),
Expression.Constant(textToFindList[1].ToUpper())
), clauseExpression);
for (int i = 2; i < textToFindList.Length; i++)
{
expression = Expression.And(Expression.Call(
fieldsExpression, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }),
Expression.Constant(textToFindList[i].ToUpper())
), expression);
}
return source.Where(Expression.Lambda<Func<TSource, bool>>(expression, parameter));
}
I will modify to manage some rules like "phrase" + and - operator.