How to apply an expression tree for IQueryable? - c#

Is there any way to apply a manually created expression tree from one IQueryable to another? For example:
IQueryable<string> p = Enumerable.Empty<string>().AsQueryable();
p = p.Where(pp => pp[0] == 'A');
p = p.Skip(2).Take(4);
p = p.OrderBy(pp => pp.Length);
var vv = new[] {"Afss", "Acv", "Adfv", "Bcvx", "Ng"}.AsQueryable();
// Filtering of the vv collection with expression from p
var filteredResult = vv.Filter(p.Expression);
Or we have to separately apply the Where and OrderBy expressions?

Simple CreateQuery call does not work, because provider uses data source of the passed expression.
Before executing expression you need to replace Enumerable.Empty<string>() by a collection with your data:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
public class Program
{
public static void Main()
{
IQueryable<string> p = Enumerable.Empty<string>().AsQueryable();
p = p.Where(pp => pp[0] == 'A');
p = p.Skip(2).Take(4);
p = p.OrderBy(pp => pp.Length);
var vv = new[] {"Afss", "Acv", "Adfv", "Bcvx", "Ng"}.AsQueryable();
// Filtering of the vv collection with expression from p
var expr = ExpressionTreeConstantReplacer.CopyAndReplace(p.Expression, typeof(EnumerableQuery<string>), vv);
var filteredResult = vv.Provider.CreateQuery<string>(expr);
Console.Write("Source: \t");
foreach(var temp in vv)
Console.Write("{0} ", temp);
Console.WriteLine();
Console.Write("Filtered: ");
foreach(var temp in filteredResult)
Console.WriteLine(temp);
}
class ExpressionTreeConstantReplacer<T> : ExpressionVisitor
{
Type originalType;
T replacementConstant;
internal ExpressionTreeConstantReplacer(Type originalType, T replacementConstant)
{
this.originalType = originalType;
this.replacementConstant = replacementConstant;
}
protected override Expression VisitConstant(ConstantExpression c)
{
return c.Type == originalType ? Expression.Constant(replacementConstant) : c;
}
}
class ExpressionTreeConstantReplacer
{
internal static Expression CopyAndReplace<T>(Expression expression, Type originalType, T replacementConstant)
{
var modifier = new ExpressionTreeConstantReplacer<T>(originalType, replacementConstant);
return modifier.Visit(expression);
}
}
}

You can access the provider of the query and use CreateQuery to create a query based on an expression, and you can get the expression from the other query to pass to it:
var filteredResult = vv.Provider.CreateQuery<string>(p.Expression);

I had a similar case in which I had to send filter/sort expressions over WCF. I could not find a way to use a single instance for both the filter and the sort expression. I ended up using SerializableExpression from InterLinq. Doesn't solve your particular question but I hope it helps.

Related

Lambda Expression for dynamic Object

I am trying to build a Lambda Expression for a table that has been created at run time.
The Expression is build fine but when I call Compile() method I get this error
"ParameterExpression of type 'cseval.Item' cannot be used for delegate parameter of type 'System.Object'"
this is my function
public Func<dynamic, Boolean> GetWhereExp(List<WhereCondition> SearchFieldList, dynamic item)
{
ParameterExpression pe = Expression.Parameter(item.GetType(), "c");
Expression combined = null;
if (SearchFieldList != null)
{
foreach (WhereCondition fieldItem in SearchFieldList)
{
//Expression for accessing Fields name property
Expression columnNameProperty = Expression.Property(pe, fieldItem.ColumName);
//the name constant to match
Expression columnValue = Expression.Constant(fieldItem.Value);
//the first expression: PatientantLastName = ?
Expression e1 = Expression.Equal(columnNameProperty, columnValue);
if (combined == null)
{
combined = e;
}
else
{
combined = Expression.And(combined, e);
}
}
}
var result = Expression.Lambda<Func<dynamic, bool>>(combined, pe);
return result.Compile();
}
I've changed dynamic to generics, this code works for me:
public Func<T, Boolean> GetWhereExp<T>(List<WhereCondition> SearchFieldList, T item)
{
var pe = Expression.Parameter(item.GetType(), "c");
Expression combined = null;
if (SearchFieldList != null)
{
foreach (var fieldItem in SearchFieldList)
{
var columnNameProperty = Expression.Property(pe, fieldItem.ColumName);
var columnValue = Expression.Constant(fieldItem.Value);
var e1 = Expression.Equal(columnNameProperty, columnValue);
combined = combined == null ? e1 : Expression.And(combined, e1);
}
}
var result = Expression.Lambda<Func<T, bool>>(combined, pe);
return result.Compile();
}
Small remark: your method returns function, not an expression, so the name 'GetWhereExp' is slightly incorrect. If you want to return function, imho, it's better to use reflection.
UPD: I use this code to test:
var expressions = new List<WhereCondition>
{
new WhereCondition("Column1", "xxx"),
new WhereCondition("Column2", "yyy"),
};
var item = new
{
Column1 = "xxx",
Column2 = "yyy"
};
var func = LinqExpr.GetWhereExp(expressions, (dynamic)item);
Console.WriteLine(new[] {item}.Count(a => func(a)));

