Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MemberExpression, build Expression.Property from class

Below expression compares property NAME with the value PETER.

            ParameterExpression pe = Expression.Parameter(typeof(T), "x");
            MemberExpression member = Expression.Property(pe, "name");
            ConstantExpression value = Expression.Constant("Peter");
            exp = Expression.Equal(member, value);

What if the property is a class:

            public class Address
            {
                public string Name {get; set;}
            }

Then the expression would look something similar to this:

            MemberExpression member = Expression.Property(pe, "Address.Name");
            ConstantExpression value = Expression.Constant("Peter");
            exp = Expression.Equal(member, value);

This would fail because the member type doesn't match the value type.

So, the question is: How to build an Expression that would work using the above class sample ??

I use this expression in a NHibernate.Linq query:

        var q = from f in data //of type IQueryable<T>
            select f;
        if (filter != null) //filter of type Expression<Func<T, bool>>
            q = q.Where(filter);
        etc....

Thank you.

UPDATE by Peter:

Based on the code from xanatos (next post) I have created the following test to understand how it works. Its not very different from what xanatos do, but at first I could not get it to work, so I decided to write it allover in one simple test, and that did it. With thanks to xanatos:

    [Test]
    public void FilterWithDeepProperties()
    {
        //Arrange
        IGenericGridRepository repository = ObjectFactory.GetInstance<IGenericGridRepository>();

        FilterDescriptor filter = new FilterDescriptor("AgreementId.Name", FilterOperator.IsEqualTo, "a name");
        string[] properties = filter.Member.Split('.');
        ParameterExpression pe = Expression.Parameter(typeof(SampleDomain), "x");

        //Act
        Expression lastMember = pe;
        for (int i = 0; i < properties.Length; i++)
        {
            MemberExpression member = Expression.Property(lastMember, properties[i]);
            lastMember = member;
        }
        ConstantExpression valueExpression = Expression.Constant(filter.Value);
        Expression equalityExpression = Expression.Equal(lastMember, valueExpression);
        Expression<Func<SampleDomain, bool>> where = Expression.Lambda<Func<SampleDomain, bool>>(equalityExpression, pe);
        var result = repository.GetObjects<SampleDomain>(filter: where);

        //Assert
        result.Count().Should().BeGreaterThan(0, "because there are many schedule items equals to " + filter.Value);
    }
like image 697
Peter Larsen 'CPH' Avatar asked Mar 16 '15 11:03

Peter Larsen 'CPH'


1 Answers

You probably want something like:

public static Expression<Func<TSource, bool>> GetEquality<TSource>(object value, params string[] properties)
{
    ParameterExpression pe = Expression.Parameter(typeof(TSource), "source");

    Expression lastMember = pe;

    for (int i = 0; i < properties.Length; i++)
    {
        MemberExpression member = Expression.Property(lastMember, properties[i]);
        lastMember = member;
    }

    Expression valueExpression = Expression.Constant(value);
    Expression equalityExpression = Expression.Equal(lastMember, valueExpression);
    Expression<Func<TSource, bool>> lambda = Expression.Lambda<Func<TSource, bool>>(equalityExpression, pe);
    return lambda;
}

Use it like:

Expression exp = GetEquality<Person>("Foo", "Address", "Name");

Where Foo is your Peter (so the value that must be compared), while Address and Name are the names of the "chain" of properties. For example I'm using

public class Person
{
    public Address Address { get; set; }
}

public class Address
{
    public string Name { get; set; }
}

So the expression generated is

source.Address.Name == "Foo"

If you want to use something like Address.Name, you can use the method like

Expression exp = GetEquality<Person>("Foo", "Address.Name".Split('.'));
like image 192
xanatos Avatar answered Nov 10 '22 13:11

xanatos