TrimStart in Expression Tree with LINQ to Entities - c#

I'm attempting to write an expression tree which can allow dynamic use of a StartsWith() method on non-string valued columns using Entity Framework.
E.g. IntegerValuedColumn.StartsWith(5) would return 500, 5000, 555, 5123, etc
I am trying to write an expression tree based on this answer:
How do I query an integer column for "starts with" in Entity Framework?
Here is what I have so far:
MethodInfo stringConvert = typeof(SqlFunctions).GetMethod("StringConvert", new[] { typeof(double?) });
Expression castExpression = Expression.Convert(propertyExpression, typeof(double?));
Expression convertExpression = Expression.Call(null, stringConvert, castExpression);
MethodInfo trimStart = typeof(string).GetMethod("TrimStart");
Expression nullExpression = Expression.Constant(null, typeof(char[]));
Expression trimExpression = Expression.Call(convertExpression, trimStart, nullExpression);
MethodInfo startsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
Expression methodExpression = Expression.Call(trimExpression, startsWith, constantExpression);
return methodExpression;
When I compile and run this expression, I get the following exception:
The method 'System.String TrimStart(Char[])' is only supported in LINQ to Entities when there are no trim characters specified as arguments.
In the original example, the expression is:
SqlFunctions.StringConvert((double)x.AccountNumber)
.TrimStart().StartsWith(searchTerm)
But what I get comes out as:
StringConvert(Convert(x.AccountNumber)).TrimStart(null).StartsWith(searchTerm)
I have removed the two lines dealing with TrimStart (nullExpression and trimExpression) and verified that the statement runs (excluding the thought that the error is being caused by the different language use). My theory based on the exception message is that the TrimStart() method wants to be called with zero parameters, but when I try that the Expression builder tells me that the incorrect number of parameters have been passed in so I figure I'm just missing something.
How would I go about calling the TrimStart method like TrimStart() instead of TrimStart(null) or TrimStart(new char[0]) using expression trees?

Related

Building a parameterized EntityFramework Core Expression

Hi, I'm trying to build an Expression to get a generic entity by its primary key and getting a parameterized sql query.
Currently I can get the correct WHERE query, but it isn't parameterized.
public async Task<TDbo> Get(TKey key, Expression<Func<TEntity, TKey>> keySelector)
{
var propertyRef = keySelector.Body;
var parameter = keySelector.Parameters[0];
var constantRef = Expression.Constant(key);
var equals = Expression.Equal(propertyRef, constantRef);
var comparer = Expression.Lambda<Func<TEntity, bool>>(equals, parameter);
return await _context.Set<TDbo>().SingleOrDefaultAsync(comparer);
}
This results in the following query:
SELECT e.\"Id\", e.\"Name\" \r\n FROM \"People\" AS e\r\nWHERE e.\"Id\" = 1\r\nLIMIT 2,
instead of the wanted:
SELECT e.\"Id\", e.\"Name\" \r\n FROM \"People\" AS e\r\nWHERE e.\"Id\" = #__s_0\r\nLIMIT 2
It's because of Expression.Constant(key). Value constant expressions are not parameterized by the query translator. What you need is an expression referring to a property or field of another expression (which could be constant). That's basically what C# compiler emits for closures.
One way is to actually use the C# compiler to create lambda expression with closure and take the body:
Expression<Func<TKey>> keyValue = () => key;
var variableRef = key.Body;
(the variableRef is a replacement of yours constantRef)
Another way is to use anonymous, tuple or specific class type to create explicit closure instance and bind the corresponding property or field. For instance, with anonymous type:
var variableRef = Expression.Property(Expression.Constant(new { key }), "key");
or with System.Tuple:
var variableRef = Expression.Property(Expression.Constant(Tuple.Create(key)), "Item1");
The actual method doesn't really matter (I personally prefer the first variant with lambda) - all they will cause creating parameter by EF Core query translator.

How to Perform ToString On Property of an Entity Framework Model Using Reflection

