【问题标题】:How to replace property type and its value in expression trees如何替换表达式树中的属性类型及其值
【发布时间】:2016-11-15 22:44:41
【问题描述】:

我有一个带有 Nullable DateTime 属性的 PersonDTO 类:

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&lt;Func&lt;PersonViewModel, bool&gt;&gt; 对象:

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

我也在使用 AutoMapper?它将一个Expression&lt;Func&lt;PersonViewModel, bool&gt;&gt; 转换为Expression&lt;Func&lt;PersonDTO, bool&gt;&gt;。转换代码如下:

// ...
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 =&gt; person.Birthday &gt; Convert(15.11.2016 00:00)(在这种情况下,人是 PersonViewModel 和 DateTime 类型的生日?)看起来像:person =&gt; person.Birthday &gt; 20161115(在这种情况下,人是 PersonDTO 和生日类型诠释?)。如果没有这个问题,一切都会映射并正常工作。我知道我需要更深入地了解树并进行一些操作,但我不明白我应该如何以及在哪里执行此操作。

【问题讨论】:

  • 哇。你能把代码变小,但它仍然会重现问题吗?
  • @Thomas 是的,我还没有找到答案。在 Visitor 方法中,SingleOrDefault 方法一无所获,因此转换没有按预期进行。
  • @ThomasWeller 我已经编辑了我的问题

标签: c# expression-trees


【解决方案1】:

我会用 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
    }

}

它非常具体,但你明白了,你可以让它更适合你的用例。

但我认为您对该属性的映射是错误的。在 sql 中你想使用 int 比较。以上是为你做的。当 automapper 更改您的属性时,它应该只是用新的生日替换旧的生日(更改类型),而不调用 NullDateTimeToNullInt。上面的代码将负责比较的类型更改。如果你在匿名选择或其他地方有会员,我相信你仍然会有问题......

【讨论】:

  • 感谢代码,但是 Convert.ChangeType(getValueFromNode(cast.Operand), cast.Type) 上有一个异常。从 System.DateTime 到 System.Nullable… 的 InvalidCastException
  • 对不起,你不需要那个changetype,只返回第一个参数:)
猜你喜欢
  • 2013-06-06
  • 2017-01-15
  • 1970-01-01
  • 1970-01-01
  • 2022-12-02
  • 2015-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多