8

Let's suppose I have the following class:

public class Show
{
    public string Language { get; set; }
    public string Name { get; set; }
}

Based on this information, my goal is to create a lambda expression like this:

g => g.Language == lang && g.Name == name

lang and name are local variables I would like to add as constant values when creating the expression.

As you can see, the compiled function would be of type Func<Genre, bool>

To help you understand more clearly, I would like to achieve something similar to this:

string lang = "en";
string name = "comedy";
Genre genre = new Genre { Language = "en", Name = "comedy" };
Expression<Func<Genre, bool>> expression = CreateExpression(genre, lang, name);
// expression = (g => g.Language == "en" && g.Name == "comedy")

I am aware of the existence of expression trees but I'm pretty much new to this topic so I don't even know how to start.

Can this problem be solved? How can I create such expression dynamically?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Matias Cicero
  • 25,439
  • 13
  • 82
  • 154
  • i'm guessing the problem is more complicated in practice than you have described, right? If you know the properties that you are testing ahead of time, can't you can write a function that returns a function? – kaveman Jul 08 '15 at 01:40
  • @kaveman In reality, it is, you're right. It is just too complicated for me to explain without probably confusing you and even myself. I have a repository interface that can query my entities using an expression. The problem is that this expression must be in the format stated above: key conditions separated by `&&`. – Matias Cicero Jul 08 '15 at 01:43
  • @kaveman I am receiving the type (in my example, Genre) as a generic parameter, along with the key values, so I need to fetch my key properties via reflection (I think I know how to do this) and create the expression so I can query the Genre entities – Matias Cicero Jul 08 '15 at 01:44
  • when you say *receiving* what do you mean? where are you receiving? what is the method signature that is receiving? Do you have the **keys** as well as the values, or just the *key values*, as arguments to a function? – kaveman Jul 08 '15 at 01:48
  • @kaveman I only have the key values, but I can fetch the key properties via reflection using some custom attributes that I defined. In this example, `Language` and `Name` would be decorated with an attribute that defines them as key properties – Matias Cicero Jul 08 '15 at 01:49

4 Answers4

1
public Expression<Func<TValue, bool>> CreateExpression<TValue, TCompare>(TValue value, TCompare compare)
{
    var pv = Expression.Parameter(typeof(TValue), "data");
    var compareProps = typeof(TCompare).GetProperties();

    // First statement of the expression
    Expression exp = Expression.Constant(true);

    foreach (var prop in typeof(TValue).GetProperties())
    {
        // Check if the compare type has the same property
        if (!compareProps.Any(i => i.Name == prop.Name))
            continue;

        // Build the expression: value.PropertyA == "A" 
        // which "A" come from compare.PropertyA
        var eq = Expression.Equal(
            Expression.Property(pv, prop.Name), 
            Expression.Constant(compareProps
                .Single(i => i.Name == prop.Name)
                .GetValue(compare)));

        // Append with the first (previous) statement
        exp = Expression.AndAlso(exp, eq);
    }

    return Expression.Lambda<Func<TValue, bool>>(exp, pv);
}

Usage:

var value = new { Lang = "en", Name = "comedy"};

// The compareValue should have the same property name as the value, 
// or the expression will just ignore the property
var compareValue = new { Lang = "en", Name = "comedy", Other = "xyz" };

// The create expression content is
// {data => ((True AndAlso (data.Lang == "en")) AndAlso (data.Name == "comedy"))}
bool isMatch = CreateExpression(value, compareValue).Compile()(value); // true
Eric
  • 5,675
  • 16
  • 24
0

You can use interfaces in order to create a Func against the interface who has defined the necessary properties.

Example:

interface IExpressionable {
    string Language { get;set; }
    string Name { get;set; }
}
class Genre : IExpressionable {
    string Language {get;set;}
    string Name {get;set;}
}
Genre genre = new Genre();
Expression<Func<IExpressionable, bool>> expression = CreateExpression(genre, lang, name);
expression = (g => g.Language == "en" && g.Name == "comedy")

An alternative implementation of this concept can be had by providing a GetLanguage and GetName method on your interface, which would force subscribers to implement the underlying methods in order to return a "Language" and "Name" based on their own internal methods.

interface IExpressionable {
    string GetExpressionOne();
    string GetExpressionTwo();
}
class Genre : IExpressionable {
    string Language {get;set;}
    string Name {get;set;}
    public string GetExpressionOne() {
        return Language;
    }
    public string GetExpressionOne() {
        return Name;
    }
}
class SomethingElse {

    string Orange {get;set;}
    string BananaPeel {get;set;}
    public string GetExpressionOne() {
        return Orange;
    }
    public string GetExpressionOne() {
        return BananaPeel;
    }
}
Genre genre = new Genre();
SomethingElse else = new SomethingElse();
Expression<Func<IExpressionable, bool>> expression = CreateExpression(genre, lang, name);
Expression<Func<IExpressionable, bool>> expression2 = CreateExpression(else, lang, name);

