我试图动态构建Linq查询的select语句。 我有这样的功能:
public Task<List<T>> RunQuery<T>(
IQueryable<T> query,
FieldSelector<T> fields,
int pageNumber, int pageSize)
{
var skip = (pageNumber-1) * pageSize;
var query = query.Skip(skip).Take(pageSize);
var selector = fields.GetSelector();
return query.Select(selector).ToListAsync();
}
这是FieldSelector类:(我的代码我每个字段都有额外的属性)
public class FieldSelector<T>
{
private List<LambdaExpression> expressions;
public FieldSelector()
{
expressions = new List<LambdaExpression>();
}
public void Add(Expression<Func<T, object>> expr)
{
expressions.Add(expr);
}
public Expression<Func<T, object>> GetSelector()
{
//Build an expression like e => new {e.Name, e.Street}
}
}
如何实现GetSelector功能?可能吗? (不要太复杂) 这就是我想用它的方式:
var fields = new FieldSelector<Employee>();
fields.Add(e => e.Name);
fields.Add(e => e.Street);
RunQuery<Employee>(query, fields, 1, 100);
答案 0 :(得分:3)
您需要为Anonymous Type
生成自定义类型的编译器,因为您无法使用动态属性计数生成真正的Anonymous Type
。生成此类型后,您可以轻松设置传递给FieldSelector
的表达式的分配,并将其组合为自定义类型。
public class FieldSelector<T>
{
private List<LambdaExpression> expressions;
public FieldSelector()
{
expressions = new List<LambdaExpression>();
}
public void Add(Expression<Func<T, object>> expr)
{
expressions.Add(expr);
}
public Expression<Func<T, object>> GetSelector()
{
// We will create a new type in runtime that looks like a AnonymousType
var str = $"<>f__AnonymousType0`{expressions.Count}";
// Create type builder
var assemblyName = Assembly.GetExecutingAssembly().GetName();
var modelBuilder = AppDomain.CurrentDomain
.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect)
.DefineDynamicModule("module");
var typeBuilder = modelBuilder.DefineType(str, TypeAttributes.Public | TypeAttributes.Class);
var types = new Type[expressions.Count];
var names = new List<string>[expressions.Count];
for (int i = 0; i < expressions.Count; i++)
{
// Retrive passed properties
var unExpr = expressions[i].Body as UnaryExpression;
var exp = unExpr == null ? expressions[i].Body as MemberExpression : unExpr.Operand as MemberExpression;
types[i] = exp.Type;
// Retrive a nested properties
names[i] = GetAllNestedMembersName(exp);
}
// Defined generic parameters for custom type
var genericParams = typeBuilder.DefineGenericParameters(types.Select((_, i) => $"PropType{i}").ToArray());
for (int i = 0; i < types.Length; i++)
{
typeBuilder.DefineField($"{string.Join("_", names[i])}", genericParams[i], FieldAttributes.Public);
}
// Create generic type by passed properties
var type = typeBuilder.CreateType();
var genericType = type.MakeGenericType(types);
ParameterExpression parameter = Expression.Parameter(typeof(T), "MyItem");
// Create nested properties
var assignments = genericType.GetFields().Select((prop, i) => Expression.Bind(prop, GetAllNestedMembers(parameter, names[i])));
return Expression.Lambda<Func<T, object>>(Expression.MemberInit(Expression.New(genericType.GetConstructors()[0]), assignments), parameter);
}
private Expression GetAllNestedMembers(Expression parameter, List<string> properties)
{
Expression expression = parameter;
for (int i = 0; i < properties.Count; ++i)
{
expression = Expression.Property(expression, properties[i]);
}
return expression;
}
private List<string> GetAllNestedMembersName(Expression arg)
{
var result = new List<string>();
var expression = arg as MemberExpression;
while (expression != null && expression.NodeType != ExpressionType.Parameter)
{
result.Insert(0, expression.Member.Name);
expression = expression.Expression as MemberExpression;
}
return result;
}
}
顺便说一句,正如您在上面的代码中看到的那样,当您尝试检索具有超过1级嵌套class1->class2->class3->Propery_1
的属性时,当前解决方案不起作用。解决它并不困难。
编辑:以上案例已修复。现在您可以检索class1->class2->class3->Propery_1
它的用法:
private class TestClass
{
public string Arg2 { get; set; }
public TestClass Nested { get; set; }
public int Id { get; set; }
}
var field = new FieldSelector<TestClass>();
field.Add(e => e.Arg2);
field.Add(e => e.Id);
field.Add(e => e.Nested.Id);
dynamic cusObj = field.GetSelector().Compile()(new TestClass { Arg2 = "asd", Id = 6, Nested = new TestClass { Id = 79 } });
Console.WriteLine(cusObj.Arg2);
Console.WriteLine(cusObj.Id);
Console.WriteLine(cusObj.Nested_Id);
不幸的是,cusObj在编译类型时将为object
,如果您没有将其标记为dynamic
,则无法检索其属性。
顺便说一句,您的public Task<List<T>> RunQuery<T>
无法编译,因为field.GetSelector()
会返回Func<T, object>
,当您调用return {{}时,您将获得Task<List<object>>
1}}
希望,这很有帮助。