Accessing Method based Embodied Members to build expression trees - c#

Trying to build an order by expression using expression trees. But I am unable to access an expression bodied property of the query result's class.
This is the class structure:
public class AssetFileRecord : IAuditable, IEntity, INavigateToCustomValues
{
public AssetFileRecord()
{
this.UpdatedTimeStamp = DateTime.UtcNow;
}
public AssetFileRecord GetRecord()
{
return this;
}
public Guid Id { get; set; }
public int DisplayId { get; set; }
public string AssetTagNumber { get; set; }
[JObjectIgnore]
public virtual Account Account { get; set; }
public string AccountNumber => Account?.AccountNumber;
public string AuditTrail { get; set; }
public string OldTagNumber { get; set; }
public ActivityCode ActivityCode { get; set; }
[JObjectIgnore]
public virtual ICollection<AssetFileRecordDepreciation> AssetFileRecordDepreciations { get; set; }
// Depreciation Records
public double? AccumulatedDepreciation => Depreciation()?.AccumulatedDepreciation;
public DateTime? DepreciationAsOfDate => Depreciation()?.DepreciationAsOfDate;
public double? LifeMonths => Depreciation()?.LifeMonths;
public double? DepreciationBasis => Depreciation()?.DepreciationBasis;
public double? PeriodDepreciation => Depreciation()?.PeriodDepreciation;
private AssetFileRecordDepreciation Depreciation()
{
return AssetFileRecordDepreciations?.AsQueryable()?.OrderBy(d => d.AssetFileDepreciationBook.BookNo)?.FirstOrDefault();
}
}
I am unable to get to the property AccumulatedDepreciation which is a property of a virtual property of AssetFileRecord.
Below is the current code that works fine for any other non-expression bodied properties.
public static IQueryable<T> BuildOrderByExpression<T>(IQueryable<T> source, string sortProperty, ListSortDirection sortOrder, bool isFirstOrderTerm = true)
{
var type = typeof(T);
var property = type.GetProperty(sortProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var parameter = Expression.Parameter(type, "p");
// ReSharper disable once AssignNullToNotNullAttribute
Expression orderByExp;
var map = AssetFileRecordExpressionHelper.GetExpressionEmbodiedMemberMappings();
if (!map.TryGetValue($"{type.Name}.{property.Name}", out orderByExp))
{
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
orderByExp = Expression.Lambda(propertyAccess, parameter);
}
var typeArguments = new[] { type, property.PropertyType };
var methodBase = isFirstOrderTerm ? "OrderBy" : "ThenBy";
var methodName = sortOrder == ListSortDirection.Ascending ? methodBase : $"{methodBase}Descending";
var resultExp = Expression.Call(typeof(Queryable), methodName, typeArguments, source.Expression, Expression.Quote(orderByExp));
return source.Provider.CreateQuery<T>(resultExp);
}
In the above code "map" is a dictionary of that returns appropriate expression terms for the embodied members except for the ones accessed on top of Depreciation() and is as follows.
public static Dictionary<string, Expression> GetExpressionEmbodiedMemberMappings()
{
var map = new Dictionary<string, Expression>
{
{
$"{nameof(AssetFileRecord)}.{nameof(AssetFileRecord.AccountNumber)}",
(Expression<Func<AssetFileRecord, string>>) (
afr => afr.Account != null ? afr.Account.AccountNumber : null
)
}
};
return map;
}
Expression.Call does not evaluate to a valid SQL query and rather throws an exception.
((System.Data.Entity.Infrastructure.DbQuery<AssetFileRecord>)records).Sql = '((System.Data.Entity.Infrastructure.DbQuery<AssetFileRecord>)records).Sql' threw an exception of type 'System.NotSupportedException'
Intended result: It should append an order by expression to the expression tree generated in the end; while it's failing to do so, when tried to order by an expression bodied property member.
Can someone please help me get this working.

Related

Automate the validation process on properties using FluentValidation Library

Wondering is there a possibility to avoid writing rules for every fields that needs to get validated, for an example all the properties must be validated except Remarks. And I was thinking to avoid writing RuleFor for every property.
public class CustomerDto
{
public int CustomerId { get; set; } //mandatory
public string CustomerName { get; set; } //mandatory
public DateTime DateOfBirth { get; set; } //mandatory
public decimal Salary { get; set; } //mandatory
public string Remarks { get; set; } //optional
}
public class CustomerValidator : AbstractValidator<CustomerDto>
{
public CustomerValidator()
{
RuleFor(x => x.CustomerId).NotEmpty();
RuleFor(x => x.CustomerName).NotEmpty();
RuleFor(x => x.DateOfBirth).NotEmpty();
RuleFor(x => x.Salary).NotEmpty();
}
}
Here you go:
public class Validator<T> : AbstractValidator<T>
{
public Validator(Func<PropertyInfo, bool> filter) {
foreach (var propertyInfo in typeof(T)
.GetProperties()
.Where(filter)) {
var expression = CreateExpression(propertyInfo);
RuleFor(expression).NotEmpty();
}
}
private Expression<Func<T, object>> CreateExpression(PropertyInfo propertyInfo) {
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, propertyInfo);
var conversion = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda<Func<T, object>>(conversion, parameter);
return lambda;
}
}
And it can be used as such:
private static void ConfigAndTestFluent()
{
CustomerDto customer = new CustomerDto();
Validator<CustomerDto> validator = new Validator<CustomerDto>(x=>x.Name!="Remarks"); //we specify here the matching filter for properties
ValidationResult results = validator.Validate(customer);
}