expression = (g => g.GetExpressionOne() == "en" && g.GetExpressionTwo() == "comedy");

Edit from your comment above: @kaveman I only have the key values, but I can fetch the key properties via reflection using some custom attributes that I defined. In this example, Language and Name would be decorated with an attribute that defines them as key properties

My answer is to avoid this thought completely. Create an interface that defines the methods you need to perform whatever expression you are looking for, and have the entities inherit from it. You could have your methods on the interface be "GetKeyOne" and "GetKeyTwo". Then your expression doesn't have to be dynamic, you just need to define what the expression does when it is interacting with KeyOne and KeyTwo, which is defined in each implementor.

C Bauer
  • 5,003
  • 4
  • 33
  • 62
  • This is a good solution, but the existence of `Language` and `Name` properties really depend on the type of the current entity, so I would need to create a different interface for as many different entity types I have (They can have completely different properties) – Matias Cicero Jul 08 '15 at 01:48
  • This is an example and you can name the properties whatever you want, even "ExpressionArgument1" and "ExpressionArgument2". See the edit. – C Bauer Jul 08 '15 at 01:52
  • May I see the body of `CreateExpression`? – Matias Cicero Jul 08 '15 at 01:54
  • My suggestion is to not dynamically create the expression, but to define an interface that forces the implementors to maintain some property that you can work on statically. – C Bauer Jul 08 '15 at 01:58
0

Assuming you have attributes on each of the properties of each of the entities that you are interested in dealing with here (see comments), and you have the type parameter (call it T) a possible signature for CreateExpression is:

Expression<Func<T, bool>> CreateExpression(T entity, IOrderedDictionary<string, string> propertyTests);

In this way, your CreateExpression works with the known generic type, and the caller can specify what tests to make against the entity's properties. Here we assume the key is the property name to reflect on (ensure it has the known attribute) while the value is the required value of said property. IOrderedDictionary is a way to enforce short-circuit of the && tests, if that's your thing.

You can add additional constraints on T (e.g. T must implement some interface) via generic type constraints using where

See also Check if property has attribute

And Reflection - get attribute name and value on property

Community
  • 1
  • 1
kaveman
  • 4,339
  • 25
  • 44
0

This will do it, and should make it fairly user-friendly to build

private Expression<Func<Genre,bool>> CreateExpression(params Expression<Func<Object>>[] selectors)
{
    //We are working on a Genre type, and make a parameter of it
    //Query so far looks like g =>
    var param = Expression.Parameter(typeof(Genre),"g");

    //Set the base expression to make it easy to build
    //Query so far looks like g => true
    Expression expression = Expression.Constant(true);

    foreach(var selector in selectors) {
        //Find out the name of the variable was passed
        var selectorname = TestExtension.nameof(selector);
        //Get the value
        var selectorValue = selector.Compile()();

        //Create an accessor to the genre (g.Language for example)
        var accessMethod = Expression.PropertyOrField(param, selectorname);

        //Check if it equals the value (g.Language == "en")
        var equalExpr = Expression.Equal(accessMethod, Expression.Constant(selectorValue));

        //Make it an And expression
        //Query so far looks like g => true && g.Language == "en"
        expression = Expression.AndAlso(expression, equalExpr);

        //Second pass through the loop would build:
        //g => true && g.Language == "en" && g.Name == "comedy"
    }

    //Turn it into a lambda func and cast it
    var result = Expression.Lambda(expression, param) as Expression<Func<Genre, bool>>;
    return result;
}

public class Genre
{
    public string Language { get; set; }
    public string Name { get; set; }
}

//Taken from my answer at http://stackoverflow.com/a/31262225/563532

public static class TestExtension
{
    public static String nameof<T>(Expression<Func<T>> accessor)
    {
        return nameof(accessor.Body);
    }

    public static String nameof<T, TT>(this T obj, Expression<Func<T, TT>> propertyAccessor)
    {
        return nameof(propertyAccessor.Body);
    }

    private static String nameof(Expression expression)
    {
        if (expression.NodeType == ExpressionType.MemberAccess)
        {
            var memberExpression = expression as MemberExpression;
            if (memberExpression == null)
                return null;
            return memberExpression.Member.Name;
        }
        return null;
    }
}

Then you use it like this:

string language = "en";
string name = "comedy";
var genre = new Genre { Language = "en", Name="comedy" };

var query = CreateExpression(() => language, () => name);

Note that the variables must match the property names. Otherwise, you will need some kind of mapping [lang => language] etc.

To evaluate it:

var matches = query.Compile()(genre);

Or, you can pass it into EF for example:

dtx.Genre.Where(query);
Rob
  • 26,989
  • 16
  • 82
  • 98