Expression Tree Null VisitMember

时间:2015-02-07 20:26:34

标签: c# linq lambda expression-trees

我正在将Expression<T, bool>转换为Expression<Y, bool>,其中T和Y是通过Automapper映射以外的任何方式不相关的不同实体。基本上,我有一个我的代码使用的Model对象:

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; }
}

我正在映射到要存储在我的mongo数据库中的实体对象:

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; }
}

我在这个问题中使用了答案来研究如何在表达式(Question)之间进行转换,这让我有90%的表达。我能够修改代码以获取模型存储和实体存储之间的AutoMapper映射,并从源属性中获取目标属性:

    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;


        }

问题是,我的实体对象没有与我的Model对象相同的所有属性(例如,Account对象与AccountId)。当Transformer到达Model对象的Account属性时,我得到一个Exception(因为我的Entity对象上没有匹配的属性)。我无法从VisitMember返回null,也不允许使用新的Expression()。如何处理我的Model对象上忽略我的Entity对象上不存在的属性?

使用评论信息进行更新

所以,为了更清楚一点,我使用Automapper从Models.Store映射到Entity.Store。我的实体.Store只有一个AccountId(因为我不想复制所有帐户数据),但我的Models.Store需要整个帐户对象(我将通过查询Accounts集合获得)。

Automapper基本上将我的Account对象转换为我的实体上的AccountId。因此,当我搜索x => x.Account.AccountId == abcd1234(其中x是models.Store)时,我需要将表达式转换为x => x.AccountId == abcd1234(其中x是Entity.Store)。

我正在使用该部分(将mS => mS.Account.AccountId == 1234更改为mE => mE.AccountId == 1234)。我现在遇到的问题是,在执行AccountId属性后,调用VisitMember,并将Account作为节点。由于我的Entity.Store对象中没有Account,因此我得到了异常。

1 个答案:

答案 0 :(得分:1)

在没有可测试/可运行代码的情况下测试解决方案相当困难。但这是一个猜测

鉴于以下表达式mS => mS.Account.AccountId == 1234并希望转换MemberExpressions,您将获得以下调用:

  1. VisitMember(mS.Account.AccountId
  2. VisitMember(mS.Account)
  3. 您想将第二个转换为mE.AccountId。这涉及两个转换:一,将属性访问从(EntityType).(AccountType).AccountId更改为(MongoStoreType).AccountId,还更改基础对象。如果您已经在ExpressionVisitor的其他方法(可能是VisitParameterVisitLambda)处理参数转换,那么您就可以了。然后你只需要跳过查看父MemberAccess,然后直接跳到祖父母:

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

    变成这样:

            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;
            }