3 回答
TA贡献1995条经验 获得超2个赞
很高兴您找到了解决您的特定问题的方法。
这是一个更通用的解决方案,一旦原始属性名称和类型匹配(例如Entity->Dto等),以及多级嵌套,它就会处理不同的源和目标类型:
public static Expression<Func<TSource, TTarget>> BuildSelector<TSource, TTarget>(string members) =>
BuildSelector<TSource, TTarget>(members.Split(',').Select(m => m.Trim()));
public static Expression<Func<TSource, TTarget>> BuildSelector<TSource, TTarget>(IEnumerable<string> members)
{
var parameter = Expression.Parameter(typeof(TSource), "e");
var body = NewObject(typeof(TTarget), parameter, members.Select(m => m.Split('.')));
return Expression.Lambda<Func<TSource, TTarget>>(body, parameter);
}
static Expression NewObject(Type targetType, Expression source, IEnumerable<string[]> memberPaths, int depth = 0)
{
var bindings = new List<MemberBinding>();
var target = Expression.Constant(null, targetType);
foreach (var memberGroup in memberPaths.GroupBy(path => path[depth]))
{
var memberName = memberGroup.Key;
var targetMember = Expression.PropertyOrField(target, memberName);
var sourceMember = Expression.PropertyOrField(source, memberName);
var childMembers = memberGroup.Where(path => depth + 1 < path.Length);
var targetValue = !childMembers.Any() ? sourceMember :
NewObject(targetMember.Type, sourceMember, childMembers, depth + 1);
bindings.Add(Expression.Bind(targetMember.Member, targetValue));
}
return Expression.MemberInit(Expression.New(targetType), bindings);
}
前两种方法只是公开暴露的高级助手。实际工作是通过私有递归NewObject方法完成的。它对当前级别的属性进行分组,并且对于每个分组,要么创建简单的分配,就像PropertyN = source.Property1.Property2...PropertyN它是最后一个级别一样,要么递归地PropertyN = new TypeN { … }否则。
与您的示例中的表达式匹配的示例用法:
var test = BuildSelector<Shipment, Shipment>(
"Recipient.CityName, Sender.CityId, Sender.CityName, ParcelUniqueId");
只需Compile在需要时调用Func。
TA贡献1827条经验 获得超9个赞
最后我找到了解决方案。它为两级嵌套属性(如 Shipment.Sender.CityName)生成正确的 lambda。所以任何需要同样东西的人都可以使用它。
我希望它有帮助。
/* this comes from request
* request.Fields = "Sender.CityId,Sender.CityName,Recipient.CityName,parcelUniqueId"
*/
// in the service method
var shipmentList = _context.Shipments.
.OrderByDescending(s => s.Id)
.Skip((request.Page -1) * request.PageSize)
.Take(request.PageSize)
.Select(new SelectLambdaBuilder<Shipment>().CreateNewStatement(request.Fields))
.ToList();
public class SelectLambdaBuilder<T>
{
// as a performence consideration I cached already computed type-properties
private static Dictionary<Type, PropertyInfo[]> _typePropertyInfoMappings = new Dictionary<Type, PropertyInfo[]>();
private readonly Type _typeOfBaseClass = typeof(T);
private Dictionary<string, List<string>> GetFieldMapping(string fields)
{
var selectedFieldsMap = new Dictionary<string, List<string>>();
foreach (var s in fields.Split(','))
{
var nestedFields = s.Split('.').Select(f => f.Trim()).ToArray();
var nestedValue = nestedFields.Length > 1 ? nestedFields[1] : null;
if (selectedFieldsMap.Keys.Any(key => key == nestedFields[0]))
{
selectedFieldsMap[nestedFields[0]].Add(nestedValue);
}
else
{
selectedFieldsMap.Add(nestedFields[0], new List<string> { nestedValue });
}
}
return selectedFieldsMap;
}
public Func<T, T> CreateNewStatement(string fields)
{
ParameterExpression xParameter = Expression.Parameter(_typeOfBaseClass, "s");
NewExpression xNew = Expression.New(_typeOfBaseClass);
var selectFields = GetFieldMapping(fields);
var shpNestedPropertyBindings = new List<MemberAssignment>();
foreach (var keyValuePair in selectFields)
{
PropertyInfo[] propertyInfos;
if (!_typePropertyInfoMappings.TryGetValue(_typeOfBaseClass, out propertyInfos))
{
var properties = _typeOfBaseClass.GetProperties();
propertyInfos = properties;
_typePropertyInfoMappings.Add(_typeOfBaseClass, properties);
}
var propertyType = propertyInfos
.FirstOrDefault(p => p.Name.ToLowerInvariant().Equals(keyValuePair.Key.ToLowerInvariant()))
.PropertyType;
if (propertyType.IsClass)
{
PropertyInfo objClassPropInfo = _typeOfBaseClass.GetProperty(keyValuePair.Key);
MemberExpression objNestedMemberExpression = Expression.Property(xParameter, objClassPropInfo);
NewExpression innerObjNew = Expression.New(propertyType);
var nestedBindings = keyValuePair.Value.Select(v =>
{
PropertyInfo nestedObjPropInfo = propertyType.GetProperty(v);
MemberExpression nestedOrigin2 = Expression.Property(objNestedMemberExpression, nestedObjPropInfo);
var binding2 = Expression.Bind(nestedObjPropInfo, nestedOrigin2);
return binding2;
});
MemberInitExpression nestedInit = Expression.MemberInit(innerObjNew, nestedBindings);
shpNestedPropertyBindings.Add(Expression.Bind(objClassPropInfo, nestedInit));
}
else
{
Expression mbr = xParameter;
mbr = Expression.PropertyOrField(mbr, keyValuePair.Key);
PropertyInfo mi = _typeOfBaseClass.GetProperty( ((MemberExpression)mbr).Member.Name );
var xOriginal = Expression.Property(xParameter, mi);
shpNestedPropertyBindings.Add(Expression.Bind(mi, xOriginal));
}
}
var xInit = Expression.MemberInit(xNew, shpNestedPropertyBindings);
var lambda = Expression.Lambda<Func<T,T>>( xInit, xParameter );
return lambda.Compile();
}
它编译 lambda 如下:
s => new Shipment {
Recipient = new Address {
CityName = s.Recipient.CityName
},
Sender = new Address {
CityId = s.Sender.CityId,
CityName = s.Sender.CityName
},
ParcelUniqueId = s.ParcelUniqueId
}
我分享了 debug 的一些截图:
TA贡献1898条经验 获得超8个赞
最后我找到了解决方案。它为两级嵌套属性(如 Shipment.Sender.CityName)生成正确的 lambda。所以任何需要同样东西的人都可以使用它。
我希望它有帮助。
/* this comes from request
* request.Fields = "Sender.CityId,Sender.CityName,Recipient.CityName,parcelUniqueId"
*/
// in the service method
var shipmentList = _context.Shipments.
.OrderByDescending(s => s.Id)
.Skip((request.Page -1) * request.PageSize)
.Take(request.PageSize)
.Select(new SelectLambdaBuilder<Shipment>().CreateNewStatement(request.Fields))
.ToList();
public class SelectLambdaBuilder<T>
{
// as a performence consideration I cached already computed type-properties
private static Dictionary<Type, PropertyInfo[]> _typePropertyInfoMappings = new Dictionary<Type, PropertyInfo[]>();
private readonly Type _typeOfBaseClass = typeof(T);
private Dictionary<string, List<string>> GetFieldMapping(string fields)
{
var selectedFieldsMap = new Dictionary<string, List<string>>();
foreach (var s in fields.Split(','))
{
var nestedFields = s.Split('.').Select(f => f.Trim()).ToArray();
var nestedValue = nestedFields.Length > 1 ? nestedFields[1] : null;
if (selectedFieldsMap.Keys.Any(key => key == nestedFields[0]))
{
selectedFieldsMap[nestedFields[0]].Add(nestedValue);
}
else
{
selectedFieldsMap.Add(nestedFields[0], new List<string> { nestedValue });
}
}
return selectedFieldsMap;
}
public Func<T, T> CreateNewStatement(string fields)
{
ParameterExpression xParameter = Expression.Parameter(_typeOfBaseClass, "s");
NewExpression xNew = Expression.New(_typeOfBaseClass);
var selectFields = GetFieldMapping(fields);
var shpNestedPropertyBindings = new List<MemberAssignment>();
foreach (var keyValuePair in selectFields)
{
PropertyInfo[] propertyInfos;
if (!_typePropertyInfoMappings.TryGetValue(_typeOfBaseClass, out propertyInfos))
{
var properties = _typeOfBaseClass.GetProperties();
propertyInfos = properties;
_typePropertyInfoMappings.Add(_typeOfBaseClass, properties);
}
var propertyType = propertyInfos
.FirstOrDefault(p => p.Name.ToLowerInvariant().Equals(keyValuePair.Key.ToLowerInvariant()))
.PropertyType;
if (propertyType.IsClass)
{
PropertyInfo objClassPropInfo = _typeOfBaseClass.GetProperty(keyValuePair.Key);
MemberExpression objNestedMemberExpression = Expression.Property(xParameter, objClassPropInfo);
NewExpression innerObjNew = Expression.New(propertyType);
var nestedBindings = keyValuePair.Value.Select(v =>
{
PropertyInfo nestedObjPropInfo = propertyType.GetProperty(v);
MemberExpression nestedOrigin2 = Expression.Property(objNestedMemberExpression, nestedObjPropInfo);
var binding2 = Expression.Bind(nestedObjPropInfo, nestedOrigin2);
return binding2;
});
MemberInitExpression nestedInit = Expression.MemberInit(innerObjNew, nestedBindings);
shpNestedPropertyBindings.Add(Expression.Bind(objClassPropInfo, nestedInit));
}
else
{
Expression mbr = xParameter;
mbr = Expression.PropertyOrField(mbr, keyValuePair.Key);
PropertyInfo mi = _typeOfBaseClass.GetProperty( ((MemberExpression)mbr).Member.Name );
var xOriginal = Expression.Property(xParameter, mi);
shpNestedPropertyBindings.Add(Expression.Bind(mi, xOriginal));
}
}
var xInit = Expression.MemberInit(xNew, shpNestedPropertyBindings);
var lambda = Expression.Lambda<Func<T,T>>( xInit, xParameter );
return lambda.Compile();
}
它编译 lambda 如下:
s => new Shipment {
Recipient = new Address {
CityName = s.Recipient.CityName
},
Sender = new Address {
CityId = s.Sender.CityId,
CityName = s.Sender.CityName
},
ParcelUniqueId = s.ParcelUniqueId
}
我分享了 debug 的一些截图:我相信你的问题出在这段代码中:
string[] nestedProps = o.Split('.');
Expression mbr = xParameter;
foreach (var prop in nestedProps)
mbr = Expression.PropertyOrField(mbr, prop);
// property "Field1"
PropertyInfo mi = typeof( Shipment ).GetProperty( ((MemberExpression)mbr).Member.Name );
该foreach循环被重复地将值分配给mbr,然后覆盖它,这意味着它的最终值将是表达等效过去值的nestedProps。假设输入字符串是"Recipient.CityId",mbr将是 的表达式CityId。然后,您尝试GetProperty在Shipment类型上查找名称为 的属性,该属性CityId当然不存在(CityId作为 的属性Address)。
不过,我不确定要解决什么问题,因为我不确定您最终想要什么。
- 3 回答
- 0 关注
- 217 浏览
添加回答
举报