如何动态创建带参数化返回属性的LINQ查询表达式?
Absolutely! You can totally build this dynamic expression using .NET's Expression Tree API. The catch is that we can't rely on the compiler's anonymous type shortcut directly (since that's statically defined), but we can manually construct the exact same logic at runtime. Let's break this down based on your use case.
Scenario 1: Fixed Property Names (X/Y) with Dynamic Values (LINQ-to-EF Friendly)
If you just need the returned object to have consistent property names like X and Y, but their values map to the dynamic colX and colY properties from your Rule class (this works great for LINQ-to-EF since the type structure is static), here's how to implement it:
private Expression<Func<Rule, object>> GetExpression(string colX, string colY) { // Validate that the provided property names exist on Rule var ruleType = typeof(Rule); var propertyX = ruleType.GetProperty(colX); var propertyY = ruleType.GetProperty(colY); if (propertyX == null) throw new ArgumentException($"Property '{colX}' does not exist on {nameof(Rule)}"); if (propertyY == null) throw new ArgumentException($"Property '{colY}' does not exist on {nameof(Rule)}"); // Create the parameter expression (represents 'x' in x => ...) var param = Expression.Parameter(ruleType, "x"); // Create expressions to access the target properties on the Rule parameter var accessX = Expression.Property(param, propertyX); var accessY = Expression.Property(param, propertyY); // We need to convert the property values to object to match the return type var convertedX = Expression.Convert(accessX, typeof(object)); var convertedY = Expression.Convert(accessY, typeof(object)); // Create a sample anonymous type to get its constructor and properties var anonymousType = new { X = default(object), Y = default(object) }.GetType(); var constructor = anonymousType.GetConstructor(new[] { typeof(object), typeof(object) }); var anonymousProperties = anonymousType.GetProperties(); // Build the expression to instantiate the anonymous type var newAnonymous = Expression.New(constructor, new[] { convertedX, convertedY }, anonymousProperties); // Wrap everything into a lambda expression return Expression.Lambda<Func<Rule, object>>(newAnonymous, param); }
How this works:
- We first validate that the input property names exist on the
Ruleclass to avoid runtime errors. - We create a parameter expression for the
Ruleinstance (thexin your lambda). - We build expressions to access the dynamic properties (
x.C1,x.C3, etc.) based on your input parameters. - We use a sample anonymous type to get the constructor and property metadata needed to build the new object expression.
- Finally, we assemble all these parts into a lambda expression that matches your desired return type.
Scenario 2: Dynamic Property Names (LINQ-to-Objects Only)
If you need the returned object's property names to exactly match the colX and colY input values (e.g., return { C1 = x.C1, C3 = x.C3 } when you pass "C1" and "C3"), we can't use anonymous types (since their property names are fixed at compile time). Instead, we can return a Dictionary<string, object>:
private Expression<Func<Rule, Dictionary<string, object>>> GetDynamicPropertyExpression(string colX, string colY) { var ruleType = typeof(Rule); var propertyX = ruleType.GetProperty(colX); var propertyY = ruleType.GetProperty(colY); if (propertyX == null) throw new ArgumentException($"Property '{colX}' does not exist on {nameof(Rule)}"); if (propertyY == null) throw new ArgumentException($"Property '{colY}' does not exist on {nameof(Rule)}"); var param = Expression.Parameter(ruleType, "x"); // Get the property values and convert to object var valueX = Expression.Convert(Expression.Property(param, propertyX), typeof(object)); var valueY = Expression.Convert(Expression.Property(param, propertyY), typeof(object)); // Build expressions to create a Dictionary and add entries var dictType = typeof(Dictionary<string, object>); var dictVar = Expression.Variable(dictType, "dict"); var dictConstructor = Expression.New(dictType); var addMethod = dictType.GetMethod("Add", new[] { typeof(string), typeof(object) }); var block = Expression.Block( new[] { dictVar }, Expression.Assign(dictVar, dictConstructor), Expression.Call(dictVar, addMethod, Expression.Constant(colX), valueX), Expression.Call(dictVar, addMethod, Expression.Constant(colY), valueY), dictVar ); return Expression.Lambda<Func<Rule, Dictionary<string, object>>>(block, param); }
Important Note:
This approach works for LINQ-to-Objects, but it won't work with LINQ-to-EF because Entity Framework can't translate the Dictionary constructor and Add method calls into SQL queries. Use this only if you're working with in-memory collections.
内容的提问来源于stack exchange,提问作者Aleksandar

