I'm using Entity Framework and building queries using navigation properties dynamically.
For most of my use cases, the following works fine:
private static MethodCallExpression GetNavigationPropertyExpression<T>(string propertyName, int test,
ParameterExpression parameter, string subParameter)
{
var navigationPropertyCollection = Expression.Property(parameter, propertyName);
var childType = navigationPropertyCollection.Type.GetGenericArguments()[0];
var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(childType);
var aclAttribute = GetAclAttribute(typeof(T), propertyName);
var childProperty = aclAttribute.ChildProperty;
var propertyCollectionGenericArg = childType;
var serviceLocationsParam = Expression.Parameter(propertyCollectionGenericArg, subParameter);
var left = Expression.Property(serviceLocationsParam, childProperty);
var right = Expression.Constant(test, typeof(int));
var isEqual = Expression.Equal(left, right);
var subLambda = Expression.Lambda(isEqual, serviceLocationsParam);
var resultExpression = Expression.Call(anyMethod, navigationPropertyCollection, subLambda);
return resultExpression;
}
I use a custom AclAttribute class assigned to properties via metadata type and partial classes. For navigation properties, a ChildProperty is provided so that the expression builder knows to look deeper for the desired property.
For example: the table Services references another table called ServiceLocations. I need the LocationId value(s) from the ServiceLocations reference. This part works fine.
My problem is when there is more than 1 nested property to go through. Another example: the table ServiceCategories references Services which, again, references ServiceLocations. Like before, I need the LocationId value(s) from ServiceLocations.
I've done this manually by using two "Any" methods, combining, and returning the resulting expression, but there will be times where navigation properties won't be collections, or a child of a navigation property may be a collection. For those cases, I need some kind of recursive option. I've been trying for a while now, and coming up short.
Since I mentioned it, here's the test method I put together for my second example. This works, but I'm violating DRY. (Note: I didn't name the tables):
private static MethodCallExpression GetNestedNavigationPropertyExpression(int test, ParameterExpression rootParameter)
{
var servicesProperty = Expression.Property(rootParameter, "tblServices");
var servicesParameter = Expression.Parameter(servicesProperty.Type.GetGenericArguments()[0], "ss");
var serviceLocationsProperty = Expression.Property(servicesParameter, "tblServiceLocations");
var serviceLocationsParameter = Expression.Parameter(serviceLocationsProperty.Type.GetGenericArguments()[0], "s");
var servicesAnyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(servicesProperty.Type.GetGenericArguments()[0]);
var serviceLocationsAnyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(serviceLocationsProperty.Type.GetGenericArguments()[0]);
var aclAttribute = GetAclAttribute(typeof(tblService), "tblServiceLocations");
var left = Expression.Property(serviceLocationsParameter, aclAttribute.ChildProperty);
var right = Expression.Constant(test, typeof(int));
var isEqual = Expression.Equal(left, right);
var subLambda = Expression.Lambda(isEqual, serviceLocationsParameter);
var endExpression = Expression.Call(serviceLocationsAnyMethod, serviceLocationsProperty, subLambda);
var intermediaryLamba = Expression.Lambda(endExpression, servicesParameter);
var resultExpression = Expression.Call(servicesAnyMethod, servicesProperty, intermediaryLamba);
return resultExpression;
}