2016-11-15 6 views
3

J'ai un PersonDTO de classe avec la propriété Nullable DateTime:Comment remplacer le type de propriété et sa valeur dans les arbres d'expression

public class PersonDTO 
{ 
    public virtual long Id { get; set; } 
    public virtual string Name { get; set; } 
    // YYYYMMDD format 
    public virtual Nullable<int> Birthday { get; set; } 
} 

Et une classe dans la couche de présentation:

public class PersonViewModel 
{ 
    public virtual long Id { get; set; } 
    public virtual string Name { get; set; } 
    public virtual Nullable<DateTime> Birthday { get; set; } 
} 

Sur mon formulaire je deux méthodes qui sont responsables de la création objet Expression<Func<PersonViewModel, bool>>:

private Expression<Func<PersonViewModel, bool>> GetFilterExpression() 
    {    
     Expression condition = null; 
     ParameterExpression pePerson = Expression.Parameter(typeof(PersonViewModel), "person"); 
     //... 
     if (dtpBirth.Format != DateTimePickerFormat.Custom) 
     { 
      Expression target = Expression.Property(pePerson, pePerson.Type.GetProperty("Birthday", typeof(DateTime?))); 
      UnaryExpression date = Expression.Convert(Expression.Constant(dtpBirth.Value.Date), typeof (DateTime?)); 
      condition = (condition == null) 
        ? Expression.GreaterThan(target, date) 
        : Expression.And(condition, Expression.GreaterThan(target, date)); 
     } 
     // Формируем лямбду с условием и возвращаем результат сформированного фильтра 
     return condition != null ? Expression.Lambda<Func<PersonViewModel, bool>>(condition, pePerson) : null; 
    } 

aussi je suis usin g AutoMapper? qui convertit un Expression<Func<PersonViewModel, bool>> en Expression<Func<PersonDTO, bool>>. Le code pour la conversion ressemble:

// ... 
Mapper.CreateMap<PersonViewModel, PersonDTO>() 
       .ForMember(dto => dto.Birthday, opt => opt.MapFrom(model => model.BirthdaySingle.NullDateTimeToNullInt("yyyyMMdd"))); 
// ... 
public static class DataTypesExtensions 
{ 
    public static DateTime? NullIntToNullDateTime(this int? input, string format) 
    { 
     if (input.HasValue) 
     { 
      DateTime result; 
      if (DateTime.TryParseExact(input.Value.ToString(), format, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) 
      { 
       return result; 
      } 
     } 
     return null; 
    } 

    //... 
} 

Mon convertisseur d'expression ressemble à:

public static Expression<Func<TDestination, TResult>> RemapForType<TSource, TDestination, TResult>(
     this Expression<Func<TSource, TResult>> expression) 
    { 
     var newParameter = Expression.Parameter(typeof(TDestination)); 

     var visitor = new AutoMapVisitor<TSource, TDestination>(newParameter); 
     var remappedBody = visitor.Visit(expression.Body); 
     if (remappedBody == null) 
     { 
      throw new InvalidOperationException("Unable to remap expression"); 
     } 

     return Expression.Lambda<Func<TDestination, TResult>>(remappedBody, newParameter); 
    } 

public class AutoMapVisitor<TSource, TDestination> : ExpressionVisitor 
{ 
    private readonly ParameterExpression _newParameter; 
    private readonly TypeMap _typeMap = Mapper.FindTypeMapFor<TSource, TDestination>(); 

    public AutoMapVisitor(ParameterExpression newParameter) 
    { 
     _newParameter = newParameter; 
    } 

