Comment remplacer le type de propriété et sa valeur dans les arbres d'expression

c# expression-trees

Question

J'ai une classe PersonDTO avec la propriété Nullable 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; }
}

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, j'ai deux méthodes responsables de la création de l' Expression<Func<PersonViewModel, bool>> object:

    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, j'utilise AutoMapper? qui convertit une Expression<Func<PersonViewModel, bool>> en Expression<Func<PersonDTO, bool>> . Le code de 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 à ceci:

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

J'ai besoin d'une certaine manière de convertir une partie d'une expression lambda: person => person.Birthday > Convert(15.11.2016 00:00) (dans ce cas, la personne est PersonViewModel et Birthday de type DateTime?) En quelque chose qui ressemble à: person => person.Birthday > 20161115 (dans ce cas, il s'agit de PersonDTO et Birthday de type int?). Sans ce problème, tout fonctionne correctement. Je comprends que je dois aller plus loin dans l’arbre et faire quelques manipulations, mais je ne comprends pas comment et où dois-je faire cela.

Réponse populaire

Je voudrais adapter la valeur de la date et l'heure 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
    }

}

c'est assez spécifique, mais vous avez l'idée, et vous pouvez le rendre plus générique pour vos cas d'utilisation.

Mais je pense que votre cartographie de la propriété est fausse. En SQL, vous voulez utiliser la comparaison int. Ce qui précède le fait pour vous. Lorsque l'automapper modifie vos propriétés, il convient simplement de remplacer l'ancien anniversaire par le nouveau (changer de type), sans l'appel de NullDateTimeToNullInt. Le code ci-dessus prendra en charge le changement de type pour les comparaisons. Si vous avez le membre en sélection anonyme ou d'autres endroits, vous aurez toujours un problème, je crois ...




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi