如何解决具有集合项属性的 C# 表达式 - 访问成员名称 概述代码
我正在构建一个 SDK,用于构建特定系统的 HTTP 查询,我需要在查询字符串中指定我想要包含的模型的哪些属性。
例如https://system/api/projects/1?fields=name,description
我希望 SDK 是强类型的,所以我有查询构建器类,允许将查询指定为
new ProjectBuilder(1,f => f.Name,f => f.Description)
即使对于嵌套对象的复杂树,这也非常有效,例如f => f.ProjectTemplate.Location.Owner.Email
唯一的问题是集合,例如
public class Task
{
public string Name {get;set;}
//lots of other stuff
}
public string Project
{
public string Description {get;set;}
//lots of other stuff
public List<Task> Tasks {get;set;}
}
当我需要检索 Project
的 Description
和 Tasks
中所有 Project
的名称时,查询字符串必须如下所示:
https://system/api/projects/1?fields=description,tasks.name
我无法定义这样的表达式:
new ProjectBuilder(1,f => f.Tasks.Name)
,语法似乎需要 f.Tasks[0].Name
。
我可以对集合成员(以及更多嵌套对象)使用同样漂亮的表达式类型语法吗?
我用于从表达式访问成员的代码如下(稍微简化):
public static string Evaluate<T>(Expression<Func<T,object>> expression)
{
if (expression.Body is MemberExpression body)
{
return EvaluateExpressionTree(body);
}
else
{
throw new InvalidOperationException(
"Invalid expression. Expected Member expression,e.g. p=>p.Description");
}
}
private static string EvaluateExpressionTree(MemberExpression root)
{
if (root.Expression is MemberExpression nested)
{
var nestedProperty = EvaluateExpressionTree(nested);
var thisProperty = root.Member.Name;
return nestedProperty + "." + thisProperty;
}
else if (root.Expression is MethodCallExpression call)
{
//that's where I get when using the Tasks[0] Syntax
}
else
{
return GetMemberName(root);
}
}
当表达式访问集合元素时,我能够从集合中找到泛型类型,但从那时起我无法重建表达式的其他元素...
解决方法
所以我设法解决了我的问题。并不是说这是处理具有集合属性的表达式的方式,但问题是具体的,答案也是:)
概述
解决方案是
- 字符串化表达式
- 标记化
- 去除噪音
- 通过反射获取 PropertyInfo 来评估零件
- 跟踪表达式树中的当前类型以获得正确的结果
这让我可以有一个很好的流利的 API 来生成强类型查询字符串,如下所示。
代码
使用
[TestMethod]
public void ProjectExpression_NestedType_CollectionAccess()
{
//arrange
ProjectBuilder builder = new ProjectBuilder("000",f => f.Name,f => f.Workflow.TaskConfigurations,f => f.Workflow.TaskConfigurations[0].TaskTemplate.TaskType)
;
//act
string stringified = builder.BuildFullRequestUri();
//assert
Assert.AreEqual("projects/000?fields=name,workflow.taskConfigurations,workflow.taskConfigurations.taskTemplate.taskType",stringified);
}
实施:
采用函数参数的调用者代码:
protected ProjectBuilder(params Expression<Func<Project,object>>[] propertiesToInclude)
{
List<string> values = new List<string>();
foreach (Expression<Func<Project,object>> expression in propertiesToInclude)
{
values.Add(ExpressionEvaluator.Evaluate(expression));
}
this.AddProperties(values);
}
以及接受每个参数并很好地转换它的表达式评估器。
internal static class ExpressionEvaluator
{
public static string Evaluate<T>(Expression<Func<T,object>> expression)
{
if (expression.Body is MemberExpression)
{
List<string> result = new List<string>();
List<string> parts = GetExpressionParts(expression);
List<Type> expressionTreeTypes = new List<Type>() { typeof(T) };
foreach (string part in parts)
{
PropertyInfo member = expressionTreeTypes.Last().GetProperty(part);
if (member != null)
{
AddValueFromJsonAttributeOrPropertyName<T>(member,result);
UpdateLastUsedType<T>(member,expressionTreeTypes);
}
else
{
throw new InvalidOperationException($"Expression [{expression.Body}] is not valid. Failed to resolve: [{part}]");
}
}
return string.Join(".",result);
}
else
{
throw new InvalidOperationException("Invalid expression. Expected Member expression,e.g. p=>p.Description");
}
}
private static List<string> GetExpressionParts<T>(Expression<Func<T,object>> expression)
{
var stringified = expression.Body.ToString();
//skip the first part of expression - e.g. (f=>f.) - skip 'f.
//also skip collection accessors
return stringified.Split('.').Where(x => !x.Contains("get_Item(")).Skip(1).ToList();
}
private static void UpdateLastUsedType<T>(PropertyInfo member,List<Type> expressionTreeTypes)
{
//make sure that in case of primitive types we don't change the type
if (member.PropertyType.Assembly == typeof(Project).Assembly && member.PropertyType != expressionTreeTypes.Last())
{
expressionTreeTypes.Add(member.PropertyType);
}
else if (member.PropertyType.IsGenericType) // in case of collection properties,extract the nested type
{
expressionTreeTypes.Add(member.PropertyType.GenericTypeArguments.First());
}
}
private static void AddValueFromJsonAttributeOrPropertyName<T>(MemberInfo member,List<string> result)
{
var attr = member.CustomAttributes
.FirstOrDefault(x => x.AttributeType == typeof(JsonPropertyAttribute))?.ConstructorArguments?
.FirstOrDefault();
if (!string.IsNullOrEmpty(attr?.Value?.ToString()))
{
result.Add(attr?.Value?.ToString());
}
else
{
result.Add(member.Name[0].ToString().ToLower() + member.Name.Substring(1));
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。