2016-11-15 45 views
3

我有一個類PersonDTO可空的DateTime屬性值:如何更換物業類型及其在表達式樹

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

而在表示層的類:

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

在我的表我有兩種方法負責創建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; 
    } 

另外我是usin g AutoMapper?其將一個Expression<Func<PersonViewModel, bool>>轉換成Expression<Func<PersonDTO, bool>>。轉換的代碼如下所示:

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

    //... 
} 

我的表情轉換器看起來像:

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

我需要以某種方式部分轉換lambda表達式:person => person.Birthday > Convert(15.11.2016 00:00)(在這種情況下,人的類型爲PersonViewModel和生日DateTime?)看起來像這樣:person => person.Birthday > 20161115(在這種情況下,personDTO和生日類型int?)。沒有這個問題,所有的東西都能正確地映射和工作我知道我需要深入樹並做一些操作,但我無法理解如何以及在哪裏應該這樣做。

+0

哇。你能否讓這個代碼更小,但它仍然會重現這個問題? –

+0

@Thomas是的,我還沒有找到答案。在Visitor方法中SingleOrDefault找不到任何內容,因此轉換不會按預期方式進行。 – Dmitry

+0

@ThomasWeller我編輯了我的問題 – Dmitry

回答

0

我會沿着SG適應二進制表達式的日期時間值:

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 
    } 

} 

是相當具體,但你的想法,你可以把它你usecases更通用。

但我認爲你的屬性映射是錯誤的。在sql中你想使用int比較。以上是爲你做的。當automapper改變你的屬性時,它應該用新的生日(改變類型)替換舊的生日,而不用調用NullDateTimeToNullInt。上面的代碼將處理類型更改以進行比較。如果你有匿名選擇或其他地方的成員,你會仍然有一個問題,我相信...

+0

感謝您的代碼,但Convert.ChangeType(getValueFromNode(cast.Operand),cast.Type)存在一個異常。 InvalidCastException從System.DateTime到System.Nullable ... – Dmitry

+0

對不起,您不需要changetype,只需返回第一個參數:) – MBoros