我有一个带有 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<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;
}
我也在使用 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)
(在这种情况下,person 是 PersonViewModel 和 DateTime 类型的 Birthday?)看起来像:person => person.Birthday > 20161115
(在这种情况下 person 是 PersonDTO 和 int 类型的 Birthday?)。如果没有这个问题,一切都会正常映射和工作。我知道我需要更深入地研究树并进行一些操作,但我不明白我应该如何以及在何处执行此操作。
最佳答案
我会使用 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。上面的代码将处理用于比较的类型更改。如果您在匿名选择或其他地方有成员,我相信您仍然会有问题...
关于c# - 如何替换表达式树中的属性类型及其值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40621052/