locked
EF Dynamic Query - Like RRS feed

  • Question

  • After successfully implementing a like function with use of string function 'contains' I noticed that wildcards are escaped when translated to sql.

    Eg. When I search for a street starting with p and has number 13 in it. I replace all space with '%' but EF escapes it. The output query then looks like this:

    SELECT " FROM Customer WHERE (LOWER([Street]) LIKE N'%p~%13%' ESCAPE N'~')

    In one of the blogs a suggestion was to use patindex instead. I just don't know how to implement this.

    My current code looks like this:

    public static Expression<Func<T, bool>> Create<T>(string propertyName, ComparisonOperators comparisonOperator, dynamic comparedValue1, dynamic comparedValue2 = null)
    {
        ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "x");
        MemberExpression memberExpression = Expression.MakeMemberAccess(parameterExpression, typeof(T).GetProperty(propertyName));
        ConstantExpression constantExpression = Expression.Constant(comparedValue1, comparedValue1.GetType());
        Expression expressionBody = null;
    
        switch (comparisonOperator)
        {
            ...
    
            case ComparisonOperators.Contains:
                expressionBody = Expression.Call(GetLowerCasePropertyAccess(memberExpression), ContainsMethod, Expression.Constant(comparedValue1.ToLower()));
                break;
        }
    
        return Expression.Lambda<Func<T, bool>>(expressionBody, new ParameterExpression[] { parameterExpression });
    }
    
    private static MethodCallExpression GetLowerCasePropertyAccess(MemberExpression propertyAccess)
    {
        var stringExpression = GetConvertToStringExpression(propertyAccess);
        if (stringExpression == null)
            throw new Exception(string.Format("Not supported property type {0}", propertyAccess.Type));
    
        return Expression.Call(stringExpression, typeof(string).GetMethod("ToLower", Type.EmptyTypes));
    }
    
    private static Expression GetConvertToStringExpression(Expression e)
    {
        // if property string - no cast needed
        // else - use SqlFunction.StringConvert(double?) or SqlFunction.StringConvert(decimal?);
        Expression strExpression = null;
        if (e.Type == typeof(string)) strExpression = e;
        else
        {
            var systemType = Nullable.GetUnderlyingType(e.Type) ?? e.Type;
            if (systemType == typeof(int) || systemType == typeof(long) || systemType == typeof(double) || systemType == typeof(short) || systemType == typeof(byte))
            {
                // cast int to double
                var doubleExpr = Expression.Convert(e, typeof(double?));
                strExpression = Expression.Call(StringConvertMethodDouble, doubleExpr);
            }
            else if (systemType == typeof(decimal))
            {
                // call decimal version of StringConvert method
                // cast to nullable decimal
                var decimalExpr = Expression.Convert(e, typeof(decimal?));
                strExpression = Expression.Call(StringConvertMethodDecimal, decimalExpr);
            }
        }
        return strExpression;
    }
    
    private static readonly MethodInfo ContainsMethod = typeof(String).GetMethod("Contains", new Type[] { typeof(String) });
    private static readonly MethodInfo StringConvertMethodDouble = typeof(SqlFunctions).GetMethod("StringConvert", new Type[] { typeof(double?) });
    private static readonly MethodInfo StringConvertMethodDecimal = typeof(SqlFunctions).GetMethod("StringConvert", new Type[] { typeof(decimal?) });

    I need to replace the method call to use patindex instead:

    expressionBody = Expression.Call(GetLowerCasePropertyAccess(memberExpression), ContainsMethod, Expression.Constant(comparedValue1.ToLower()));

     
    • Edited by Syslock Thursday, September 5, 2013 7:33 AM
    • Moved by Lilia gong - MSFT Friday, September 6, 2013 1:32 AM Data Platform
    Thursday, September 5, 2013 6:20 AM

