What you are trying to do is actually more complicated than it seems, because it involves manually combining a Person
property expression with an expression that represents an invocation to string.Contains(searchTerm)
.
When you can directly write a complete lambda expresion like
p => p.FirstName.Contains(searchTerm)
...it's not hard at all, because the compiler does all the heavy lifting for you. But when you have to combine the expression pieces manually, as in this case, it can quickly become messy.
I haven't tried octavioccl
's answer, but if it works as advertised, it's a very elegant solution, and I would definitely use that if possible.
However, if anything, to gain some appreciation for all the work the compiler does for us, here is how you can solve your problem without any external libraries (uses a few C# 6 features, but it can easily be tweaked for older versions):
public List<Person> SearchPeople(string searchTerm, Expression<Func<Person, string>> personProperty)
{
// Note: Explanatory comments below assume that the "personProperty" lambda expression is:
// p => p.FirstName
// Get MethodInfo for "string.Contains(string)" method.
var stringContainsMethod = typeof(string).GetMethod(
nameof(string.Contains),
new Type[] { typeof(string) });
// Create a closure class around searchTerm.
// Using this technique allows EF to use parameter binding
// when building the SQL query.
// In contrast, simply using "Expression.Constant(searchTerm)",
// makes EF hard-code the string in the SQL, which is not usually desirable.
var closure = new { SearchTerm = searchTerm };
var searchTermProperty = Expression.Property(
Expression.Constant(closure), // closure
nameof(closure.SearchTerm)); // SearchTerm
// This forms the complete statement: p.FirstName.Contains(closure.SearchTerm)
var completeStatement = Expression.Call(
personProperty.Body, // p.FirstName
stringContainsMethod, // .Contains()
searchTermProperty); // closure.SearchTerm
// This forms the complete lambda: p => p.FirstName.Contains(closure.SearchTerm)
var whereClauseLambda = Expression.Lambda<Func<Person, bool>>(
completeStatement, // p.FirstName.Contains(closure.SearchTerm)
personProperty.Parameters[0]); // p
// Execute query using constructed lambda.
return myDbContext.Persons.Where(whereClauseLambda).ToList();
}
You can then invoke the method like this:
foreach (var person in SearchPeople("joe", p => p.FirstName))
{
Console.WriteLine($"First Name: {person.FirstName}, Last Name: {person.LastName}");
}
Expression<Func<T,U>>
instead of just aFunc
. Look at this answer: stackoverflow.com/a/10498056/359157 – TyCobb