2011-09-15 9 views
7

Tengo dos tipos: Cat y . Me gustaría seleccionar Gatos usando un Func<Dog, bool>. Para hacer eso, necesito una forma de mapear las propiedades de Cat a Dog en algún tipo de mapeador (similar a cómo AutoMapper mapea las propiedades de un objeto a otro tipo de objeto).AutoMapper para Func entre los tipos de selector

estoy imaginando algo como esto:

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

O ya hay algo que hace esto o no debería ser.

Respuesta

14

Aquí es una solución utilizando 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; 
} 

ACTUALIZACIÓN: Ha sido de 1,5 años desde la primera vez respondí esto, y yo pensé que podría expandir en mi respuesta ahora ya que las personas se preguntan cómo hacer esto cuando tienes una expresión en oposición a un delegado.

La solución es la misma en principio: necesitamos poder compose las dos funciones (selector y mapper) en una sola función. Desafortunadamente, dado que no hay forma de que C# "llame" una expresión de otra (como podríamos hacerlo con los delegados), no podemos representar esto directamente en el código. Por ejemplo, el siguiente código fallará para compilar:

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

La única manera de crear nuestra función compuesta, por lo tanto, es la construcción de la expression tree a nosotros mismos utilizando los System.Linq.Expressions clases.

Lo que realmente tenemos que hacer es modificar el cuerpo de la función selector para que todas las instancias de su parámetro sean reemplazadas por el cuerpo de la función mapper. Esto se convertirá en el cuerpo de nuestra nueva función, que aceptará el parámetro mapper.

Para reemplazar el parámetro he creado una subclase de ExpressionVisitor clase que puede atravesar un árbol de expresión y reemplazar un solo parámetro con una expresión arbitraria:

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

Entonces creé un método de extensión, Compose(), que utiliza la visitante a componer dos expresiones lambda, un exterior y un interior:

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

Ahora, con todo lo que la infraestructura en su lugar, podemos modificar nuestro método GetMappedSelector() utilizar nuestro Compose() extensión:

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

que creó un simple console application a probar esto. Con suerte, mi explicación no estaba demasiado ofuscada; pero desafortunadamente, no hay un enfoque más simple para hacer lo que estás tratando de hacer. Si todavía estás totalmente confundido, al menos puedes reutilizar mi código y has apreciado los matices y las complejidades de tratar con los árboles de expresiones.

+1

Qué pasa si tengo algo como: pública IEnumerable (Expresión > { var x = dbo.Products.Get (ExpressionMapper.GetMappedSelector (predicate);) } solo funciona con Func no con Expression > –

+0

@ persianDev podría encontrar una manera de hacer que esto funcione? yo tengo el mismo problema. –

+1

@bahadirarslan Actualicé mi respuesta para abordar este problema. – luksan

2

@luksan, gracias por la inspiración! Tu solución no resolvió mi problema, pero me hizo pensar. Como necesitaba pasar la expresión traducida a IQueryable.OrderBy(), el método de traducción de la expresión interna no funcionaba. Pero se me ocurrió una solución que funcionará en ambos casos y también es más fácil de implementar. También es genérico, por lo que puede reutilizarse para cualquier tipo de mapa.Aquí está el código:

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

Aquí está el código ReflectionHelper (utilizado sólo para mantener el código anterior limpiador)

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

Tuve problemas para poner este código en una PCL, me falta la propiedad MetadataToken en MemberInfo y el método selector.GetPropertyName(). ¿Algunas ideas? –

+0

Lo siento, @GeorgeTaskos! Ha pasado mucho tiempo desde que trabajé con .Net. La API probablemente cambió y esas propiedades/métodos tienen diferentes nombres ahora. – svallory

Cuestiones relacionadas