【问题标题】:AutoMapper for Func's between selector typesAutoMapper for Func 在选择器类型之间
【发布时间】:2011-09-15 00:14:44
【问题描述】:

我有两种类型:CatDog。我想使用Func<Dog, bool> 选择猫。为此,我需要一种在某种映射器中将属性从 Cat 映射到 Dog 的方法(类似于 AutoMapper 如何将属性从一个对象映射到另一种类型的对象)。

我在想象这样的事情:

public Cat GetCat(Func<Dog, bool> selector)
{
    Func<Cat, bool> mappedSelector = getMappedSelector(selector);
    return _catRepository.Get(mappedSelector);
}

private Func<Cat, bool> getMappedSelector(Func<Dog, bool> selector)
{
    //some code here to map from one function type to another

    //something like AutoMapper would be sweet... 
    //something that I can configure how I want the properties to be mapped.
}

要么已经有这样的东西,要么应该有。

【问题讨论】:

    标签: c# linq


    【解决方案1】:

    这是使用 AutoMapper 的解决方案:

    Func<Cat, bool> GetMappedSelector(Func<Dog, bool> selector)
    {
        Func<Cat, Dog> mapper = Mapper.CreateMapExpression<Cat, Dog>().Compile();
        Func<Cat, bool> mappedSelector = cat => selector(mapper(cat));
        return mappedSelector;
    }
    

    更新:自从我第一次回答这个问题已经 1.5 年了,我想我现在应该扩展我的答案,因为当你有一个表达而不是一个表达时,人们会问如何做到这一点委托。

    解决方案在原理上是相同的——我们需要能够将compose 两个函数(selectormapper)合并为一个函数。不幸的是,由于在 C# 中没有办法从另一个表达式“调用”一个表达式(就像我们可以使用委托一样),我们不能直接在代码中表示它。例如下面的代码会编译失败:

    Expression<Func<Cat, bool>> GetMappedSelector(Expression<Func<Dog, bool>> selector)
    {
        Expression<Func<Cat, Dog>> mapper = Mapper.CreateMapExpression<Cat, Dog>();
        Expression<Func<Cat, bool>> mappedSelector = cat => selector(mapper(cat));
        return mappedSelector;
    }
    

    因此,创建组合函数的唯一方法是使用 System.Linq.Expressions 类自己构建 expression tree

    我们真正需要做的是修改selector 函数的主体,使其参数的所有实例都替换为mapper 函数的主体。这将成为我们新函数的主体,它将接受mapper 的参数。

    为了替换参数,我创建了ExpressionVisitor 类的子类,它可以遍历表达式树并将单个参数替换为任意表达式:

    class ParameterReplacer : ExpressionVisitor
    {
        private ParameterExpression _parameter;
        private Expression _replacement;
    
        private ParameterReplacer(ParameterExpression parameter, Expression replacement)
        {
            _parameter = parameter;
            _replacement = replacement;
        }
    
        public static Expression Replace(Expression expression, ParameterExpression parameter, Expression replacement)
        {
            return new ParameterReplacer(parameter, replacement).Visit(expression);
        }
    
        protected override Expression VisitParameter(ParameterExpression parameter)
        {
            if (parameter == _parameter)
            {
                return _replacement;
            }
            return base.VisitParameter(parameter);
        }
    }
    

    然后我创建了一个扩展方法Compose(),它使用访问者组成两个 lambda 表达式,一个外部和一个内部:

    public static class FunctionCompositionExtensions
    {
        public static Expression<Func<X, Y>> Compose<X, Y, Z>(this Expression<Func<Z, Y>> outer, Expression<Func<X, Z>> inner)
        {
            return Expression.Lambda<Func<X ,Y>>(
                ParameterReplacer.Replace(outer.Body, outer.Parameters[0], inner.Body),
                inner.Parameters[0]);
        }
    }
    

    现在,有了所有基础设施,我们可以修改 GetMappedSelector() 方法以使用我们的 Compose() 扩展:

    Expression<Func<Cat, bool>> GetMappedSelector(Expression<Func<Dog, bool>> selector)
    {
        Expression<Func<Cat, Dog>> mapper = Mapper.CreateMapExpression<Cat, Dog>();
        Expression<Func<Cat, bool>> mappedSelector = selector.Compose(mapper);
        return mappedSelector;
    }
    

    我创建了一个simple console application 来测试它。希望我的解释不会太混乱。但不幸的是,没有更简单的方法来做你想做的事情。如果您仍然完全感到困惑,至少您可以重用我的代码,并且对处理表达式树的细微差别和复杂性有所了解!

    【讨论】:

    • 如果我有类似的东西: public IEnumerable (Expression> predicate) { var x = dbo.Products.Get(ExpressionMapper.GetMappedSelector (谓词);) } 它仅适用于 Func 不适用于 Expression>
    • @persianDev 你能找到一种方法来解决这个问题吗?我有同样的问题。
    • @bahadirarslan 很遗憾没有
    • Automapper 这里说它没有 Mapper.CreateMapExpression 的定义。那怎么样?
    • 他们将 CreateMapExpression 移至 Mapper.Engine.CreateMapExpression();
    【解决方案2】:

    @luksan,感谢您的启发!您的解决方案没有解决我的问题,但让我思考。由于我需要将翻译后的表达式传递给 IQueryable.OrderBy(),因此使用内部表达式翻译方法不起作用。但我想出了一个适用于这两种情况的解决方案,而且实施起来也更简单。它也是通用的,因此可以重用于任何映射类型。代码如下:

    private Expression<Func<TDestination, TProperty>> GetMappedSelector<TSource, TDestination, TProperty>(Expression<Func<TSource, TProperty>> selector)
    {
        var map = Mapper.FindTypeMapFor<TSource, TDestination>();
    
        var mInfo = ReflectionHelper.GetMemberInfo(selector);
    
        if (mInfo == null)
        {
            throw new Exception(string.Format(
                "Can't get PropertyMap. \"{0}\" is not a member expression", selector));
        }
    
        PropertyMap propmap = map
            .GetPropertyMaps()
            .SingleOrDefault(m => 
                m.SourceMember != null && 
                m.SourceMember.MetadataToken == mInfo.MetadataToken);
    
        if (propmap == null)
        {
            throw new Exception(
                string.Format(
                "Can't map selector. Could not find a PropertyMap for {0}", selector.GetPropertyName()));
        }
    
        var param = Expression.Parameter(typeof(TDestination));
        var body = Expression.MakeMemberAccess(param, propmap.DestinationProperty.MemberInfo);
        var lambda = Expression.Lambda<Func<TDestination, TProperty>>(body, param);
    
        return lambda;
    }
    

    这里是 ReflectionHelper 代码(只是用来保持上面的代码更干净)

    private static class ReflectionHelper
    {
        public static MemberInfo GetMemberInfo(Expression memberExpression)
        {
            var memberExpr = memberExpression as MemberExpression;
    
            if (memberExpr == null && memberExpression is LambdaExpression)
            {
                memberExpr = (memberExpression as LambdaExpression).Body as MemberExpression;
            }
    
            return memberExpr != null ? memberExpr.Member : null;
        }
    }
    

    【讨论】:

    • 我在将此代码放入 PCL 时遇到问题,MemberInfo 以及 selector.GetPropertyName() 方法中缺少 MetadataToken 属性。有什么想法吗?
    • 对不起,@GeorgeTaskos!自从我使用 .Net 以来已经很长时间了。 API 很可能发生了变化,这些属性/方法现在有了不同的名称。
    猜你喜欢
    • 2021-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-23
    • 2017-05-16
    • 2012-06-19
    • 1970-01-01
    • 2010-10-27
    相关资源
    最近更新 更多