0

I have a service method called FindAll() that expects a expression/predicate parameter and returns all the matching rows.

Let's say I have a collection of Book objects and I want to get all the books with their names found in a list of strings. I'd have something like this:

var lstNames = { "Book1", "Book2" };
var matchedBooks = myService<Book>.FindAll(x => lstNames.Any(y => x.Name.Equals(y)));

I also have a number of other classes that all have the Name property so I'd like to build a dynamic expression which allows me to do something like:

var matchedObjs = myService<T>.FindAll(x => lstNames.Any(y => x.Name.Equals(y)));

How do I build such a dynamic expression?

notlkk
  • 1,231
  • 2
  • 23
  • 40
  • 1
    Either use an interface, or use dynamic variables – Jbjstam Feb 12 '17 at 23:13
  • don't want to do either. EF generates those classes, don't want to add an interface on it even if I could. Creating a dynamic expression seems to be the cleanest way. – notlkk Feb 13 '17 at 00:15
  • Any reason you are using `FindAll` rather than `Where`? – Abion47 Feb 13 '17 at 00:47
  • Also, is there a reason you want to avoid a fairly simple syntax like you have above? You can simplify a little bit: `x => lstNames.Contains(x.Name)` – DavidG Feb 13 '17 at 00:54
  • @DavidG, that's absolutely true. I can simplify this a lot more if I just use Contains. Thx. – notlkk Feb 13 '17 at 02:56
  • What you're thinking would require a little complex coding, if you really want to get a property `Name` out of generic classes. Perhaps take a look here: [Get property value from string using reflection in C#](http://stackoverflow.com/a/1197004/6741868), and use `Name` string to *try* to get a value. Of course, you will need lots of error checking. – Keyur PATEL Feb 13 '17 at 03:50
  • Should `FindAll()` be an expression tree compilable down to SQL or is it purely for c# code? – zaitsman Feb 13 '17 at 08:20

1 Answers1

0

Thanks to this answer: How to declare a Linq Expression variable in order to have it processed as a dbParameter

I propose you do this:

static Expression<Func<T, bool>> GetExpr<T> (string name, string value)
{
  ParameterExpression param = Expression.Parameter(typeof(T), "x");
  Expression prop = Expression.Property(param, name); // this is the property name, e.g. .Name
  Expression<Func<string>> valueLambda = () => value; // This is the value for == expression.
  Expression lookupExpression = Expression.Equal(prop, valueLambda.Body);

  Expression<Func<T, bool>> expr = Expression.Lambda<Func<T, bool>>(lookupExpression, param);

  return expr;
}

... or, for .Contains():

static Expression<Func<T, bool>> GetExprContains<T>(string name, string[] value)
{
  ParameterExpression param = Expression.Parameter(typeof(T), "x");
  Expression prop = Expression.Property(param, name); // this is the property name, e.g. .Name
  Expression<Func<string[]>> valueLambda = () => value; // This is the value for .Contains() expression.
  var mi =
    typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
      .FirstOrDefault(x => x.Name == "Contains" && x.GetParameters().Count() == 2)
      .MakeGenericMethod(typeof(string)); // Need methodinfo for .Contains(), might want to keep static somewhere
  var lookupExpr = Expression.Call(null, mi, valueLambda.Body, prop);

  Expression<Func<T, bool>> expr = Expression.Lambda<Func<T, bool>>(lookupExpr, param);

  return expr;
}

Tested, works with EF.

Community
  • 1
  • 1
zaitsman
  • 8,984
  • 6
  • 47
  • 79