Answers

  • Hi,

    Maybe codes like below is what you want:

    static void Main(string[] args) { using (DataClasses1DataContext dataContext = new DataClasses1DataContext()) { var productNames = new string[] { "P", "13" }; IQueryable<Course> custs = dataContext.Courses; IQueryable<Course> query = dataContext.Courses.AsQueryable().Provider.CreateQuery<Course>(likeExpression(custs, productNames)); } } private static Expression likeExpression(IQueryable<Course> custs, string[] productNames) { ParameterExpression param = Expression.Parameter(typeof(Course), "c"); //c.City.StartsWith("P") Expression left = Expression.Property(param, typeof(Course).GetProperty("CourseName")); Expression right1 = Expression.Constant("P"); Expression right2 = Expression.Constant("13"); MethodCallExpression filter1 = Expression.Call(left, typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }), right1); MethodCallExpression filter2 = Expression.Call(left, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), right2); LambdaExpression pred = Expression.Lambda(Expression.And(filter1, filter2), param); //Where(c=>c.City.StartsWith("London")) MethodCallExpression whereCallExpression = Expression.Call( typeof(Queryable), "Where", new Type[] { typeof(Course) }, Expression.Constant(custs), pred); return whereCallExpression; }

    More information about how to use Expression Trees to Build Dynamic Queries:

    http://msdn.microsoft.com/en-us/library/vstudio/bb882637.aspx

    Regards.


    <THE CONTENT IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED>
    Thanks
    MSDN Community Support

    Please remember to "Mark as Answer" the responses that resolved your issue. It is a common way to recognize those who have helped you, and makes it easier for other visitors to find the resolution later.

    • Proposed as answer by Fred Bao Thursday, September 12, 2013 1:43 AM
    • Marked as answer by Fred Bao Saturday, September 14, 2013 3:56 AM
    Wednesday, September 11, 2013 12:51 PM

All replies

  • Hi Syslock,
    Thanks for your posting.
    From your description, I think this issue is related to EF Dynamic Query. So Data Platform Development forums is more suitable for it, I move it to Data Platform Development forums.
    Data Platform Development forums : http://social.msdn.microsoft.com/Forums/en-US/home?category=dataplatformdev

    Hope these help.


    Lilia Gong <THE CONTENT IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED>
    Thanks
    MSDN Community Support

    Please remember to "Mark as Answer" the responses that resolved your issue. It is a common way to recognize those who have helped you, and makes it easier for other visitors to find the resolution later.

    Friday, September 6, 2013 1:31 AM
  • Hello,

    In my opinion, to implement the search for a street starting with p and has number 13 in it we can use Startwith and Contans() like below:

    var result = from p in db.Persons
                                  where p.Address.StartsWith("P") & p.Address.Contains("13")
                                  select p;

    Have a try.

    Regards.


    <THE CONTENT IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED>
    Thanks
    MSDN Community Support

    Please remember to "Mark as Answer" the responses that resolved your issue. It is a common way to recognize those who have helped you, and makes it easier for other visitors to find the resolution later.


    • Edited by Fred Bao Friday, September 6, 2013 4:31 AM Better
    • Proposed as answer by Fred Bao Saturday, September 14, 2013 3:56 AM
    Friday, September 6, 2013 3:59 AM
  • Hi

    I know how to construct the predicate but why I need is how to create the Expression like the above.

    Wednesday, September 11, 2013 6:18 AM
  • Hi,

    Maybe codes like below is what you want:

    static void Main(string[] args) { using (DataClasses1DataContext dataContext = new DataClasses1DataContext()) { var productNames = new string[] { "P", "13" }; IQueryable<Course> custs = dataContext.Courses; IQueryable<Course> query = dataContext.Courses.AsQueryable().Provider.CreateQuery<Course>(likeExpression(custs, productNames)); } } private static Expression likeExpression(IQueryable<Course> custs, string[] productNames) { ParameterExpression param = Expression.Parameter(typeof(Course), "c"); //c.City.StartsWith("P") Expression left = Expression.Property(param, typeof(Course).GetProperty("CourseName")); Expression right1 = Expression.Constant("P"); Expression right2 = Expression.Constant("13"); MethodCallExpression filter1 = Expression.Call(left, typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }), right1); MethodCallExpression filter2 = Expression.Call(left, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), right2); LambdaExpression pred = Expression.Lambda(Expression.And(filter1, filter2), param); //Where(c=>c.City.StartsWith("London")) MethodCallExpression whereCallExpression = Expression.Call( typeof(Queryable), "Where", new Type[] { typeof(Course) }, Expression.Constant(custs), pred); return whereCallExpression; }

    More information about how to use Expression Trees to Build Dynamic Queries:

    http://msdn.microsoft.com/en-us/library/vstudio/bb882637.aspx

    Regards.


    <THE CONTENT IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED>
    Thanks
    MSDN Community Support

    Please remember to "Mark as Answer" the responses that resolved your issue. It is a common way to recognize those who have helped you, and makes it easier for other visitors to find the resolution later.

    • Proposed as answer by Fred Bao Thursday, September 12, 2013 1:43 AM
    • Marked as answer by Fred Bao Saturday, September 14, 2013 3:56 AM
    Wednesday, September 11, 2013 12:51 PM