Search code examples
c#.netexpression-trees

MemberExpression property not defined


I need help. I have two classes: Entity and Model. Their code is below.

public class TestEntityForSerialize
{
    public String FIO { get; set; }
    public DateTime Birthday { get; set; }
    public String Name { get; set; }
}

[DataContract]
public class TestModelForSerialize
{
    [DataMember]
    public String FIO { get; set; }
    [DataMember]
    public DateTime Birthday { get; set; }
    [DataMember(Name = "Name")]
    public String BigName { get; set; }
    public Int32 Age { get; set; }
}

The main task is to programmatically convert any Func<TestModelForSerialize, Boolean> to Func <TestEntityForSerialize, Boolean> . My decision is to use Expression trees. I'm using ExpressionVisitor for iterate Model's expression tree's nodes and to construct a new Expression with Entity as parameter. Generic parameters of these Func's are set in class, holding the functionality.

I've succeeded in converting ParameterExpression by overriding VisitParameter method, but I'd stucked with overriding VisitMember method of ExpressionVisitor's descendant. in fact I have the code: VisitMember override:

    protected override Expression VisitMember(MemberExpression node)
    {            
        Expression e = base.VisitMember(node);
        MemberExpression me = e as MemberExpression;
        if (me != null)
        {
            String modelPropertyName = ((PropertyInfo)me.Member).Name;
            ParameterExpression pe = node.Expression as ParameterExpression;
            TModel m = Activator.CreateInstance<TModel>();
            PropertyInfo memberPI = GetEntityProperty(m, modelPropertyName);
            MemberExpression meToReturn = Expression.Property(Expression.Parameter(typeof(TEntity)), memberPI);

            return meToReturn;
        }
        else
        {
            return base.VisitMember(node);
        }
    }

VisitParameter override:

    protected override Expression VisitParameter(ParameterExpression node)
    {
        ParameterExpression pe = base.VisitParameter(node) as ParameterExpression;
        if (pe.Type == typeof(TModel))
        {
            ParameterExpression ex = Expression.Parameter(typeof(TEntity), pe.Name);
            return ex;
        }
        else
        {
            return pe;
        }
    }

GetEntityProperty is the method mapping property of TModel class to property of TEntity class. It using DataContract and DataMember to map.

    protected PropertyInfo GetEntityProperty(TModel model, String propertyName)
    {
        PropertyInfo property = model.GetType().GetProperty(propertyName);
        if (property != null)
        {
            Attribute attr = property.GetCustomAttribute(typeof(DataMemberAttribute));
            if (attr != null)
            {
                String entityPropertyName = (((DataMemberAttribute)attr).Name != null ? ((DataMemberAttribute)attr).Name : propertyName);
                return typeof(TEntity).GetProperty(entityPropertyName);
            }
            else
            {
                throw new DataMemberAttributeNotFoundException();
            }
        }
        else
        {
            throw new MissingMemberException();
        }
    }

The TestMethod is

    [TestMethod]
    public void TestVisitMemberShouldReplaceModelWithEntity()
    {
        ParameterExpression p = Expression.Parameter(typeof(TestModelForSerialize));
        Type tm = typeof(TestModelForSerialize);
        PropertyInfo birthdayProp = tm.GetProperty("FIO");
        Assert.IsNotNull(birthdayProp);
        MemberExpression me = Expression.Property(p, birthdayProp);
        VisitorForTests vft = new VisitorForTests();
        MemberExpression meResult = vft.VisitMemberForTest(me) as MemberExpression;
        Assert.IsInstanceOfType(meResult.Member, typeof(PropertyInfo));
    }

VisitorForTests is the class to invoke protected method in class, I'm trying to deal with. It's method:

    public Expression VisitMemberForTest(MemberExpression node)
    {
        return this.VisitMember(node);
    }

When I try to invoke VisitMember method, it crushes on the first line ("Expression e = base.VisitMember(node);"), throwing the ArgumentException with message: Property "FIO" is not defined in class "TestEntityForSerialize". Only thing I could notice - VisitMember tries to launch VisitParameter method, and after getting it's result - crushes.

Any help is very appreciated.


Solution

  • Gotcha! As Russian proverb says "Sinking man saving - is this man's hands own business.". The problem was - I was trying to use Expression with wrong parameters. It should be supplied with TEntity's property and I'd supplying it with TModel's one. So just light changing of VisitMember method and Qawabanga!

        protected override Expression VisitMember(MemberExpression node)
        {            
            //Expression e = base.VisitMember(node); - I've commented this line
            MemberExpression me = node as MemberExpression;
            if (me != null)
            {
                String modelPropertyName = ((PropertyInfo)me.Member).Name;
                ParameterExpression pe = node.Expression as ParameterExpression;
                TModel m = Activator.CreateInstance<TModel>();
                PropertyInfo memberPI = GetEntityProperty(m, modelPropertyName);
                MemberExpression meToReturn = Expression.Property(Expression.Parameter(typeof(TEntity)), memberPI.Name);
    
                return base.VisitMember(meToReturn); //and changed this line
            }
            else
            {
                return base.VisitMember(node);
            }
        }
    

    And this works!