    protected override Expression VisitMember(MemberExpression node) 
    { 
     var propertyMaps = _typeMap.GetPropertyMaps(); 

     // Find any mapping for this member 
     // Here I think is a problem, because if it comes (person.Birthday => Convert(16.11.2016 00:00:00)) it can't find it. 
     var propertyMap = propertyMaps.SingleOrDefault(map => map.SourceMember == node.Member); 
     if (propertyMap == null) 
     { 
      return base.VisitMember(node); 
     } 

     var destinationProperty = propertyMap.DestinationProperty; 
     var destinationMember = destinationProperty.MemberInfo; 

     // Check the new member is a property too 
     var property = destinationMember as PropertyInfo; 
     if (property == null) 
     { 
      return base.VisitMember(node); 
     } 

     // Access the new property 
     var newPropertyAccess = Expression.Property(_newParameter, property); 
     return base.VisitMember(newPropertyAccess); 
    } 
} 

je dois en quelque sorte de convertir une partie d'une expression lambda: person => person.Birthday > Convert(15.11.2016 00:00) (dans cette personne de cas est PersonViewModel et anniversaire de type DateTime?) À quelque chose qui ressemble à: person => person.Birthday > 20161115 (dans ce cas personne est PersonDTO et Birthday de type int?). Sans ce problème, tout est mappé et fonctionne correctement. Je comprends que j'ai besoin d'aller plus loin dans l'arbre et de faire quelques manipulations, mais je ne peux pas comprendre comment et où devrais-je faire cela.

+0

Wow. Pouvez-vous réduire le code, mais cela reproduit toujours le problème? –

+0

@Thomas Oui, je n'ai pas encore trouvé de réponse. Dans la méthode de la méthode Visitor SingleOrDefault ne trouve rien, donc la conversion n'agit pas comme prévu. – Dmitry

+0

@ThomasWeller J'ai édité ma question – Dmitry

Répondre

0

je serais d'adapter la valeur de la datetime sur les expressions binaires avec sg le long:

class AutoMapVisitor<TSource, TDestination>: ExpressionVisitor 
{ 
    // your stuff 
    protected override Expression VisitBinary(BinaryExpression node) 
    { 
     var memberNode = IsBirthdayNode(node.Left) 
      ? node.Left 
      : IsBirthdayNode(node.Right) 
       ? node.Right 
       : null; 
     if (memberNode != null) 
     { 
      var valueNode = memberNode == node.Left 
       ? node.Right 
       : node.Left; 
      // get the value 
      var valueToChange = (int?)getValueFromNode(valueNode); 
      var leftIsMember = memberNode == node.Left; 
      var newValue = Expression.Constant(DataTypesExtensions.NullIntToNullDateTime(valueToChange, /*insert your format here */ "")); 
      var newMember = Visit(memberNode); 
      return Expression.MakeBinary(node.NodeType, leftIsMember ? newMember : newValue, leftIsMember ? newValue : newMember); // extend this if you have a special comparer or sg 
     } 
     return base.VisitBinary(node); 
    } 

    private bool IsBirthdayNode(Expression ex) 
    { 
     var memberEx = ex as MemberExpression; 
     return memberEx != null && memberEx.Member.Name == "Birthday" && memberEx.Member.DeclaringType == typeof(PersonViewModel); 
    } 

    private object getValueFromNode(Expression ex) 
    { 
     var constant = ex as ConstantExpression; 
     if (constant != null) 
      return constant.Value; 
     var cast = ex as UnaryExpression; 
     if (cast != null && ex.NodeType == ExpressionType.Convert) 
      return getValueFromNode(cast.Operand); 
     // here you can add more shortcuts to improve the performance of the worst case scenario, which is: 
     return Expression.Lambda(ex).Compile().DynamicInvoke(); // this will throw an exception, if you have references to other parameters in your ex 
    } 

} 

il est tout à fait spécifique, mais vous avez l'idée, et vous pouvez le rendre plus générique pour vos usecases.

Mais je pense que votre mappage pour la propriété est erroné. En sql vous voulez utiliser la comparaison int. Ce qui précède le fait pour vous. Quand automapper change vos propriétés, il devrait simplement remplacer l'ancien, avec le nouveau (en changeant le type), sans l'appel de NullDateTimeToNullInt. Le code ci-dessus prendra soin du changement de type pour les comparaisons. Si vous avez le membre dans des sélections anonymes ou d'autres endroits, vous aurez toujours un problème, je crois ...

+0

Merci pour le code, mais il y a une exception sur Convert.ChangeType (getValueFromNode (cast.Operand), cast.Type). InvalidCastException de System.DateTime à System.Nullable ... – Dmitry

+0

Désolé, vous n'avez pas besoin de ce type de changet, il suffit de retourner le premier paramètre :) – MBoros