2

I am converting an Expression<T, bool> to an Expression<Y, bool> where T and Y are different entities not related in any way other than through an Automapper mapping. Essentially, I have a Model object that my code uses:

public class Store
{
    public string StoreId { get; set; }

    public string Name { get; set; }

    public List<Phone> Phones { get; set; }

    public Address Address { get; set; }

    public Account Account { get; set; }

    public Status Status { get; set; }
}

that I am mapping to an entity object to store in my mongo database:

public class Store : MongoEntity
{

    public string AccountId { get; set; }

    public string Name { get; set; }

    public List<string> UserIds { get; set; }

    public List<Phone> PhoneNumbers { get; set; }

    public Address Address { get; set; }
}

public abstract class MongoEntity : IMongoEntity
{
    [BsonId]
    public ObjectId Id { get; set; }

    public Status Status { get; set; }
}

I used the answer in this question to work out how to convert between expressions (Question), and that got me 90% there. I was able to modify the code to grab the AutoMapper mappings between my Model store and my entity store and grab the destination property from from the source property:

    private Expression<Func<TNewTarget, bool>> TransformPredicateLambda<TOldTarget, TNewTarget>(
Expression<Func<TOldTarget, bool>> predicate)
    {
        var lambda = (LambdaExpression)predicate;
        if (lambda == null)
        {
            throw new NotSupportedException();
        }

        //Modified here to get automapper mappings
        var maps = Mapper.FindTypeMapFor<TOldTarget, TNewTarget>();
        var mutator = new ExpressionTargetTypeMutator(t => typeof(TNewTarget), maps);
        var explorer = new ExpressionTreeExplorer();
        var converted = mutator.Visit(predicate.Body);

        return Expression.Lambda<Func<TNewTarget, bool>>(
            converted,
            lambda.Name,
            lambda.TailCall,
            explorer.Explore(converted).OfType<ParameterExpression>());
    }

        protected override Expression VisitMember(MemberExpression node)
        {
            var dataContractType = node.Member.ReflectedType;
            var activeRecordType = _typeConverter(dataContractType);

            PropertyMap prop = null;
            foreach (var propertyMap in _maps)
            {
                var source = propertyMap.SourceMember;
                var dest = propertyMap.DestinationProperty;

                if (source != null && source.Name == node.Member.Name)
                {
                    prop = propertyMap;
                }
            }
            if (prop == null)
            {
                return base.VisitMember(node);
            }

            var propertyName = prop.DestinationProperty.Name;

            var property = activeRecordType.GetProperty(propertyName);

            var converted = Expression.MakeMemberAccess(
                    base.Visit(node.Expression),
                    property
                    );

                return converted;


        }

The problem is, my entity object doesn't have all of the same properties as my Model object (Account object versus AccountId for example). When the Transformer gets to the Account property on the Model object, I get an Exception (because there is no matching property on my Entity object). I cannot return null from VisitMember, and new Expression() is not allowed either. How can I handle ignoring properties on my Model object that do not exist on my Entity object?

Updating with info from Comments

So, to be a little more clear, I am using Automapper to map from a Models.Store to an Entity.Store. My entity.Store only has an AccountId (because I don't want to duplicate all of the account data), but my Models.Store needs the whole account object (which I would get by querying the Accounts collection).

Automapper is bascially converting my Account object to just an AccountId on my entity. Therefore, when I search for x => x.Account.AccountId == abcd1234 (where x is a models.Store), I need my expression to convert to x => x.AccountId == abcd1234 (where x is an Entity.Store).

I have that part working (changing mS => mS.Account.AccountId == 1234 to mE => mE.AccountId == 1234). The problem I am having now is that after doing the AccountId property, VisitMember is called with Account as the node. Since there is no Account in my Entity.Store object, I get the exception.

Community
  • 1
  • 1
Mike
  • 1,718
  • 3
  • 30
  • 58
  • 1
    the link to question is not working correctly, i suppose it was [this one](http://stackoverflow.com/questions/2797261/mutating-the-expression-tree-of-a-predicate-to-target-another-type) =) – Guru Stron Feb 07 '15 at 21:27
  • Sorry about that Guru, I updated the link. – Mike Feb 07 '15 at 21:45
  • 1
    How would you ignore the fact that `A` does not exist in the predicate `x => x.A == 1`? – usr Feb 07 '15 at 22:06
  • I was trying to do x=> x.Account.AccountId==... I find the right property for AccountId but then it tries to find Account(which doesn't exist) – Mike Feb 07 '15 at 22:41
  • So, to be a little more clear, I am using Automapper to map from a Models.Store to an Entity.Store. My entity.Store only has an AccountId (because I don't want to duplicate all of the account data), but my Models.Store needs the whole account object (which I would get by querying the Accounts collection). Automapper is bascially converting my Account object to just an AccountId on my entity. Therefore, when I search for x => x.Account.AccountId == abcd1234 (where x is a models.Store), I need my expression to convert to x => x.AccountId (where x is an Entity.Store).... – Mike Feb 08 '15 at 00:32
  • I have that part working (changing mS => mS.Account.AccountId == 1234 to mE => mE.AccountId == 1234). The problem I am having now is that after doing the AccountId property, VisitMember is called with Account as the node. Since there is no Account in my Entity.Store object, I get the exception. – Mike Feb 08 '15 at 00:33

1 Answers1

1

It's rather hard to test a solution without testable/runnable code. But here's a guess

Given the following expression mS => mS.Account.AccountId == 1234 and looking to transform MemberExpressions, you'll get the following calls:

  1. VisitMember(mS.Account.AccountId
  2. VisitMember(mS.Account)

You want to transform the second one into mE.AccountId. This involves two transformations: One, changing the property access from (EntityType).(AccountType).AccountId to (MongoStoreType).AccountId, and also changing the underlying object. If you're already handling the parameter transformations in other methods of your ExpressionVisitor, probably VisitParameter and VisitLambda, you'll be fine there. You then just need to skip looking at the parent MemberAccess, and jump straight to the grandparent:

        var converted = Expression.MakeMemberAccess(
                base.Visit(node.Expression),
                property
                );

        return converted;

becomes something like this:

        var parentMember = node.Expression as MemberExpression;

        if (parentMember != null)
        {
            var grandparent = parentMember.Expression;

            var converted = Expression.MakeMemberAccess(
                    base.Visit(grandparent),
                    property
                    );

            return converted;
        }
        else
        {
            var converted = Expression.MakeMemberAccess(
                    base.Visit(node.Expression),
                    property
                    );

            return converted;
        }
Shlomo
  • 14,102
  • 3
  • 28
  • 43
  • Thanks Shlomo, I will give this a shot and see how it turns out. Thanks for pointing me in the right direction. – Mike Feb 10 '15 at 04:43