Dynamically Modifying a LINQ Query Based on a Related Table

I am dynamically creating a LINQ query based on various search criteria.
As an example, let's say I am searching a Automobiles table, and I have an option to filter by ratings. I have two controls:
Compare type: [At least], [At most], [Less than], [Greater than], and [Equal].
Value: The value to compare the rating against.
So the user could, for example, select the compare type [At least] and the value 3, and my code needs to create a query that limits results to automobile ratings greater than or equal to 3.
I found a great solution given by VinayC in the question How to implement search functionality in C#/ASP.NET MVC. His DynamicWhere() method dynamically creates part of the expression that would produce the correct filter.
My problem is that my primary query type is Automobile but my ratings are in a separate table (Automobile.Ratings). How could I implement this same technique and filter on a type other than my primary query type?
Thanks for any tips.
Since the number of operations that you have is small, finite, and known at comiple time, you can simply handle it with a switch:
IQueryable<Something> query = GetQuery();
int ratingToComareWith = 1;
string operation = "Equal";
switch (operation)
{
case ("Equal"):
query = query.Where(item => item == ratingToComareWith);
break;
case ("Less Than"):
query = query.Where(item => item < ratingToComareWith);
break;
}
Here's an Entity Framework friendly alternative to expression building. Of course you will want to validate column and op to prevent SQL injection.
// User provided values
int value = 3;
string column = "Rating";
string op = "<";
// Dynamically built query
db.Database.SqlQuery<Automobile>(#"select distinct automobile.* from automobile
inner join ratings on ....
where [" + column + "] " + op + " #p0", value);
Here is a method to build conditions for nested collections or types for linq-to-entities.
Restructured for your needs:
public static Expression GetCondition(Expression parameter, object value, OperatorComparer operatorComparer, params string[] properties)
{
Expression resultExpression = null;
Expression childParameter, navigationPropertyPredicate;
Type childType = null;
if (properties.Count() > 1)
{
//build path
parameter = Expression.Property(parameter, properties[0]);
var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
//if it´s a collection we later need to use the predicate in the methodexpressioncall
if (isCollection)
{
childType = parameter.Type.GetGenericArguments()[0];
childParameter = Expression.Parameter(childType, childType.Name);
}
else
{
childParameter = parameter;
}
//skip current property and get navigation property expression recursivly
var innerProperties = properties.Skip(1).ToArray();
navigationPropertyPredicate = GetCondition(childParameter, test, innerProperties);
if (isCollection)
{
//build methodexpressioncall
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 = MakeLambda(parameter, navigationPropertyPredicate);
}
else
{
resultExpression = navigationPropertyPredicate;
}
}
else
{
var childProperty = parameter.Type.GetProperty(properties[0]);
var left = Expression.Property(parameter, childProperty);
var right = Expression.Constant(value,value.GetType());
if(!new List<OperatorComparer> {OperatorComparer.Contains,OperatorComparer.StartsWith}.Contains(operatorComparer))
{
navigationPropertyPredicate = Expression.MakeBinary((ExpressionType)operatorComparer,left, right);
}
else
{
var method = GetMethod(childProperty.PropertyType, operatorComparer); //get property by enum-name from type
navigationPropertyPredicate = Expression.Call(left, method, right);
}
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
return resultExpression;
}
private static MethodInfo GetMethod(Type type,OperatorComparer operatorComparer)
{
var method = type.GetMethod(Enum.GetName(typeof(OperatorComparer),operatorComparer));
return method;
}
public enum OperatorComparer
{
Equals = ExpressionType.Equal,
Contains,
StartsWith,
GreaterThan = ExpressionType.GreaterThan
....
}
private static Expression MakeLambda(Expression parameter, Expression predicate)
{
var resultParameterVisitor = new ParameterVisitor();
resultParameterVisitor.Visit(parameter);
var resultParameter = resultParameterVisitor.Parameter;
return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
}
private class ParameterVisitor : ExpressionVisitor
{
public Expression Parameter
{
get;
private set;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Parameter = node;
return node;
}
}
}
You could replace the params string[] with params Expression(Func(T,object)), if you want. Would need some more work to do it that way. You would need to definie nested collections with a syntax like
item => item.nestedCollection.Select(nested => nested.Property)
and rewrite the expression with the help of an expressionvisitor.

Lambda to SQL Translation

So I am having fun with myself and C#, by creating a nice Data Access Layer.
I have the following method that translates a simple expression to a SQL where clause, but it only works with the following
var people = DataAccessLayer.SelectAllPeople(x => x.Name == "Donald");
//Do some changes to the list
people[0].Surname = "Jansen"
var m = p.BuildUpdateQuerry(people[0], x => x.PersonID == 1);
I get the following result
UPDATE People SET Name='Donald',Surname='Jansen' WHERE (PersonID = 1)
But now If I do the following
var m = p.BuildUpdateQuerry(people[0], x => x.PersonID == people[0].PersonID);
I get the following Result
UPDATE People SET Name='Donald',Surname='Jansen' WHERE (PersonID = value(ReflectionExampleByDonaldJansen.Program+<>c__DisplayClass0).people.get_Item(0).PersonID)
My method I am using to Convert the Lambda to String is
public static string GetWhereClause<T>(Expression<Func<T, bool>> expression)
{
var name = expression.Parameters[0].ToString();
var body = expression.Body.ToString().Replace("\"", "'");
body = body.Replace("OrElse", "OR");
body = body.Replace("AndAlso", "AND");
body = body.Replace("==", "=");
body = body.Replace("!=", "<>");
body = body.Replace(string.Format("{0}.", name), "");
return body;
}
So far this is very basic and real fun to do, but I have no Idea how to overcome this XDXD, any Suggestions or Codes ?
I managed to solve it myself, hehehe here is what I did, not finnished yet but maybe someone else might find it usefull
public static string GetWhereClause<T>(Expression<Func<T, bool>> expression)
{
return GetValueAsString(expression.Body);
}
public static string GetValueAsString(Expression expression)
{
var value = "";
var equalty = "";
var left = GetLeftNode(expression);
var right = GetRightNode(expression);
if (expression.NodeType == ExpressionType.Equal)
{
equalty = "=";
}
if (expression.NodeType == ExpressionType.AndAlso)
{
equalty = "AND";
}
if (expression.NodeType == ExpressionType.OrElse)
{
equalty = "OR";
}
if (expression.NodeType == ExpressionType.NotEqual)
{
equalty = "<>";
}
if (left is MemberExpression)
{
var leftMem = left as MemberExpression;
value = string.Format("({0}{1}'{2}')", leftMem.Member.Name, equalty, "{0}");
}
if (right is ConstantExpression)
{
var rightConst = right as ConstantExpression;
value = string.Format(value, rightConst.Value);
}
if (right is MemberExpression)
{
var rightMem = right as MemberExpression;
var rightConst = rightMem.Expression as ConstantExpression;
var member = rightMem.Member.DeclaringType;
var type = rightMem.Member.MemberType;
var val = member.GetField(rightMem.Member.Name).GetValue(rightConst.Value);
value = string.Format(value, val);
}
if (value == "")
{
var leftVal = GetValueAsString(left);
var rigthVal = GetValueAsString(right);
value = string.Format("({0} {1} {2})", leftVal, equalty, rigthVal);
}
return value;
}
private static Expression GetLeftNode(Expression expression)
{
dynamic exp = expression;
return ((Expression)exp.Left);
}
private static Expression GetRightNode(Expression expression)
{
dynamic exp = expression;
return ((Expression)exp.Right);
}
You can use the inbuilt extension methods of System.Linq to convert lambda expressions to SQL.
See the code below...
var Enames = emp.Employees.Select ( e => e.EmployeeName );
Console.WriteLine ( Enames );
The output which I got..
SELECT
[Extent1].[EmployeeName] AS [EmployeeName]
FROM [dbo].[Employee] AS [Extent1]
I had the same problem and started to solve it some time ago. Take a look on LambdaSql.
For now it contains basic scenarios for select clause and where filters. Setting fields, where, group by, having, order by, joins, nested queries are already supported. Insert, Update and Delete are going to be supported later.
Example:
var qry = new SqlSelect
(
new SqlSelect<Person>()
.AddFields(p => p.Id, p => p.Name)
.Where(SqlFilter<Person>.From(p => p.Name).EqualTo("Sergey"))
, new SqlAlias("inner")
).AddFields<Person>(p => p.Name);
Console.WriteLine(qry.ParametricSql);
Console.WriteLine("---");
Console.WriteLine(string.Join("; ", qry.Parameters
.Select(p => $"Name = {p.ParameterName}, Value = {p.Value}")));
Output:
SELECT
inner.Name
FROM
(
SELECT
pe.Id, pe.Name
FROM
Person pe
WHERE
pe.Name = #w0
) AS inner
---
Name = #w0, Value = Sergey
See more here https://github.com/Serg046/LambdaSql

dynamic orderBy using linq dynamic

I am trying to convert this Func to use string values using linq.dynamic.
Currently I have
Func<IQueryable<Customer>, IOrderedQueryable<Customer>> orderBy = o => o.OrderBy(c => c.Postcode);
But I want to do
string sortItem = "customer";
string order = "ASC"
Func<IQueryable<Customer>, IOrderedQueryable<Customer>> orderBy = o => o.OrderBy(sortItem + " " + order);
I am using the Linq.Dynamic library but I am unable to get it to work with the function.
Any help...
Like the other answer suggests, this may not be possible. However, I wanted to post some code which I wrote recently to do something similar:
// single column sorting support
var sortColumnIndex = Convert.ToInt32(Request["iSortCol_0"]);
Func<LegalComplianceDatatable, string> orderingFunction = (c => sortColumnIndex == 0 ? c.HomeCountry :
sortColumnIndex == 1 ? c.HostCountry :
sortColumnIndex == 2 ? c.YearOneRate :
sortColumnIndex == 3 ? c.YearOtherRate :
sortColumnIndex == 4 ? c.RateType :
c.HostCountry);
if (Request["sSortDir_0"] == "desc")
{
filteredResults = filteredResults.OrderByDescending(orderingFunction);
}
else
{
filteredResults = filteredResults.OrderBy(orderingFunction);
}
I haven't used Linq.Dynamic but you can achieve this if you are comfortable building your own expression tree. For example:
public static class IQueryableExtension
{
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName)
{
var memberProp = typeof(T).GetProperty(propertyName);
var method = typeof(IQueryableExtension).GetMethod("OrderByInternal")
.MakeGenericMethod(typeof(T), memberProp.PropertyType);
return (IOrderedQueryable<T>)method.Invoke(null, new object[] { query, memberProp });
}
public static IOrderedQueryable<T> OrderByInternal<T, TProp>(IQueryable<T> query, PropertyInfo memberProperty)
{
if (memberProperty.PropertyType != typeof(TProp)) throw new Exception();
var thisArg = Expression.Parameter(typeof(T));
var lamba = Expression.Lambda<Func<T, TProp>>(Expression.Property(thisArg, memberProperty), thisArg);
return query.OrderBy(lamba);
}
}
And you can use this like:
IQueryable<Customer> query; // Some query
query = query.OrderBy("Name"); // Will return an IOrderedQueryable<Customer>
This is the logic for ascending sort, without checking (you will need to make sure the property exists, and so on) and some things can be optimized (the reflected method invocation for example). It should get you started.

