Reputation: 73
I have an expression of this form:
Expression<Func<ShowParticipant, bool>> expr = z => z.Show.OrgName == "xyz";
I need to convert/expand it to following form:
Expression<Func<ShowParticipant, bool>> expr = z => z.Show.Organization.Name == "xyz";
where OrgName
property on Show
entity resolves to Organization.Name
. How can I achieve this assuming I need this to work in EF? You can imagine OrgName
defined in Show class as below -
public partial class Show
{
public string OrgName
{
get
{
return this.Organization.Name;
}
set
{
this.Organization.Name = value;
}
}
}
Appreciate your response,
Anand.
Upvotes: 1
Views: 1754
Reputation: 73
Thanks for your reply. This exactly answers my question. In mean time I was coming up with solution using ExpressionVisitor. It's not complete but I am giving approach I took here -
public class ExpressionExpander : ExpressionVisitor
{
private Dictionary<Expression, Expression> parameterMap = new Dictionary<Expression, Expression>();
protected override Expression VisitParameter(ParameterExpression node)
{
if (parameterMap.ContainsKey(node))
return parameterMap[node];
return node;
}
protected override Expression VisitMember(MemberExpression node)
{
var newObj = Visit(node.Expression);
if (NeedsInlineExpansion(newObj.Type, node.Member.Name))
{
LambdaExpression exp = GetPropertyTransform();
if (parameterMap.ContainsKey(node.Expression))
parameterMap.Add(exp.Parameters[0], node.Expression);
var visitedExp = Visit(exp.Body);
var memExp = (MemberExpression)visitedExp;
parameterMap.Add(node, memExp);
return memExp;
}
else
{
var newMember = newObj.Type.GetMember(node.Member.Name).First();
var newMemberAccessExpr = Expression.MakeMemberAccess(newObj, newMember);
parameterMap.Add(node, newMemberAccessExpr);
return newMemberAccessExpr;
}
}
private bool NeedsInlineExpansion(Type type, string coreMemberName)
{
// Figure out way to determine if the property needs to be flattened out...
// may be using attribute on Property
}
private LambdaExpression GetPropertyTransform()
{
// this is hardcoded right now, but represents some mechanism of getting a lambda
// returned for property in question...
Expression<Func<Show, string>> exp = z => z.Organization.Name;
var lambda = (LambdaExpression)exp;
return lambda;
}
}
And on caller would look like below -
Expression<Func<ShowParticipant, bool>> expr1 = z => z.Show.OrgName == "xyz";
ExpressionExpander expander = new ExpressionExpander();
var inlineExpanded = expander.Visit(expr1);
Thanks, Anand.
Upvotes: 0
Reputation: 908
By itself, expression trees can't do this as there's no way for an expression tree to know what calling the OrgName property does under the covers.
However if you were to put an attribute on the property, perhaps then some factory could replace a call to property1 ("OrgName") with the property path Organization.Name
Sample code below.
static void Main(string[] args)
{
Company company = new Company { Organization = { Name = "Microsoft" } };
Expression<Func<Company, int>> lambdaExpression = c => c.OrgName.Length;
var expanded = Expand(lambdaExpression);
}
private static Expression<Func<TSource, TResult>> Expand<TSource, TResult>(Expression<Func<TSource, TResult>> lambdaExpression)
{
Expression expanded = GetExpandedExpression(lambdaExpression.Body);
if (Object.ReferenceEquals(lambdaExpression.Body, expanded))
{
return lambdaExpression;
}
return Expression.Lambda<Func<TSource, TResult>>(
expanded,
lambdaExpression.Parameters
);
}
private static Expression GetExpandedExpression(Expression expression)
{
Expression expandedContainer;
switch (expression.NodeType)
{
case ExpressionType.MemberAccess:
MemberExpression memberExpression = (MemberExpression)expression;
if (memberExpression.Expression != null)
{
expandedContainer = GetExpandedExpression(memberExpression.Expression);
PropertyPathAttribute attribute = memberExpression.Member.GetCustomAttributes(typeof(PropertyPathAttribute), false).Cast<PropertyPathAttribute>().FirstOrDefault();
if (attribute != null && !String.IsNullOrEmpty(attribute.Path))
{
string[] parts = attribute.Path.Split('.');
expression = expandedContainer;
for (int i = 0; i < parts.Length; i++)
{
string part = parts[i];
expression = Expression.MakeMemberAccess(
expression,
(MemberInfo)expression.Type.GetProperty(part, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ??
expression.Type.GetField(part, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
);
}
}
else if (!Object.ReferenceEquals(expandedContainer, memberExpression.Expression))
{
return Expression.MakeMemberAccess(
expandedContainer,
memberExpression.Member
);
}
}
break;
case ExpressionType.ArrayLength:
UnaryExpression unaryExpression = (UnaryExpression)expression;
if (!Object.ReferenceEquals(expandedContainer = GetExpandedExpression(unaryExpression.Operand), unaryExpression.Operand))
{
return Expression.ArrayLength(expandedContainer);
}
break;
}
return expression;
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class PropertyPathAttribute : Attribute
{
private readonly string _path;
public string Path
{
get
{
return this._path;
}
}
public PropertyPathAttribute(string path)
{
this._path = path;
}
}
public class Organization
{
public string Name { get; set; }
}
public class Company
{
[PropertyPath("Organization.Name")]
public string OrgName
{
get
{
return this.Organization.Name;
}
set
{
this.Organization.Name = value;
}
}
public Organization Organization { get; private set; }
public Company()
{
this.Organization = new Organization();
}
}
Upvotes: 1