Dynamic Linq Expression Query Nested List Object - c#

Is it possible to dynamically build an IQueryable/Linq Expression with filtering criteria based on a NESTED/Child List Object's Property.
I have not included all code here - particularly the code around Pagination but I hope there is sufficient detail. Things to note is my use of EFCore5 and Automapper ProjectTo extension method.
For Example:
public class PersonModel
{
public int Id { get; set; }
public PersonName Name { get; set; }
public List<Pet> Pets { get; set; }
}
[Owned]
public class PersonName
{
public string Surname { get; set; }
public string GivenNames { get; set; }
}
public class Pet
{
public string Name { get; set; }
public string TypeOfAnimal { get; set; }
}
Here is my WebApi Controller.
[HttpGet(Name = nameof(GetAllPersons))]
public async Task<ActionResult<IEnumerable<PersonDTO>>> GetAllPersons(
[FromQuery] QueryStringParameters parameters)
{
IQueryable<Person> persons = _context.Persons;
parameters.FilterClauses.ForEach(filter =>
persons = persons.Where(filter.name, filter.op, filter.val));
// Note the use of 'Where' Extension Method.
var dTOs = persons
.ProjectTo<PersonDTO>(_mapper.ConfigurationProvider);;
var pagedPersons = PaginatedList<PersonDTO>
.CreateAsync(dTOs, parameters);
return Ok(await pagedPersons);
}
To query for all people with a Name.GivenNames property equal to "John" I would issue a GET call such as;
http://127.0.0.1/api/v1.0/?Filter=Name.GivenNames,==,John
This works perfectly fine.
However I would like to query for all people with a Pet with a Name property equal to "Scruffy" I would issue a GET call such as;
http://127.0.0.1/api/v1.0/?Filter=Pets.Name,==,Scruffy
Somewhat expectedly it throws the following exception on the line of code in BuildPredicate Function. This is because "Pets" is Type is a "List"... not a "Pet"
var left = propertyName.Split...
Instance property 'Pet:Name' is not defined for type
System.Collections.Generic.List`1[Person]' (Parameter 'propertyName')
Here are the Extension Methods.
public static class ExpressionExtensions
{
public static IQueryable<T> Where<T>(this IQueryable<T> source, string propertyName, string comparison, string value)
{
return source.Where(BuildPredicate<T>(propertyName, comparison, value));
}
}
public static Expression<Func<T, bool>> BuildPredicate<T>(string propertyName, string comparison, string value)
{
var parameter = Expression.Parameter(typeof(T), "x");
var left = propertyName.Split('.').Aggregate((Expression)parameter, Expression.Property);
var body = MakeComparison(left, comparison, value);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
private static Expression MakeComparison(Expression left, string comparison, string value)
{
switch (comparison)
{
case "==":
return MakeBinary(ExpressionType.Equal, left, value);
case "!=":
return MakeBinary(ExpressionType.NotEqual, left, value);
case ">":
return MakeBinary(ExpressionType.GreaterThan, left, value);
case ">=":
return MakeBinary(ExpressionType.GreaterThanOrEqual, left, value);
case "<":
return MakeBinary(ExpressionType.LessThan, left, value);
case "<=":
return MakeBinary(ExpressionType.LessThanOrEqual, left, value);
case "Contains":
case "StartsWith":
case "EndsWith":
return Expression.Call(MakeString(left), comparison, Type.EmptyTypes, Expression.Constant(value, typeof(string)));
default:
throw new NotSupportedException($"Invalid comparison operator '{comparison}'.");
}
}
private static Expression MakeString(Expression source)
{
return source.Type == typeof(string) ? source : Expression.Call(source, "ToString", Type.EmptyTypes);
}
private static Expression MakeBinary(ExpressionType type, Expression left, string value)
{
object typedValue = value;
if (left.Type != typeof(string))
{
if (string.IsNullOrEmpty(value))
{
typedValue = null;
if (Nullable.GetUnderlyingType(left.Type) == null)
left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type));
}
else
{
var valueType = Nullable.GetUnderlyingType(left.Type) ?? left.Type;
typedValue = valueType.IsEnum ? Enum.Parse(valueType, value) :
valueType == typeof(Guid) ? Guid.Parse(value) :
valueType == typeof(DateTimeOffset) ? DateTimeOffset.ParseExact(value, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal) :
Convert.ChangeType(value, valueType);
}
}
var right = Expression.Constant(typedValue, left.Type);
return Expression.MakeBinary(type, left, right);
}
Is there anyway to adapt this code to detect that if one of the nested properties is a LIST that it builds an 'Inner Predicate' to do a query on the child collection? ie: Enumerable.Any() ?

Working with raw expression tree's, it sometimes helps to start with an example, let the C# compiler have a go at it, and work backwards. eg;
Expression<Func<Person,bool>> expr = p => p.Pets.Any(t => t.Foo == "blah");
Though the compiler does take a shortcut in IL to specify type members that can't be decompiled.
The trick here is to make your method recursive. Instead of assuming that you can get each property;
var left = propertyName.Split('.').Aggregate((Expression)parameter, Expression.Property);
If you find a collection property in the list, you need to call BuildPredicate<Pet> with the remaining property string. Then use the return value as the argument to call .Pets.Any(...).
Perhaps something like;
public static Expression<Func<T, bool>> BuildPredicate<T>(string propertyName, string comparison, string value)
=> (Expression<Func<T, bool>>)BuildPredicate(typeof(T), propertyName.Split('.'), comparison, value);
public static LambdaExpression BuildPredicate(Type t, Span<string> propertyNames, string comparison, string value)
{
var parameter= Expression.Parameter(t, "x");
var p = (Expression)parameter;
for(var i=0; i<propertyNames.Length; i++)
{
var method = p.Type.GetMethods().FirstOrDefault(m => m.Name == "GetEnumerator" && m.ReturnType.IsGenericType);
if (method != null)
{
BuildPredicate(method.ReturnType.GetGenericArguments()[0], propertyNames.Slice(i), comparison, value);
// TODO ...
}
else
p = Expression.Property(p, propertyNames[i]);
}
// TODO ...
return Expression.Lambda(body, parameter);
}

Related

Getting the callee's name of an extension method

I've built up a simple ArgumentValidator class in order to simplify argument preconditions in any given method. Most of them are null or bounds checks and it gets pretty tedious after a couple of
if (arg == null ) throw new ArgumentNullException(nameof(arg));
So I've come up with the following set up:
public static class ArgumentValidator
{
public interface IArgument<T>
{
string ParamName { get; }
T Value { get; }
}
private class Argument<T>: IArgument<T>
{
public Argument(T argument, string paramName)
{
ParamName = paramName;
Value = argument;
}
public string ParamName { get; }
public T Value { get; }
}
public static IArgument<T> Validate<T>(this T argument, string paramName = null)
{
return new Argument<T>(argument, paramName ?? string.Empty);
}
public static IArgument<T> IsNotNull<T>(this IArgument<T> o)
{
if (ReferenceEquals(o.Value, null))
throw new ArgumentNullException(o.ParamName);
return o;
}
public static IArgument<T> IsSmallerThan<T, Q>(this IArgument<T> o, Q upperBound) where T : IComparable<Q> { ... }
//etc.
}
And I can use it in the following way:
public Bar Foo(object flob)
{
flob.Validate(nameof(flob)).IsNotNull().IsSmallerThan(flob.MaxValue);
}
Ideally I'd love to get rid of nameof(flob) in the Validate call and ultimately get rid of Validate alltogether; the only purpose of Validate is to avoid having to pass nameof(...) on every check down the chain.
Is there a way to get the name flob inside the Validate() method?
Doing that with an extension method is not that easy. It is easier with a static method that takes an LINQ expression (derived from devdigital's answer here):
public static T Validate<T>(this Expression<Func<T>> argument)
{
var lambda = (LambdaExpression)argument;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = (UnaryExpression)lambda.Body;
memberExpression = (MemberExpression)unaryExpression.Operand;
}
else
{
memberExpression = (MemberExpression)lambda.Body;
}
string name = memberExpression.Member.Name;
Delegate d = lambda.Compile();
return (T)d.DynamicInvoke();
}
The name inside is the name of the property you put in the method:
MyMethods.Validate(() => o);
Since the Validate returns T, you can use that further on. This might not be as performing as you want it to be, but this is the only viable option.
It is possible to make this an extension method too, you have to create the expression yourself by hand:
Expression<Func<object>> f = () => o; // replace 'object' with your own type
f.Validate();

Get Non-Static MethodInfo for IEnumerable<T>.First() (Or make the static method work with EF)

I have a method, GetSearchExpression, defined as:
private Expression<Func<T, bool>> GetSearchExpression(
string targetField, ExpressionType comparison, object value, IEnumerable<EnumerableResultQualifier> qualifiers = null);
At a high level, the method takes in a Field or Property (such as Order.Customer.Name), a comparison type (like Expression.Equals), and a value (like "Billy"), then returns a lambda expression suitable for input to a Where statement o => o.Customer.Name == "Billy"}.
Recently, I discovered an issue. Sometimes, the field I need is actually the field of an item in a collection (like Order.StatusLogs.First().CreatedDate).
I feel like that should be easy. The code that creates the left side of the expression (above, o => o.Customer.Name) is as follows:
var param = Expression.Parameter(typeof(T), "t");
Expression left = null;
//turn "Order.Customer.Name" into List<string> { "Customer", "Name" }
var deQualifiedFieldName = DeQualifyFieldName(targetField, typeof(T));
//loop through each part and grab the specified field or property
foreach (var part in deQualifiedFieldName)
left = Expression.PropertyOrField(left == null ? param : left, part);
It seems like I should be able to revise this to check if the field/property exists, and if not, try to call a method by that name instead. It would look like this:
var param = Expression.Parameter(typeof(T), "t");
Expression left = null;
var deQualifiedFieldName = DeQualifyFieldName(targetField, typeof(T));
var currentType = typeof(T);
foreach (var part in deQualifiedFieldName)
{
//this gets the Type of the current "level" we're at in the hierarchy passed via TargetField
currentType = SingleLevelFieldType(currentType, part);
if (currentType != null) //if the field/property was found
{
left = Expression.PropertyOrField(left == null ? param : left, part);
}
else
{ //if the field or property WASN'T found, it might be a method
var method = currentType.GetMethod(part, Type.EmptyTypes); //doesn't accept parameters
left = Expression.Call(left, method);
currentType = method.ReturnType;
}
}
The problem is that statement near the end (var method currentType.GetMethod(part, Type.EmptyTypes);). Turns out "First" and "Last" don't exist for IEnumerable objects, so I get a null exception when I try to use my Method object. In fact, the only way I can EVER them to show up in a GetMethod() call is by calling typeof(Enumerable).GetMethod(). That's useless of course, because then I get a static method in return rather than the instance method I need.
As a side-note: I tried using the static method, but Entity Framework throws a fit and won't accept it as part of the lambda.
I need help getting the instance MethodInfo of IEnumerable.First() & Last(). Please help!
My first attempt would be to identify if the instance is Enumerable<T> and treat the member name as method instead of a property/field like this
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> MakePredicate<T>(
string memberPath, ExpressionType comparison, object value)
{
var param = Expression.Parameter(typeof(T), "t");
var right = Expression.Constant(value);
var left = memberPath.Split('.').Aggregate((Expression)param, (target, memberName) =>
{
if (typeof(IEnumerable).IsAssignableFrom(target.Type))
{
var enumerableType = target.Type.GetInterfaces()
.Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
return Expression.Call(typeof(Enumerable), memberName, enumerableType.GetGenericArguments(), target);
}
return Expression.PropertyOrField(target, memberName);
});
var body = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}
}
and try use it as follows
var predicate = ExpressionUtils.MakePredicate<Order>(
"StatusLogs.First.CreatedDate", ExpressionType.GreaterThanOrEqual, new DateTime(2016, 1, 1));
The possible methods are First, FirstOrDefault, Last, LastOrDefault, Singe and SingleOrDefault.
But then you'll find that from the above methods only FirstOrDefault is supported in EF predicates.
Hence we can hardcode that call for collection types and do not include it in the accessors like this
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> MakePredicate2<T>(
string memberPath, ExpressionType comparison, object value)
{
var param = Expression.Parameter(typeof(T), "t");
var right = Expression.Constant(value);
var left = memberPath.Split('.').Aggregate((Expression)param, (target, memberName) =>
{
if (typeof(IEnumerable).IsAssignableFrom(target.Type))
{
var enumerableType = target.Type.GetInterfaces()
.Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
target = Expression.Call(typeof(Enumerable), "FirstOrDefault", enumerableType.GetGenericArguments(), target);
}
return Expression.PropertyOrField(target, memberName);
});
var body = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}
}
and use it as follows
var predicate = ExpressionUtils.MakePredicate<Order>(
"StatusLogs.CreatedDate", ExpressionType.GreaterThanOrEqual, new DateTime(2016, 1, 1));
P.S. While this will work, it might not produce the intended result. IEnumerable<T> navigation property means one-to-many relationship and assuming that the condition should apply only for the first (whatever that means in database, it's rather random) element does not make much sense. I would rather imply Any and try to build expression like this in the above case
t => t.StatusLogs.Any(s => s.CreatedDate >= new DateTime(2016, 1, 1))
or support FirstOrDefault, Any, All, (eventually Count, Sum, Min, Max) and handle them differently inside the builder.
Still IMO for collections Any is the most logical equivalent of the single entity criteria.
But all that will be another story (question).
UPDATE: Initially I was thinking to stop here, but for the sake of completeness, here is a sample implementation of the Any concept:
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> MakePredicate<T>(string memberPath, ExpressionType comparison, object value)
{
return (Expression<Func<T, bool>>)MakePredicate(
typeof(T), memberPath.Split('.'), 0, comparison, value);
}
static LambdaExpression MakePredicate(Type targetType, string[] memberNames, int index, ExpressionType comparison, object value)
{
var parameter = Expression.Parameter(targetType, targetType.Name.ToCamel());
Expression target = parameter;
for (int i = index; i < memberNames.Length; i++)
{
if (typeof(IEnumerable).IsAssignableFrom(target.Type))
{
var itemType = target.Type.GetInterfaces()
.Single(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.GetGenericArguments()[0];
var itemPredicate = MakePredicate(itemType, memberNames, i, comparison, value);
return Expression.Lambda(
Expression.Call(typeof(Enumerable), "Any", new[] { itemType }, target, itemPredicate),
parameter);
}
target = Expression.PropertyOrField(target, memberNames[i]);
}
if (value != null && value.GetType() != target.Type)
value = Convert.ChangeType(value, target.Type);
return Expression.Lambda(
Expression.MakeBinary(comparison, target, Expression.Constant(value)),
parameter);
}
static string ToCamel(this string s)
{
if (string.IsNullOrEmpty(s) || char.IsLower(s[0])) return s;
if (s.Length < 2) return s.ToLower();
var chars = s.ToCharArray();
chars[0] = char.ToLower(chars[0]);
return new string(chars);
}
}
so for this sample model
public class Foo
{
public ICollection<Bar> Bars { get; set; }
}
public class Bar
{
public ICollection<Baz> Bazs { get; set; }
}
public class Baz
{
public ICollection<Detail> Details { get; set; }
}
public class Detail
{
public int Amount { get; set; }
}
the sample expression
var predicate = ExpressionUtils.MakePredicate<Foo>(
"Bars.Bazs.Details.Amount", ExpressionType.GreaterThan, 1234);
produces
foo => foo.Bars.Any(bar => bar.Bazs.Any(baz => baz.Details.Any(detail => detail.Amount > 1234)))
What you are possibly looking for is System.Linq.Enumerable.First<T>(this IEnumerable<T> source) etc, so: start at typeof(System.Linq.Enumerable) and work from there. Note: you mention IEnumerable<T>, but it is possible that you actually mean IQueryable<T>, in which case you want Queryable.First<T>(this IQueryable<T> source) etc. Maybe this difference (between Enumerable and Queryable) is why EF "throws a fit".
Thank you to Marc and Ivan for their input. They deserve credit as without their help I would have spent much longer finding a solution. However, as neither answer solved the issue I was having, I'm posting the solution that worked for me (successfully applying criteria as well as successfully querying against an EF data source):
private Expression<Func<T, bool>> GetSearchExpression(string targetField, ExpressionType comparison, object value, string enumMethod)
{
return (Expression<Func<T, bool>>)MakePredicate(DeQualifyFieldName(targetField, typeof(T)), comparison, value, enumMethod);
}
private LambdaExpression MakePredicate(string[] memberNames, ExpressionType comparison, object value, string enumMethod = "Any")
{
//create parameter for inner lambda expression
var parameter = Expression.Parameter(typeof(T), "t");
Expression left = parameter;
//Get the value against which the property/field will be compared
var right = Expression.Constant(value);
var currentType = typeof(T);
for (int x = 0; x < memberNames.Count(); x++)
{
string memberName = memberNames[x];
if (FieldExists(currentType, memberName))
{
//assign the current type member type
currentType = SingleLevelFieldType(currentType, memberName);
left = Expression.PropertyOrField(left == null ? parameter : left, memberName);
//mini-loop for non collection objects
if (!currentType.IsGenericType || (!(currentType.GetGenericTypeDefinition() == typeof(IEnumerable<>) ||
currentType.GetGenericTypeDefinition() == typeof(ICollection<>))))
continue;
///Begin loop for collection objects -- this section can only run once
//get enum method
if (enumMethod.Length < 2) throw new Exception("Invalid enum method target.");
bool negateEnumMethod = enumMethod[0] == '!';
string methodName = negateEnumMethod ? enumMethod.Substring(1) : enumMethod;
//get the interface sub-type
var itemType = currentType.GetInterfaces()
.Single(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.GetGenericArguments()[0];
//generate lambda for single item
var itemPredicate = MakeSimplePredicate(itemType, memberNames[++x], comparison, value);
//get method call
var staticMethod = typeof(Enumerable).GetMember(methodName).OfType<MethodInfo>()
.Where(m => m.GetParameters().Length == 2)
.First()
.MakeGenericMethod(itemType);
//generate method call, then break loop for return
left = Expression.Call(null, staticMethod, left, itemPredicate);
right = Expression.Constant(!negateEnumMethod);
comparison = ExpressionType.Equal;
break;
}
}
//build the final expression
var binaryExpression = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(binaryExpression, parameter);
}
static LambdaExpression MakeSimplePredicate(Type inputType, string memberName, ExpressionType comparison, object value)
{
var parameter = Expression.Parameter(inputType, "t");
Expression left = Expression.PropertyOrField(parameter, memberName);
return Expression.Lambda(Expression.MakeBinary(comparison, left, Expression.Constant(value)), parameter);
}
private static Type SingleLevelFieldType(Type baseType, string fieldName)
{
Type currentType = baseType;
MemberInfo match = (MemberInfo)currentType.GetField(fieldName) ?? currentType.GetProperty(fieldName);
if (match == null) return null;
return GetFieldOrPropertyType(match);
}
public static Type GetFieldOrPropertyType(MemberInfo field)
{
return field.MemberType == MemberTypes.Property ? ((PropertyInfo)field).PropertyType : ((FieldInfo)field).FieldType;
}
/// <summary>
/// Remove qualifying names from a target field. For example, if targetField is "Order.Customer.Name" and
/// targetType is Order, the de-qualified expression will be "Customer.Name" split into constituent parts
/// </summary>
/// <param name="targetField"></param>
/// <param name="targetType"></param>
/// <returns></returns>
public static string[] DeQualifyFieldName(string targetField, Type targetType)
{
return DeQualifyFieldName(targetField.Split('.'), targetType);
}
public static string[] DeQualifyFieldName(string[] targetFields, Type targetType)
{
var r = targetFields.ToList();
foreach (var p in targetType.Name.Split('.'))
if (r.First() == p) r.RemoveAt(0);
return r.ToArray();
}
I included related methods in case someone actually needs to sort through this at some point. :)
Thanks again!

Get MemberInfo of child property from a MemberExpression

I am trying to get MemberInfo for a child property from a MemberExpression. I have found ways to get the full name of the nested type, but not a way to get the whole MemberInfo of the nested type. Here is a quick example of the scenario I am talking about:
Some simple models (the goal is to eventually get the MemberInfo of the Data property of the Child class)
public class Parent
{
public int ParentProperty { get; set; }
public Child Child { get; set; }
}
public class Child
{
public string Data { get; set; }
}
The lambda expression
Expression<Func<Parent,string>> func = new Func<Parent, string>(p =>
{
return p.Child.Data;
});
Code used to get the MemberInfo from the lambda expression.
internal static MemberInfo FindMemberInfoFromLambda(LambdaExpression lambda)
{
var expression = (Expression) lambda;
var flag = false;
while (!flag)
{
switch (expression.NodeType)
{
case ExpressionType.Convert:
expression = ((UnaryExpression) expression).Operand;
continue;
case ExpressionType.Lambda:
expression = ((LambdaExpression) expression).Body;
continue;
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression) expression;
if (memberExpression.Expression.NodeType == ExpressionType.Parameter ||
memberExpression.Expression.NodeType == ExpressionType.Convert)
return memberExpression.Member;
throw new Exception();
default:
flag = true;
continue;
}
}
throw new Exception();
}
This code works great if I were trying to get the ParentProperty of the Parent class, but when I try to get the MemberInfo of the Data property of the Child class, it does not work. I have seen a few StackOverflow questions posted on getting the full name of the child property, but nothing on getting the whole MemberInfo of it. Has anyone done this before or can help point me in the right direction?
The expression you get is MemberExpression, you can grab its Member property directly:
class Program
{
class Parent
{
public int A { get; set; }
public Child Child { get; set; }
}
class Child
{
public string Data { get; set; }
}
public static MemberInfo GetMemberInfo(LambdaExpression exp)
{
var body = exp.Body as MemberExpression;
return body.Member;
}
static void Main(string[] args)
{
Expression<Func<Parent, string>> func1 = p => p.Child.Data;
Console.WriteLine(GetMemberInfo(func1));
Expression<Func<Parent, int>> func2 = p => p.A;
Console.WriteLine(GetMemberInfo(func2));
}
}
Output:
System.String Data
Int32 A
You must be using Expression instead of just Func
In your code in the MemberAccess section you are checking if the member is from the parameter, in this case Parent. If you remove that check then you will get the member for Data
Change this section
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression) expression;
if (memberExpression.Expression.NodeType == ExpressionType.Parameter ||
memberExpression.Expression.NodeType == ExpressionType.Convert)
return memberExpression.Member;
throw new Exception();
To
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression) expression;
return memberExpression.Member;
I don't know why you had the guard for the parameter, if you need it in certain cases then you can create a different method or pass in a parameter.

Entity Framework DbSet dynamic query

I am creating a DbSet from a Type that is passed in and I need to query the database for a dynamic field and value. Using generics I would use the where function with a lambda expression. Can this be achieved from a standard dbSet created as follows ?
DbSet table = dataContext.Set(EntityType);
PropertyInfo propertyInfo = EntityType.GetProperty(PropertyName);
// Expression: "entity"
ParameterExpression parameter = Expression.Parameter(EntityType, "entity");
// Expression: "entity.PropertyName"
MemberExpression propertyValue = Expression.MakeMemberAccess(parameter, propertyInfo);
// Expression: "value"
object convertedValue = Convert.ChangeType(value, propertyInfo.PropertyType);
ConstantExpression rhs = Expression.Constant(convertedValue);
// Expression: "entity.PropertyName == value"
BinaryExpression equal = Expression.Equal(propertyValue, rhs);
// Expression: "entity => entity.PropertyName == value"
LambdaExpression lambda = Expression.Lambda(equal, parameter);
Now need to query the table to get data.
There is a nuget package called 'System.Linq.Dynamic'.
http://dynamiclinq.codeplex.com/
This package allows you to form statements against DbSets using strings, like this:
myContext.MyDbSet.Where("PropertyName == #0", "aValue");
It is possible to do this, with Expressions as you suggest in your question, but this library takes out all the heavy lifting.
I've used this with great success in one of my previous projects.
You can try something like the following:
public class UniqueRecordValidationAttribute : ValidationAttribute
{
public IValidationAttribute DynamicInstance { get; private set; }
public UniqueRecordValidationAttribute(Type type,
params object[] arguments )
{
DynamicInstance =
Activator.CreateInstance(type, arguments) as IValidationAttribute;
}
public override bool IsValid(object value)
{
return DynamicInstance.IsValid(value);
}
}
public class UniqueRecordValidator<C, E, P> : IValidationAttribute
where C : DataContext, new() where E : class
{
Func<E, P, bool> Check { get; set; }
public UniqueRecordValidator(Func<E, P, bool> check)
{
Check = check;
}
public bool IsValid(object value)
{
DataContext dataContext = new C();
Table<E> table = dataContext.GetTable<E>();
return table.Count(i => Check(i as E, (P)value)) == 0;
}
}
public interface IValidationAttribute
{
bool IsValid(object value);
}
and
[UniqueRecordValidation(
typeof(UniqueRecordValidator<AssetTrackingEntities, ATUser_Account, string>),
new Func<ATUser_Account, string, bool>((i, p) => i.User_Login == p))]
public string User_Name { get; set; }
This would be a completely strongly typed solution, but I am not shure the Func<E, P, bool> inside the Count is supported by EF as I could not test that here at the moment. But for LINQ to objects this code does work.
If this does not work you can at least improve it with generics and dynamic LINQ to the following:
public class UniqueRecordValidator<C, E> : IValidationAttribute
where C : DataContext, new() where E : class
{
string PropertyName { get; set; }
public UniqueRecordValidator(string propertyName)
{
PropertyName = propertyName;
}
public bool IsValid(object value)
{
DataContext dataContext = new C();
Table<E> table = dataContext.GetTable<E>();
return table.Count(PropertyName + " = #0", value) == 0;
}
}
[UniqueRecordValidation(
typeof(UniqueRecordValidator<AssetTrackingEntities, ATUser_Account>)
"User_Login")]
public string User_Login { get; set; }

Use type of this Type argument in extension method for TIn of a Func in the same method signature

I was pretty sure that this was possible, but for some reason, I can't seem to figure this out... I am trying to make an extension method off of Type, that will take in a Func to a property from that type, and extract a DefaultValue from the DefaultValueAttribute.
I can get it working, but only if I specify the type arguments for the GetDefaultValue function call. Here is my code as I have it currently:
Person entity:
public class Person
{
public string FirstName { get; set; }
[DefaultValue("1234")]
public string DOB { get; set; }
}
Calls to the method:
//Messing around in LinqPad - .Dump() is LinqPad method
//Works
//typeof(Person).GetDefaultValue<Person, string>(x=>x.DOB).Dump();
//Trying to get to work
//Can't figure out how to get it to infer that TIn is of the Type type.....
typeof(Person).GetDefaultValue(x=> x.DOB).Dump();
This is where the method calls are going to... I am just trying to figure out the means right now before I incorporate into my actual program... Error checking will come into play once I've figured out either how to do it, or to give up b/c it can't be done...
public static class Extensions
{
// Works
// public static TProperty GetDefaultValue<TModel, TProperty>(this Type type, Expression<Func<TModel, TProperty>> exp)
// {
// var property = typeof(TModel).GetProperties().ToList().Single(p => p.Name == GetFullPropertyName(exp));
// var defaultValue = (DefaultValueAttribute)property.GetCustomAttributes(typeof(DefaultValueAttribute), false).FirstOrDefault();
// return (TProperty)defaultValue.Value;
// }
//trying to get to work
//I know that I can't do the following, but it is basically what I am trying to do... I think!
public static TProperty GetDefaultValue<TProperty>(this Type type, Expression<Func<typeof(type), TProperty>> exp)
{
var property = type.GetProperties().ToList().Single(p => p.Name == GetFullPropertyName(exp));
var defaultValue = (DefaultValueAttribute)property.GetCustomAttributes(typeof(DefaultValueAttribute), false).FirstOrDefault();
return (TProperty)defaultValue.Value;
}
//GetFullPropertyName c/o: http://stackoverflow.com/users/105570/
//ref: http://stackoverflow.com/questions/2789504/
public static string GetFullPropertyName<TModel, TProperty>(Expression<Func<TModel, TProperty>> exp)
{
MemberExpression memberExp;
if (!TryFindMemberExpression(exp.Body, out memberExp))
return String.Empty;
var memberNames = new Stack<string>();
do
memberNames.Push(memberExp.Member.Name);
while (TryFindMemberExpression(memberExp.Expression, out memberExp));
return String.Join(".", memberNames.ToArray());
}
private static bool TryFindMemberExpression(Expression exp, out MemberExpression memberExp)
{
memberExp = exp as MemberExpression;
if (memberExp != null)
return true;
if (IsConversion(exp) && exp is UnaryExpression)
{
memberExp = ((UnaryExpression)exp).Operand as MemberExpression;
if (memberExp != null)
return true;
}
return false;
}
private static bool IsConversion(Expression exp)
{
return exp.NodeType == ExpressionType.Convert || exp.NodeType == ExpressionType.ConvertChecked;
}
}
Am I crazy, or is this actually possible? Thank you in advance for your help!
That's not how it works- typeof(Person) doesn't have a property called DOB, but a class whose type is Person does. What you want is to make your extension method generic:
public static TValue GetDefaultValue<TClass, TValue>(this TClass val, Expression<Func<TClass, TValue>> getter) {
var type = typeof(TClass);
var property = type.GetProperties().ToList().Single(p => p.Name == GetFullPropertyName(exp));
var defaultValue = (DefaultValueAttribute)property.GetCustomAttributes(typeof(DefaultValueAttribute), false).FirstOrDefault();
return (TProperty)defaultValue.Value;
}
and call it something like:
Person somePerson = GetMeAPerson();
somePerson.GetDefaultValue(p=>p.DOB);
Note that I have not tested the above, but I've seen similar code in the past that worked. That said, I suspect that what you were originally trying to do isn't very appealing this way because you need to make a Person instance first.
Another, possibly more appealing approach is to not make it an extension method at all:
public static TValue GetDefaultValue<TClass, TValue>(Expression<Func<TClass, TValue>> getter) {
var type = typeof(TClass);
var property = type.GetProperties().ToList().Single(p => p.Name == GetFullPropertyName(exp));
var defaultValue = (DefaultValueAttribute)property.GetCustomAttributes(typeof(DefaultValueAttribute), false).FirstOrDefault();
return (TProperty)defaultValue.Value;
}
Then you can call it without an instance, but the inference isn't as nice):
var defaultValue = GetDefaultValue<Person, DateTime>(p => p.DOB);

Categories