0

I have several autocomplete actions, one of them is listed below. Instead of writing different predicates for each autocomplete Where method, I have created an autoCompletePredicate. Since I have multiple autocompletes I am using Reflection to get the Property which is required for that specific AutoComplete and use that Property in my autoCompletePredicate.

I have following code which is working alright.

static string param1, param2;
static PropertyInfo[] properties;
static PropertyInfo prop1, prop2;

public IHttpActionResult GetAutComplete(string term, string dependent)
{
    int pagerSize = 10;

    properties = new MyObject().GetType().GetProperties();
    prop1 = properties.Where(p => p.Name.ToUpper().Equals("PROP1")).FirstOrDefault();
    prop2 = properties.Where(p => p.Name.ToUpper().Equals("PROP2")).FirstOrDefault();
    param1 = term;
    param2 = dependent;

    return Json(context.MyObject.Where(autoCompletePredicate).Select(r => new { label = r.PROP1 }).Distinct().OrderBy(r => r.label).Take(pagerSize).ToList());
}

Func<MyObject, int, bool> autoCompletePredicate = (GF, index) =>
{
    bool isFound = false;
    string term, dependent;

    term = prop1.GetValue(GF).ToString();
    dependent = prop2.GetValue(GF).ToString();

    var termFound = term.Contains(param1.ToUpper());
    var dependentFound = String.IsNullOrEmpty(param2) ? true : dependent.Contains(param2.ToUpper());

    isFound = termFound && dependentFound;

    return isFound;
};

How can I change this code into Expression. I tried below code which compiled fine but at runtime I got the following error

public static Expression<Func<MyObject, bool>> AutoCompleteExpression()
{
    return r => prop1.GetValue(r).ToString().Contains(param1.ToUpper()) && (String.IsNullOrEmpty(param2) ? true : prop2.GetValue(r).ToString().Contains(param2.ToUpper()));
}

"LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression."

I looked at the following post which makes absolute sense, but I am not sure how I can use that example in my scenario (which is dynamically finding properties using Reflection).

Also, what I would like to know what can be advantage of using Expression vs Func (specially in my case)

Community
  • 1
  • 1
programmerboy
  • 341
  • 5
  • 15
  • The provider is trying to take that expression and create SQL from it, which it can't in this case. You _could_ execute those expressions in Linq-to-Objects but it's not clear at all what it's supposed to do. – D Stanley Oct 21 '16 at 16:54
  • @DStanley I provided some explanation at the start of my question. Please see if it makes sense. – programmerboy Oct 21 '16 at 17:04

1 Answers1

1

You are trying to trying to execute Contains method on string and want that to be represented in the ExpressionTrees, following is the code you need:

Create String Extension method - Contains: (Case Insensitive)

public static class StringExtensions
{
    public static bool Contains(this string source, string toCheck)
    {
        return source.IndexOf(toCheck, StringComparison.OrdinalIgnoreCase) >= 0;
    }
}

Create the AutoCompleteExpression method as follows, it returns Func<MyObject, bool>:

public static Func<MyObject, bool> AutoCompleteExpression()
{
   // Create ParameterExpression
   ParameterExpression parameterType = Expression.Parameter(typeof(MyObject), "object");

   // Create MemberExpression for Columns
   MemberExpression typeColumnProp1 = Expression.Property(parameterType, "PROP1");
   MemberExpression typeColumnProp2 = Expression.Property(parameterType, "PROP2");

   // Create MethoIndo
   MethodInfo containsMethodInfo = typeof(StringExtensions).GetMethod("Contains",new[] { typeof(string), typeof(string) },null);    

   // Create ConstantExpression values
   ConstantExpression constant1 = Expression.Constant(param1, typeof(string));
   ConstantExpression constant2 = Expression.Constant(param2, typeof(string));

   // Expression for calling methods
   MethodCallExpression expression1 = Expression.Call(null, containsMethodInfo, typeColumnProp1, constant1);
   MethodCallExpression expression2 = Expression.Call(null, containsMethodInfo, typeColumnProp2, constant2);

   // Combine `MethodCallExpression` to create Binary Expression
   BinaryExpression resultExpression = Expression.And(expression1,expression2);

    // Compile Expression tree to fetch `Func<MyObject, bool>`
   return Expression.Lambda<Func<MyObject, bool>>(resultExpression, parameterType).Compile();
 }

It is possible to add lot more flexibility by defining custom extension methods and combining expressions using And / Or

Mrinal Kamboj
  • 11,300
  • 5
  • 40
  • 74
  • Thanks for the answer. It really helps me understand Expression Tree. However, I noticed that you are building `Expression Tree` and returning it as a FUNC. If I am returning `FUNC` then whats the point of writing Expression Tree. One more thing I returned your method as Expression but still got this error `LINQ to Entities does not recognize the method 'Boolean Contains(System.String, System.String)' method, and this method cannot be translated into a store expression.` – programmerboy Oct 21 '16 at 20:11
  • 1
    Regarding first question, please understand everywhere you create Expression trees still would compile it into Func to use it in Linq to Entities code, since no API will directly take the Expression tree, only IQueryable takes the Expression trees directly. So you may return Expression tree from the method, but would still need compilation to `Func` and better that is one time process, not compile every time, since that would impact the perfromance. Expression trees are different, since you tell system what you need to do, not how you need to do – Mrinal Kamboj Oct 22 '16 at 01:52
  • Regarding second question, this is version of my working code, please ensure you create an extension method using `StringExtensions` class as posted in answer and if any standard string in your code, which is not having Contains method by default, can access it and give correct answer, then this code will work. Contains is otherwise meant for the collections not a string, this is a workaround. – Mrinal Kamboj Oct 22 '16 at 02:01