2011-09-15 40 views
7

我有兩種類型:CatDog。我想選擇貓使用Func<Dog, bool>。要做到這一點,我需要一種方法來在某種映射器中映射Cat到Dog的屬性(類似於AutoMapper將屬性從一個對象映射到另一個對象類型)。AutoMapper for Func的選擇器類型

我想象這樣的事情:

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

有要麼已經不錯了,這是否還是應該有。

回答

14

下面是使用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; 
} 

UPDATE:它已經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測試了這一點。希望我的解釋不會太模糊;但不幸的是,做你正在做的事情並不是一個簡單的方法。如果您仍然完全困惑,至少您可以重複使用我的代碼,並且已經瞭解處理表達樹的細微差別和複雜性!

+1

什麼?如果我有這樣的: 公共IEnumerable的(表達> predicate) var x = dbo.Products.Get(ExpressionMapper.GetMappedSelector (predicate);) } 它只適用於Func 不與Expression > –

+0

@ persianDev你能找到一種方法來做到這一點?我有同樣的問題。 –

+1

@bahadirarslan我更新瞭解決此問題的答案。 – luksan

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

我在將這些代碼放入PCL時遇到了問題,MemberInfo以及selector.GetPropertyName()方法中缺少MetadataToken屬性。有任何想法嗎? –

+0

對不起,@GeorgeTaskos!自從我和.Net合作以來,這已經很長時間了。 API很可能已經改變,現在這些屬性/方法有不同的名稱。 – svallory