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

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);
}
}

Related

Using LINQ to select EF properties in DBSet with specific Attributes

Is there a way to perform a LINQ query on Entity Framework DBSets and only return the properties that have a specific custom attribute?
My goal is to perform a query and return only the properties/columns that I need for an export.
I also want to make this an extension for IEnumerable as I have many separate EF Classes that will use this export attribute.
This is how I am visualizing it:
public class Person
{
[Export]
public string FirstName { get; set; }
[Export]
public string LastName { get; set; }
[Export]
public string Address { get; set; }
[NeverExport]
public string SocialSecurityNumber { get; set; }
}
public void main()
{
var people = PersonRepository.GetAll();
var exportResults = people.GetByAttribute(ExportAttribute);
{
public static IEnumerable<object> GetByAttribute(this IEnumerable<object> list, Attribute attribute)
{
return list
.Select(x =>
// Select properties with { attribute } (ie. [Export])
);
}
I've made a very basic example for your problem. In short, you want to get all Properties, where a specific Attribute is present. You can achive this via reflection. First, you want all properties of a specific type, then only the properties, where a specific attribute is present.
Here is my example code:
using System;
using System.Linq;
using System.Reflection;
namespace Sandbox
{
public class Program
{
public static void Main(string[] args)
{
var attrs = typeof(User).GetProperties().Where(x => x.GetCustomAttributes().Any(y => y.GetType() == typeof(Custom)));
var users = new User[]
{
new User() { ID = 1, Name = "John", Lastname = "Doe" },
new User() { ID = 2, Name = "Jane", Lastname = "Doe" }
};
foreach(var u in users)
{
foreach(var attr in attrs)
{
Console.WriteLine(typeof(User).GetProperty(attr.Name).GetValue(u, null));
}
}
}
}
public class User
{
public string Name { get; set; }
[Custom]
public int ID { get; set; }
[Custom]
public string Lastname { get; set; }
}
public class Custom : System.Attribute
{
}
}
The following code projects any IQueryable to IQueryable<ExpandoObject> and you can use it for exporting data. Additionally code caches projection expression for reusing later in application.
Usage is simple:
var exportResult = anyQuery.ProjectForExport().ToList();
And implementation:
[AttributeUsage(AttributeTargets.Property)]
public class ExportAttribute : Attribute
{
}
public static class ExportTools
{
private static readonly ConstructorInfo _expandoObjectConstructor =
typeof(ExpandoObject).GetConstructor(Type.EmptyTypes) ?? throw new InvalidOperationException();
private static readonly MethodInfo _expandoAddMethodInfo = typeof(IDictionary<string, object>).GetTypeInfo()
.GetRuntimeMethods()
.Single(mi => mi.Name == nameof(IDictionary<string, object>.Add) && mi.GetParameters().Length == 2);
public static class ProjectionCache<T>
{
public static Expression<Func<T, ExpandoObject>> ProjectionExpression { get; } = BuildProjection();
private static Expression<Func<T, ExpandoObject>> BuildProjection()
{
var param = Expression.Parameter(typeof(T), "e");
var properties = typeof(T).GetProperties()
.Where(p => p.GetCustomAttributes().Any(a => a is ExportAttribute)).ToList();
if (properties.Count == 0)
throw new InvalidOperationException($"Type '{typeof(T).Name}' has no defined Export properties.");
var expr = (Expression)Expression.ListInit(
Expression.New(_expandoObjectConstructor),
properties.Select(p =>
{
var readerExpr = Expression.MakeMemberAccess(param, p);
return Expression.ElementInit(_expandoAddMethodInfo, Expression.Constant(p.Name), readerExpr);
}));
var lambda = Expression.Lambda<Func<T, ExpandoObject>>(expr, param);
return lambda;
}
}
public static IQueryable<ExpandoObject> ProjectForExport<T>(this IQueryable<T> query)
{
return query.Select(ProjectionCache<T>.ProjectionExpression);
}
}

How do I set return type from method with an Expression<Func<TModel, TProperty>>

I am wanting to call a method with the signiture something like
string fullname = UserBuilder.DefaultProperty(e => e.Fullname)
I have created a builder class and want to return the default property from a default supplied
public class BuilderGeneric<TModel>
{
private readonly TModel _defaultModel;
public BuilderGeneric(TModel defaultModel)
{
_defaultModel = defaultModel;
}
public object DefaultProperty<TProperty>([NotNull] Expression<Func<TModel, TProperty>> propertyExpression)
{
// Reflection here to get the property
// ...
var propertyType = myPropInfo.PropertyType;
var propertyValue = myPropInfo.GetValue(_defaultModel);
return propertyValue;
}
}
How can I make it so that I dont return an object back but instead the type of the property in the e => e.Fullname) in that example it would be a string
Thanks #canton7
I was trying to create a generic builder class which I have included below along with a unit test.
I hope this helps anyone trying to to a similar thing.
public class BuilderGeneric<TModel>
where TModel : class
{
private readonly TModel _defaultModel;
private TModel BuilderEntity { get; }
public BuilderGeneric(TModel defaultModel)
{
_defaultModel = defaultModel;
BuilderEntity = (TModel)Activator.CreateInstance(typeof(TModel));
}
public TProperty DefaultProperty<TProperty>(Func<TModel, TProperty> property)
{
return property(_defaultModel);
}
public BuilderGeneric<TModel> With<TProperty>(Expression<Func<TModel, TProperty>> property, TProperty value)
{
var memberExpression = (MemberExpression)property.Body;
var propertyInfo = (PropertyInfo)memberExpression.Member;
propertyInfo.SetValue(BuilderEntity, value);
return this;
}
public TModel Build()
{
return BuilderEntity;
}
}
public class UserBuilder : BuilderGeneric<UserProfile>
{
public UserBuilder()
: base(new UserProfile()
{
FullName = "Ian Bowyer",
EmailAddress = "Email#Email.com",
MyGuid = new Guid("12345678-1234-1234-1234-123456789012")
})
{
}
}
public class UserProfile
{
public string FullName { get; set; }
public string EmailAddress { get; set; }
public Guid MyGuid { get; set; }
}
public class TestGenericBuilder
{
[Test]
public void TestTheDefaultPropertyReturns()
{
// Assert
var userBuilder = new UserBuilder()
.With(e => e.FullName, "Bobby Tables")
.With(e => e.MyGuid, new Guid("85EC85CF-2892-40B8-BF63-0C621F78BE27"));
var user = userBuilder.Build();
// Act
var defaultFullname = userBuilder.DefaultProperty(e => e.FullName);
// Assert
defaultFullname.Should().Be("Ian Bowyer");
user.FullName.Should().Be("Bobby Tables");
}
}

Accessing Method based Embodied Members to build expression trees

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.

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);
}

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.

Categories