Like operator or using wildcards in LINQ to Entities

I'm using LINQ 2 Entities.
Following is the problem:
string str = '%test%.doc%'
.Contains(str) // converts this into LIKE '%~%test~%.doc~%%'
Expected Conversion: LIKE '%test%.doc%'
If it was LINQ 2 SQL, I could have used SqlMethods.Like as somebody answered it in my previous question. But now as I'm using L2E not L2S, I need other solution.
The SQL method PATINDEX provides the same functionality as LIKE. Therefore, you can use the SqlFunctions.PatIndex method:
.Where(x => SqlFunctions.PatIndex("%test%.doc%", x.MySearchField) > 0)
Following on from Magnus' correct answer, here is an extension method that can be re-used, as I needed in my project.
public static class LinqExtensions
{
public static Expression<Func<T, bool>> WildCardWhere<T>(this Expression<Func<T, bool>> source, Expression<Func<T, string>> selector, string terms, char separator)
{
if (terms == null || selector == null)
return source;
foreach (string term in terms.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries))
{
string current = term;
source = source.And(
Expression.Lambda<Func<T, bool>>(
Expression.Call(selector.Body, "Contains", null, Expression.Constant(current)),
selector.Parameters[0]
)
);
}
return source;
}
}
Usage:
var terms = "%test%.doc%";
Expression<Func<Doc, bool>> whereClause = d => d;
whereClause = whereClause.WildCardWhere(d => d.docName, terms, '%');
whereClause = whereClause.WildCardWhere(d => d.someOtherProperty, "another%string%of%terms", '%');
var result = ListOfDocs.Where(whereClause).ToList();
The extension makes use of the predicate builder at http://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/. The resulting sql does a single table scan of the table, no matter how many terms are in there. Jo Vdb has an example you could start from if you wanted an extension of iQueryable instead.
You can try use this article, where author describes how to build a LIKE statement with wildcard characters in LINQ to Entities.
EDIT: Since the original link is now dead, here is the original extension class (as per Jon Koeter in the comments) and usage example.
Extension:
public static class LinqHelper
{
//Support IQueryable (Linq to Entities)
public static IQueryable<TSource> WhereLike<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, string>> valueSelector, string value, char wildcard)
{
return source.Where(BuildLikeExpression(valueSelector, value, wildcard));
}
//Support IEnumerable (Linq to objects)
public static IEnumerable<TSource> WhereLike<TSource>(this IEnumerable<TSource> sequence, Func<TSource, string> expression, string value, char wildcard)
{
var regEx = WildcardToRegex(value, wildcard);
//Prevent multiple enumeration:
var arraySequence = sequence as TSource[] ?? sequence.ToArray();
try
{
return arraySequence.Where(item => Regex.IsMatch(expression(item), regEx));
}
catch (ArgumentNullException)
{
return arraySequence;
}
}
//Used for the IEnumerable support
private static string WildcardToRegex(string value, char wildcard)
{
return "(?i:^" + Regex.Escape(value).Replace("\\" + wildcard, "." + wildcard) + "$)";
}
//Used for the IQueryable support
private static Expression<Func<TElement, bool>> BuildLikeExpression<TElement>(Expression<Func<TElement, string>> valueSelector, string value, char wildcard)
{
if (valueSelector == null) throw new ArgumentNullException("valueSelector");
var method = GetLikeMethod(value, wildcard);
value = value.Trim(wildcard);
var body = Expression.Call(valueSelector.Body, method, Expression.Constant(value));
var parameter = valueSelector.Parameters.Single();
return Expression.Lambda<Func<TElement, bool>>(body, parameter);
}
private static MethodInfo GetLikeMethod(string value, char wildcard)
{
var methodName = "Equals";
var textLength = value.Length;
value = value.TrimEnd(wildcard);
if (textLength > value.Length)
{
methodName = "StartsWith";
textLength = value.Length;
}
value = value.TrimStart(wildcard);
if (textLength > value.Length)
{
methodName = (methodName == "StartsWith") ? "Contains" : "EndsWith";
}
var stringType = typeof(string);
return stringType.GetMethod(methodName, new[] { stringType });
}
}
Usage Example:
string strEmailToFind = "%#yahoo.com"
IQueryable<User> myUsers = entities.Users.WhereLike(u => u.EmailAddress, strEmailToFind, '%');
or, if you expect your users to be more accustomed to Windows Explorer-styled wildcards:
string strEmailToFind = "*#yahoo.com"
IQueryable<User> myUsers = entities.Users.WhereLike(u => u.EmailAddress, strEmailToFind, '*');
Use a regular expression...
The following will print out all of the files in the current directory that match test.doc* (dos wildcard style - which I believe is what you're asking for)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
namespace RegexFileTester
{
class Program
{
static void Main(string[] args)
{
string[] _files = Directory.GetFiles(".");
var _fileMatches = from i in _files
where Regex.IsMatch(i, ".*test*.doc.*")
//where Regex.IsMatch(i, ".*cs")
select i;
foreach(var _file in _fileMatches)
{
Console.WriteLine(_file);
}
}
}
}
Split the String
var str = "%test%.doc%";
var arr = str.Split(new[]{'%'} ,StringSplitOptions.RemoveEmptyEntries);
var q = tblUsers.Select (u => u);
foreach (var item in arr)
{
var localItem = item;
q = q.Where (x => x.userName.Contains(localItem));
}
So I was trying the same thing - trying to pair down a List to return all candidates that matched a SearchTerm. I wanted it so that if a user typed "Arizona" it would return everything regardless of case that had Arizona. Also, if the user typed "Arizona Cha", it would return items like "Arizona License Change". The following worked:
private List<Certification> GetCertListBySearchString()
{
string[] searchTerms = SearchString.Split(' ');
List<Certification> allCerts = _context.Certifications.ToList();
allCerts = searchTerms.Aggregate(allCerts, (current, thisSearchString) => (from ac in current
where ac.Name.ToUpper().Contains(thisSearchString.ToUpper())
select ac).ToList());
return allCerts;
}

Categories