Reputation: 654
I'm looking to create a Linq Where, based on a nested property.
Let's say this is my project:
public class Car {
public Engine Engine { get; set; }
}
public class Engine {
public int HorsePower { get; set; }
}
var myCar = new Car() {
Engine = new Engine() {
HorsePower = 400
}
};
I use this code found somewhere, which allow to create Expression,
private Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>( PropertyInfo property, TValue value ) {
var param = Expression.Parameter( typeof( TItem ) );
var memberExp = Expression.Property( param, property );
BinaryExpression body;
//If nullable, Expression.Equal won't work even if the value is not null. So we convert to non nullable (the compared expression)
Type typeIfNullable = Nullable.GetUnderlyingType( memberExp.Type );
if ( typeIfNullable != null ) {
var convertedExp = Expression.Convert( memberExp, Expression.Constant( value ).Type );
body = Expression.Equal( convertedExp, Expression.Constant( value ) );
} else {
body = Expression.Equal( memberExp, Expression.Constant( value ) );
}
return Expression.Lambda<Func<TItem, bool>>( body, param );
}
Now, I had like to have an equivalent, calling my method PropertyEquals
var engine = myCar.Select( c => c.Engine.HorsePower == 400 );
Something like
var property = GetPropertyForDotSequence( typeof( Car ), "Engine.HorsePower" );
.Select( PropertyEquals<TEntity, int>( property , 400 ) );
I found a method which allow to find property based on a dotted format (GetPropertyForDotSequence), which work properly, but return HorsePower property info, and not Engine.HorsePower, so I get an error saying Car doesnot have an int32 property called HorsePower.
private PropertyInfo GetPropertyForDotSequence ( Type baseType, string propertyName ) {
var parts = propertyName.Split( '.' );
return ( parts.Length > 1 )
? GetPropertyForDotSequence( baseType.GetProperty( parts[ 0 ] ).PropertyType, parts.Skip( 1 ).Aggregate( ( a, i ) => a + "." + i ) )
: baseType.GetProperty( propertyName );
}
Upvotes: 1
Views: 1463
Reputation: 205549
In order to achieve your goal, instead of using a separate helper function to extract the last property info from a property path and then passing the property info to your function, all that should be done inside the function itself, i.e. it should receive the string
containing the property path like this
public static partial class Utils
{
public static Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>(string propertyPath, TValue value)
{
var source = Expression.Parameter(typeof(TItem), "source");
var propertyNames = propertyPath.Split('.');
var member = Expression.Property(source, propertyNames[0]);
for (int i = 1; i < propertyNames.Length; i++)
member = Expression.Property(member, propertyNames[i]);
Expression left = member, right = Expression.Constant(value, typeof(TValue));
if (left.Type != right.Type)
{
var nullableType = Nullable.GetUnderlyingType(left.Type);
if (nullableType != null)
right = Expression.Convert(right, left.Type);
else
left = Expression.Convert(left, right.Type);
}
var body = Expression.Equal(left, right);
var expr = Expression.Lambda<Func<TItem, bool>>(body, source);
return expr;
}
}
I'm not quite sure how it can be useful though because the signature does not allow inferring the generic types, so it would require something like this
var predicate = Utils.PropertyEquals<Car, int>("Engine.HorsePower", 400);
bool result = predicate.Compile().Invoke(myCar);
IMO it would be useful if used with combination of the following extension methods
public static partial class Utils
{
public static IQueryable<T> WherePropertyEquals<T, TValue>(this IQueryable<T> source, string propertyPath, TValue value)
{
return source.Where(PropertyEquals<T, TValue>(propertyPath, value));
}
public static IEnumerable<T> WherePropertyEquals<T, TValue>(this IEnumerable<T> source, string propertyPath, TValue value)
{
return source.Where(PropertyEquals<T, TValue>(propertyPath, value).Compile());
}
}
so you can write something like this
List<Car> cars = new List<Car> { myCar };
var cars400 = cars.WherePropertyEquals("Engine.HorsePower", 400).ToList();
Upvotes: 1
Reputation: 10068
You can use this method to get the property
value from an object
with the nested property name as string
public static object GetNestedPropertyValue(object obj, string nestedDottedPropertyName)
{
foreach (String part in nestedDottedPropertyName.Split('.'))
{
if (obj == null)
return null;
PropertyInfo info = obj.GetType().GetProperty(part);
if (info == null)
return null;
obj = info.GetValue(obj, null);
}
return obj;
}
But this is not valid Linq
statement
var engine = myCar.Select( c => c.Engine.HorsePower == 400 );
What you can do instead is, if you have a car object like this
var myCar = new Car()
{
Engine = new Engine()
{
HorsePower = 400
}
};
You can get the value of Engine.HorsePower
as
var horsePower = (int)GetNestedPropertyValue(myCar, "Engine.HorsePower");
Edit
For a Linq
example, if you have a List<Car>
like this
var myCar2 = new Car()
{
Engine = new Engine()
{
HorsePower = 800
}
};
var cars = new List<Car> { myCar, myCar2 }; //myCar defined above
You can use Linq
as
var car400 = cars.FirstOrDefault(c => (int)GetNestedPropertyValue(c, "Engine.HorsePower") == 400); //=> myCar
var horsePowers = cars.Select(c => (int)GetNestedPropertyValue(c, "Engine.HorsePower")); //=> 400, 800
Upvotes: 0