I am trying to write something that will do a "Contains" query on all properties of an entity framework model.
I am able to do the following for example with no issues:
var students = db.Students.AsQueryable();
var test = students.Where(x => x.FirstName.ToString().ToLower().Contains("1"));
However when using reflection (as shown in the code below), the following error is returned:
LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.
It's supposed to be supported now.
I have read up on this error, but as you see above ToString is perfectly valid even when using an IQueryable (which is required in my case because I don't want to post filter the data).
The main difference is I need to invoke it through reflection.
private static readonly MethodInfo StringContainsMethod =
typeof(string).GetMethod(#"Contains",
BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, null);
Type dbType = typeof(Student);
var dbFieldMemberInfo = dbType.GetMember("FirstName",
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).Single();
// Create an "x" as TDbType
var dbTypeParameter = Expression.Parameter(dbType, #"x");
// Get at x.FirstName
var dbFieldMember = Expression.MakeMemberAccess(dbTypeParameter, dbFieldMemberInfo);
// Create the criterion as a constant
var criterionConstant = new Expression[] { Expression.Constant(searchString) };
var toStringMethod = typeof(Convert).GetMethod("ToString", Type.EmptyTypes);
var toStringCall = Expression.Call(dbFieldMember, toStringMethod);
var fancyContain = Expression.Call(toStringCall, StringContainsMethod, criterionConstant);
// Create a lambda like x => x.FirstName.ToString().Contains(criterion)
var lambda = Expression.Lambda(fancyContain, dbTypeParameter) as Expression<Func<Student, bool>>;
This produces the same exact lambda x.FirstName.ToString().Contains("") yet it returns an error that ToString can't be used. Clearly from the first example and the fact it was added to EF 6.1, it can be used. Is this a limitation of reflection?
Looks like the issue is stemming from using the Convert.ToString() method in your expression instead of the type's individual implementation of the ToString() method, e.g. toStringMethod = type.GetMethod("ToString", Type.EmptyTypes);
It looks like what you trying to do is to search based on all columns the easiest and fastest way would be to execute query from EF and do full text search.
var students = db.Database.SqlQuery<Student>("SELECT * FROM Student WHERE CONTAINS((Name, ID), '12')");
(just make sure you clean the string no sql injections)
I think that when you build the expression tree as you trying to do and execute it against SQL, SQL does not recognizing it because when you use Querable you not yet executing it.
another way is to use SqlFunctions.StringConvert instead of ToString
This looks like a lot of complexity for something that should be fairly simple. I would suggest making a textdata computed column in your table that simply concatenates all the fields you're interested in. Then you can add a property to your model like this:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string TextData { get; set; }
Finally, you can just do a LINQ query against this one property to check whether it .Contains(...) whatever text you're interested in. As long as you index the column, searching shouldn't be too bad. Your major penalty will be in inserts.

Dynamic LINQ, Select function, works on Enumerable, but not Queryable

I have been fiddling with dynamic LINQ for some time now, but I have yet to learn its secrets.
I have an expression that I want to parse that looks like this:
"document.LineItems.Select(i => i.Credit).Sum();"
During parsing of this I reach a point where I need to call a Select function on LineItems Collection. I am using factory method of Expression.Call:
Expression.Call(
typeof(Queryable),
"Select",
new Type[] { typeof(LineItem), typeof(decimal?) },
expr,
Expression.Lambda(expression, new ParameterExpression[] { Expression.Parameter(typeof(LineItem) }));
At this moment
expr: document.LineItems [ICollection<LineItem>]
expression: LineItem.Credit [decimal?]
none of which is materialized yet. I am just building Expression Tree at the moment.
Now, the problem:
This Expresssion.Call throws and Exception: "No generic method 'Select' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments";
I resolve it easily by looking for 'Select' in 'System.Linq.Enumerable' instead of 'Queryable' by changing first argument of Expression.Call.
But, that's not quite that I want. I don't want all LineItems hauled in only to calculate Sum(), which would obviously be case with Enumerable. I want Queryable to work.
Also, for the last part of parsing - Sum(), I also need to go with Enumerable Sum(), because Queryable Sum() throws same Exception.
I have checked MSDN, both signatures of 'Select' function are identical, so i really cannot see why would one work and other not.
Any help or pointers would be aprreciated.
Regards,
The problem is that LineItems is an ICollection<LineItem>, while the first parameter to Queryable.Select requires an IQueryable<LineItem>. ICollection<T> only implements IEnumerable<T> which is why it works for Enumerable.Select.
You will need to change the type of expr to be IQueryable<LineItem>.
You should be able to do this with the Queryable.AsQueryable method. The following function creates an expression to sum the credit property values for a given Document instance:
public static Expression<Func<decimal?>> CreateSumLineItemsExpr(Document document)
{
var docExpr = Expression.Constant(document);
var itemsExpr = Expression.Property(docExpr, "LineItems");
Expression<Func<LineItem, decimal?>> selector = i => i.Credit;
var queryableExpr = Expression.Call(typeof(Queryable), "AsQueryable", new[] { typeof(LineItem) }, itemsExpr);
var selectExpr = Expression.Call(typeof(Queryable), "Select", new[] { typeof(LineItem), typeof(decimal?) }, queryableExpr, selector);
var sumExpr = Expression.Call(typeof(Queryable), "Sum", null, selectExpr);
return Expression.Lambda<Func<decimal?>>(sumExpr);
}

How to create NotStartsWith Expression tree

I'm using jqGrid to display some data to users. jqGrid has search functionality that does string compares like Equals, NotEquals, Contains, StartsWith, NotStartsWith, etc.
When I use StartsWith I get valid results (looks like this):
Expression condition = Expression.Call(memberAccess,
typeof(string).GetMethod("StartsWith"),
Expression.Constant(value));
Since DoesNotStartWith doesn't exist I created it:
public static bool NotStartsWith(this string s, string value)
{
return !s.StartsWith(value);
}
This works, and I can create a string and call this method like so:
string myStr = "Hello World";
bool startsWith = myStr.NotStartsWith("Hello"); // false
So now I can create/call the expression like so:
Expression condition = Expression.Call(memberAccess,
typeof(string).GetMethod("NotStartsWith"),
Expression.Constant(value));
But I get a ArgumentNullException was unhandled by user code: Value cannot be null.
Parameter name: method error.
Does anyone know why this doesn't work or a better way to approach this?
You're checking for method NotStartsWith on type string, which doesn't exist. Instead of typeof(string), try typeof(ExtensionMethodClass), using the class where you put your NotStartsWith extension method. Extension methods don't actual exist on the type itself, they just act like they do.
Edit: Also rearrange your Expression.Call call like this,
Expression condition = Expression.Call(
typeof(string).GetMethod("NotStartsWith"),
memberAccess,
Expression.Constant(value));
The overload you are using expects an instance method, this overload expects a static method, based on the SO post you referred to. See here, http://msdn.microsoft.com/en-us/library/dd324092.aspx
i know the ask was answered, but another approach is available and simple:
Expression condition = Expression.Call(memberAccess,
typeof(string).GetMethod("StartsWith"),
Expression.Constant(value));
condition = Expression.Not(condition);
and... done! just have to negate the expression.

Replacing parameters in a lambda expression

I had a part of code that takes in lambda expressions at runtime, which I can then compile and invoke.
Something thing;
Expression<Action<Something>> expression = (c => c.DoWork());
Delegate del = expression.Compile();
del.DynamicInvoke(thing);
In order to save execution time, I stored those compiled delegates in a cache, a Dictionary<String, Delegate> which the key is the lambda expression string.
cache.Add("(Something)c => c.DoWork()", del);
For exact same calls, it worked fine. However I realized that I could receive equivalent lambdas, such as "d => d.DoWork()", which I should actually use the same delegate for, and I wasn't.
This got me wondering if there was a clean way (read "not using String.Replace", I already did that as a temporary fix) to replace the elements in a lambda expression, like maybe replacing them by arg0 so that both
(c => c.DoWork()) and (d => d.DoWork())
are transformed and compared as (arg0 => arg0.DoWork()) by using something fuctionnally similar to injecting a Expression.Parameter(Type, Name) in a lambda.
Is that possible ? (Answers can include C#4.0)
I used strings, since it was the easisest way for me. You can't manually change the name of the parameter expression (it has the "Name" property, but it is read-only), so you must construct a new expression from pieces. What I did is created a "nameless" parameter (actually, it gets an autogenerated name in this case, which is "Param_0") and then created a new expression almost the same as the old one, but using the new parameter.
public static void Main()
{
String thing = "test";
Expression<Action<String>> expression = c => c.ToUpper();
Delegate del = expression.Compile();
del.DynamicInvoke(thing);
Dictionary<String, Delegate> cache = new Dictionary<String, Delegate>();
cache.Add(GenerateKey(expression), del);
Expression<Action<String>> expression1 = d => d.ToUpper();
var test = cache.ContainsKey(GenerateKey(expression1));
Console.WriteLine(test);
}
public static string GenerateKey(Expression<Action<String>> expr)
{
ParameterExpression newParam = Expression.Parameter(expr.Parameters[0].Type);
Expression newExprBody = Expression.Call(newParam, ((MethodCallExpression)expr.Body).Method);
Expression<Action<String>> newExpr = Expression.Lambda<Action<String>>(newExprBody, newParam);
return newExpr.ToString();
}
There's more to a lambda expression than just the text. Lambdas are re-written by the compiler into something much more complicated. For example, they may close over variables (which could include other delegates). This means that two lambdas could look exactly the same, but perform completely different actions.
So you might have luck caching your compiled expression, but you need to give them a more meaningful name for the key than just the text of the expression. Otherwise no amount of argument substitution will help you.

Categories