Get Name and Value of a property of a POCO object using an expression

I'm looking for a way to get the name and value of a proptery in a POCO object. I've tried many solutions but can't seem to get them to work. I really liked this older solution but it causes a null ref error.
Here's kind of what I'm trying to do:
public class POCO
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Description { get; set; }
}
public class POCOValidationResult
{
public bool IsValid { get; set; }
public string Error { get; set; }
}
public abstract class Validator<T> where T : class
{
public T Entity { get; set; }
public abstract POCOValidationResult Validate();
protected POCOValidationResult ValidateStringPropertyToLengthOf(Expression<Func<T, object>> expression, int maxLength)
{
var propertyName = getPropertyName(expression);
var propertyValue = getPropertyValue(expression);
if (propertyValue.Length > maxLength)
{
return new POCOValidationResult()
{
Error = string.Format("{0} value is too long. Must be less or equal to {1}", propertyName, maxLength.ToString())
};
}
return new POCOValidationResult() { IsValid = true };
}
internal string getPropertyName(Expression<Func<T, object>> expression)
{
var memberExpersion = (MemberExpression)expression.Body;
return memberExpersion.Member.Name;
}
internal string getPropertyValue<R>(Expression<Func<T, R>> expression)
{
//struggling to get this to work
var me = (MemberExpression)expression.Body; // (MemberExpression)((MemberExpression)expression.Body).Expression;
var ce = (ConstantExpression)me.Expression; // Error here!
var fieldInfo = ce.Value.GetType().GetField(me.Member.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var value = fieldInfo.GetValue(ce.Value);
}
}
public class POCOValidator : Validator<POCO>
{
public override POCOValidationResult Validate()
{
var surnameValidationResult = ValidateStringPropertyToLengthOf(p => p.Surname, 10);
if (!surnameValidationResult.IsValid)
return surnameValidationResult;
//var descriptionValidationResult = ValidateStringPropertyToLengthOf(p => p.Description, 100);
//if (!descriptionValidationResult.IsValid)
// return descriptionValidationResult;
//var nameValidationResult = ValidateStringPropertyToLengthOf(p => p.Name, 15);
//if (!nameValidationResult.IsValid)
// return nameValidationResult;
return new POCOValidationResult() { IsValid = true };
}
}
public class WorkerBee
{
public void ImDoingWorkReally()
{
var pocoVallidation = new POCOValidator()
{
Entity = new POCO()
{
ID = 1,
Name = "James",
Surname = "Dean",
Description = "I'm not 007!"
}
};
var vallidationResult = pocoVallidation.Validate();
if (!vallidationResult.IsValid)
{
return;
}
//continue to do work...
}
}
class Program
{
static void Main(string[] args)
{
var workerBee = new WorkerBee();
workerBee.ImDoingWorkReally();
}
}
So as you can see, I'm trying to get the Property's name and value [by using an Expression (p => p.Surname) as a parameter in the method ValidateStringPropertyToLengthOf(...)]. The problem is that I'm getting a null ref error in getPropertyValue(Expression<Func<T, object>> expression) when it calls var ce = (ConstantExpression)me.Expression;
So does anyone have ideas on how to get this to work?
Thanks for taking the time to look into this. I really appreciate it and hope that my question also is helpful for others as I think this can be rather useful if I can get this to work.
EDIT: I've made the change as mentioned below in the comments and still getting the error "Unable to cast object of type 'System.Linq.Expressions.TypedParameterExpression' to type 'System.Linq.Expressions.ConstantExpression" when I run my unit test.
I worked out a solution (unfortunately the comments were not helpful). Here's the code that works:
public class POCO
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Description { get; set; }
}
public class POCOValidationResult
{
public bool IsValid { get; set; }
public string Error { get; set; }
}
public abstract class Validator<T> where T : class
{
public T Entity { get; set; }
public abstract POCOValidationResult Validate();
protected POCOValidationResult ValidateStringPropertyToLengthOf(Expression<Func<T, object>> expression, int maxLength)
{
var propertyName = getPropertyName(expression);
var propertyValue = getPropertyValue(expression);
if (propertyValue.Length > maxLength)
{
return new POCOValidationResult()
{
Error = string.Format("{0} value is too long. Must be less or equal to {1}", propertyName, maxLength.ToString())
};
}
return new POCOValidationResult() { IsValid = true };
}
internal string getPropertyName(Expression<Func<T, object>> expression)
{
var memberExpersion = (MemberExpression)expression.Body;
return memberExpersion.Member.Name;
}
internal string getPropertyValue(Expression<Func<T, object>> expression)
{
var memberExpression = expression.Body as MemberExpression;
var propertyInfo = memberExpression.Member as PropertyInfo;
return propertyInfo.GetValue(Entity, null).ToString();
}
}
public class POCOValidator : Validator<POCO>
{
public override POCOValidationResult Validate()
{
var surnameValidationResult = ValidateStringPropertyToLengthOf(p => p.Surname, 10);
if (!surnameValidationResult.IsValid)
return surnameValidationResult;
var descriptionValidationResult = ValidateStringPropertyToLengthOf(p => p.Description, 100);
if (!descriptionValidationResult.IsValid)
return descriptionValidationResult;
var nameValidationResult = ValidateStringPropertyToLengthOf(p => p.Name, 15);
if (!nameValidationResult.IsValid)
return nameValidationResult;
return new POCOValidationResult() { IsValid = true };
}
}
public class WorkerBee
{
public void ImDoingWorkReally()
{
var pocoVallidation = new POCOValidator()
{
Entity = new POCO()
{
ID = 1,
Name = "James",
Surname = "Dean",
Description = "I'm not 007!"
}
};
var vallidationResult = pocoVallidation.Validate();
if (!vallidationResult.IsValid)
{
return;
}
//continue to do work...
}
}
class Program
{
static void Main(string[] args)
{
var workerBee = new WorkerBee();
workerBee.ImDoingWorkReally();
}
}
Note the change for getPropertyValue:
internal string getPropertyValue(Expression<Func<T, object>> expression)
{
var memberExpression = expression.Body as MemberExpression;
var propertyInfo = memberExpression.Member as PropertyInfo;
return propertyInfo.GetValue(Entity, null).ToString();
}
I noticed that MaudDib's getPropertyValue method only works for properties directly on the object. e.g. it will work for myObj.Value, but not for myObj.Something.Value. The following works for both. (I don't know if there's a better way, though).
Usage: var myValue = GetPropertyValue(myObj, o => o.Something.Value);
public static object GetPropertyValue<T>(T obj, Expression<Func<T, object>> expression)
{
var members = new CompositeExpressionVisitor().GetMembers(expression);
object currentVal = obj;
foreach (var part in members)
{
currentVal = GetPropertyValue(currentVal, part);
}
return currentVal;
}
private static object GetPropertyValue(object obj, MemberInfo member)
{
var propertyInfo = (PropertyInfo)member;
return propertyInfo.GetValue(obj, null);
}
private class CompositeExpressionVisitor : ExpressionVisitor
{
private readonly List<MemberInfo> _members = new List<MemberInfo>();
protected override Expression VisitMember(MemberExpression node)
{
_members.Add(node.Member);
return base.VisitMember(node);
}
public IReadOnlyCollection<MemberInfo> GetMembers(Expression e)
{
Visit(e is LambdaExpression expression ? expression.Body : e);
_members.Reverse();
return _members;
}
}
And, if you want the full path of the expression, e.g. you don't just want the leaf... you can do this:
Usage: var myValue = NameOf(() => myObj.Something.Value);
Returns: myObj.Something.Value
Or: var myValue = NameOf(() => myObj.Something.Value, 1);
Returns: Something.Value
public static string NameOf(Expression<Func<object>> expression, int startIndex = 0)
{
return new CompositeExpressionVisitor().GetPath(expression, startIndex);
}
private class CompositeExpressionVisitor : ExpressionVisitor
{
private readonly List<string> _parts = new List<string>();
protected override Expression VisitMember(MemberExpression node)
{
_parts.Add(node.Member.Name);
return base.VisitMember(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
_parts.Add(node.Name);
return base.VisitParameter(node);
}
public string GetPath(Expression e, int startIndex = 0)
{
Visit(e is LambdaExpression expression ? expression.Body : e);
_parts.Reverse();
return string.Join(".", _parts.Skip(startIndex));
}
}

How to make an OrderBy keySelector for a sub property?

I have the following function to order an array based on a property specified by a string:
private static MethodInfo OrderByMethod = typeof(Enumerable)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(method => method.Name == "OrderBy"
&& method.GetParameters().Length == 2)
.Single();
public static IOrderedEnumerable<TSource> OrderByProperty<TSource>(IEnumerable<TSource> source, string propertyName)
{
// TODO: Lots of validation :)
PropertyInfo property = typeof(TSource).GetProperty(propertyName);
MethodInfo getter = property.GetGetMethod();
Type propType = property.PropertyType;
Type funcType = typeof(Func<,>).MakeGenericType(typeof(TSource), propType);
Delegate func = Delegate.CreateDelegate(funcType, getter);
MethodInfo constructedMethod = OrderByMethod.MakeGenericMethod(
typeof(TSource), propType);
return (IOrderedEnumerable<TSource>)constructedMethod.Invoke(null,
new object[] { source, func });
}
I have to following test class:
public class TestClass
{
public int Szam { get; set; }
public string Szoveg { get; set; }
public InnerClass BelsoData { get; set; }
}
public class InnerClass
{
public string InnerText { get; set; }
}
The order by works for a property of the "TestClass". How should I modify the code so that I can order by the properties of the "InnerClass" subclass?

How to use lambda expression to implement UniqueAttribute?

I just want to use code like this to implement a customer validate attribute named UniqueAttribute in MVC, but I am not clearly knowing about lamda expression.
The edit model class
The UniqueAttribute class :
public class UniqueAttribute<TService,TEntity,TKey> : ValidationAttribute
where TService : IDML<TEntity>
where TEntity:IPMMIdentity
{
public Expression<Func<TEntity, TKey>> UniqueExpression { get; set; }
public override bool IsValid(object value)
{
var service = IocContainerHelper.Resolve<TService>();
return !service.Contains(UniqueExpression, (TKey)value);
}
}
I have create a "Contains" method in my class where implement the interface IDML:
public virtual bool Contains<TKey>(Expression<Func<T, TKey>> selector, TKey value)
{
var predicate = Expression.Lambda<Func<T, bool>>(
Expression.Equal(selector.Body, Expression.Constant(value, typeof(TKey)))
, selector.Parameters);
return _repository.Count(predicate)>0;
}
So I want to define my edit model like below:
public class EditUser
{
public int Id { get; set; }
[Unique<IUserService,User,string>(UniqueExpression = t=>t.LoginName)] // error line,can not be complied.
public string LoginName { get; set; }
}
The error message is:
Cannot convert source type 'System.Linq.Expression.Expression<System.Func<User,string>>' to target type 'System.Linq.Expression.Expression<System.Func<TEntity,TKey>>'
How to correct it ? Any help would be much appreciated, thanks.
UPDATE:
I modified validate class, and it works well.
public class UniqueAttribute:ValidationAttribute
{
public UniqueAttribute(Type serviceType, Type entityType, string propertyName)
{
ServiceType = serviceType;
EntityType = entityType;
PropertyName = propertyName;
}
public Type ServiceType { get; private set; }
public Type EntityType { get; private set; }
public string PropertyName { get; private set; }
protected override ValidationResult IsValid(object value, System.ComponentModel.DataAnnotations.ValidationContext validationContext)
{
var propertyInfo = EntityType.GetProperty(PropertyName);
const string methodName = "Get";
object convertedValue = Convert.ChangeType(value, propertyInfo.PropertyType);
var constExpression = Expression.Constant(convertedValue);
var parameter = Expression.Parameter(EntityType, "te");
var memberExpression = Expression.MakeMemberAccess(parameter, propertyInfo);
var equal = Expression.Equal(memberExpression, constExpression);
var lambda = Expression.Lambda(equal, parameter);
var service = IocContainerHelper.Resolve(ServiceType);
const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public;
var method = service.GetType().GetMethod(methodName, bindingFlags);
if (method == null)
{
throw new NullReferenceException(string.Format("{0} have not suitable method named '{1}'", service.GetType().FullName, methodName));
}
object id = validationContext.ObjectInstance.GetId();
object data = method.Invoke(service, new object[] { lambda });
if (!data.GetId().Equals(id))
{
var errorMessage = string.Format("{0} is used。", convertedValue);
return new ValidationResult(errorMessage);
}
return ValidationResult.Success;
}
The instance of ServiceType implement a Get(Expression> condition) method:
public virtual T Get(Expression<Func<T, bool>> condition)
{
return _repository.Get(condition);
}
In edit model, it is used like:
[Required]
[Unique(typeof(IUserService),typeof(User),"LoginName")]
[StringLength(50)]
[Display(Name = "Login Name")]
public string LoginName { get; set; }
Hope it can help you.

Creating an Expression<Func<T, object>> variable by reflection

I have created the following class:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
I am able to set the following statement to a method parameter:
myClass.SetFieldName<Person>(p => p.LastName);
The type of the parameter is:
Expression<Func<Person, object>>
Now what I am trying to accomplish is to call the SetFieldName Method for a property found by reflection. Imagine I have an instance of PropertyInfo (for Person.LastName). I tried to create the Expression by using its Lambda method, but I failed.
So it would be very nice, if you can help me with this.
Regards,
Koray
You just need to expand a little bit the flem's answer:
using System;
using System.Linq.Expressions;
using NUnit.Framework;
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public static class ExpressionBuilder
{
public static Expression<Func<TClass, TProperty>> Build<TClass, TProperty>(string fieldName)
{
var param = Expression.Parameter(typeof(TClass));
var field = Expression.PropertyOrField(param, fieldName);
return Expression.Lambda<Func<TClass, TProperty>>(field, param);
}
}
[TestFixture]
public class Test
{
[Test]
public void TestExpressionBuilder()
{
var person = new Person { FirstName = "firstName", LastName = "lastName" };
var expression = ExpressionBuilder.Build<Person, string>("FirstName");
var firstName = expression.Compile()(person);
Assert.That(firstName, Is.EqualTo(person.FirstName));
}
}
// reflected field name
string fieldName = "LastName";
// create the parameter of the expression (Person)
ParameterExpression param = Expression.Parameter(typeof(Person), string.Empty);
// create the expression as a get accessor of a particular
// field of the parameter (p.LastName)
Expression field = Expression.PropertyOrField(param, fieldName);
public static class LambdaExpressionExtensions
{
public static Expression<Func<TInput, object>> ToUntypedPropertyExpression<TInput, TOutput> (this Expression<Func<TInput, TOutput>> expression)
{
var memberName = ((MemberExpression)expression.Body).Member.Name;
var param = Expression.Parameter(typeof(TInput));
var field = Expression.Property(param, memberName);
return Expression.Lambda<Func<TInput, object>>(field, param);
}
}

Categories