I can successfully replace simple parameter types in a lambda expression thanks to some answers on a previous question but I cannot figure out how to replace parameters from an incoming lambda to a nested parameter.
Consider the following objects:
public class DtoColour {
public DtoColour(string name)
{
Name = name;
}
public string Name { get; set; }
public ICollection<DtoFavouriteColour> FavouriteColours { get; set; }
}
public class DtoPerson
{
public DtoPerson(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
FavouriteColours = new Collection<DtoFavouriteColour>();
}
public string FirstName { get; private set; }
public string LastName { get; private set; }
public ICollection<DtoFavouriteColour> FavouriteColours { get; set; }
}
public class DtoFavouriteColour
{
public DtoColour Colour { get; set; }
public DtoPerson Person { get; set; }
}
public class DomainColour {
public DomainColour(string name)
{
Name = name;
}
public string Name { get; set; }
public ICollection<DomainPerson> People { get; set; }
}
public class DomainPerson {
public DomainPerson(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
Colours = new Collection<DomainColour>();
}
public string FirstName { get; private set; }
public string LastName { get; private set; }
public ICollection<DomainColour> Colours { get; set; }
}
and a Repository:
public class ColourRepository {
private IList<DtoColour> Colours { get; set; }
public ColourRepository()
{
var favColours = new Collection<DtoFavouriteColour>
{
new DtoFavouriteColour() { Person = new DtoPerson("Peter", "Parker") },
new DtoFavouriteColour() { Person = new DtoPerson("John", "Smith") },
new DtoFavouriteColour() { Person = new DtoPerson("Joe", "Blogs") }
};
Colours = new List<DtoColour>
{
new DtoColour("Red") { FavouriteColours = favColours },
new DtoColour("Blue"),
new DtoColour("Yellow")
};
}
public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
{
var coonvertedPred = MyExpressionVisitor.Convert(predicate);
return Colours.Where(coonvertedPred).Select(c => new DomainColour(c.Name)).ToList();
}
}
and finally an expression visitor which should convert the predicate into the correct one for the Dto Models
public class MyExpressionVisitor : ExpressionVisitor
{
private ReadOnlyCollection<ParameterExpression> _parameters;
public static Func<DtoColour, bool> Convert<T>(Expression<T> root)
{
var visitor = new MyExpressionVisitor();
var expression = (Expression<Func<DtoColour, bool>>)visitor.Visit(root);
return expression.Compile();
}
protected override Expression VisitParameter(ParameterExpression node)
{
var param = _parameters?.FirstOrDefault(p => p.Name == node.Name);
if (param != null)
{
return param;
}
if(node.Type == typeof(DomainColour))
{
return Expression.Parameter(typeof(DtoColour), node.Name);
}
if (node.Type == typeof(DomainPerson))
{
return Expression.Parameter(typeof(DtoFavouriteColour), node.Name);
}
return node;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
_parameters = VisitAndConvert<ParameterExpression>(node.Parameters, "VisitLambda");
return Expression.Lambda(Visit(node.Body), _parameters);
}
protected override Expression VisitMember(MemberExpression node)
{
var exp = Visit(node.Expression);
if (node.Member.DeclaringType == typeof(DomainColour))
{
if (node.Type == typeof(ICollection<DomainPerson>))
{
return Expression.MakeMemberAccess(exp, typeof(DtoColour).GetProperty("FavouriteColours"));
}
return Expression.MakeMemberAccess(exp, typeof(DtoColour).GetProperty(node.Member.Name));
}
if (node.Member.DeclaringType == typeof(DomainPerson))
{
var nested = Expression.MakeMemberAccess(exp, typeof(DtoFavouriteColour).GetProperty("Person"));
return Expression.MakeMemberAccess(nested, typeof(DtoPerson).GetProperty(node.Member.Name));
}
return base.VisitMember(node);
}
}
Currently I get the following Exception
[System.ArgumentException: Expression of type
'System.Collections.Generic.ICollection1[ExpressionVisitorTests.DtoFavouriteColour]'
cannot be used for parameter of type
'System.Collections.Generic.IEnumerable1[ExpressionVisitorTests.DomainPerson]'
of method 'Boolean
Any[DomainPerson](System.Collections.Generic.IEnumerable1[ExpressionVisitorTests.DomainPerson],
System.Func2[ExpressionVisitorTests.DomainPerson,System.Boolean])']
Here is a dotnetfiddle of it not working.
Thank in advance for any help.
After some more searching I came across this answer by John Skeet which has led to me coming up with a working solution which involves adding an override for VisitMethodCall method on the ExpressionVisitor to replace the original MethodInfo with a new one for the correct type of collection.
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(Enumerable) && node.Arguments[0].Type == typeof(ICollection<DomainPerson>))
{
Expression obj = Visit(node.Object);
IEnumerable<Expression> args = Visit(node.Arguments);
if (obj != node.Object || args != node.Arguments)
{
var generic = typeof(Enumerable).GetMethods()
.Where(m => m.Name == node.Method.Name)
.Where(m => m.GetParameters().Length == node.Arguments.Count)
.Single();
var constructed = generic.MakeGenericMethod(typeof(DtoFavouriteColour));
return Expression.Call(obj, constructed, args);
}
}
return node;
}
I also needed to make sure my reference to the _parameters collection wasn't replaced by nested calls to VisitLambda<T> which might happen whilst visiting node.Body.
protected override Expression VisitLambda<T>(Expression<T> node)
{
var parameters = VisitAndConvert(node.Parameters, "VisitLambda");
// ensure parameters set but dont let original reference
// be overidden by nested calls
_parameters = parameters;
return Expression.Lambda(Visit(node.Body), parameters);
}
See dotnetfiddle for fully working solution.
If anyone has a better/more elegant solution please add an answer for me to mark.
You already solved the concrete issue, so I can't say if what I'm going to propose you is better/more elegant, but for sure is a bit more generic (removed the concrete types/properties/assumptions), hence can be reused for translating similar expressions from different model types.
Here is the code:
public class ExpressionMap
{
private Dictionary<Type, Type> typeMap = new Dictionary<Type, Type>();
private Dictionary<MemberInfo, Expression> memberMap = new Dictionary<MemberInfo, Expression>();
public ExpressionMap Add<TFrom, TTo>()
{
typeMap.Add(typeof(TFrom), typeof(TTo));
return this;
}
public ExpressionMap Add<TFrom, TFromMember, TTo, TToMember>(Expression<Func<TFrom, TFromMember>> from, Expression<Func<TTo, TToMember>> to)
{
memberMap.Add(((MemberExpression)from.Body).Member, to.Body);
return this;
}
public Expression Map(Expression source) => new MapVisitor { map = this }.Visit(source);
private class MapVisitor : ExpressionVisitor
{
public ExpressionMap map;
private Dictionary<Type, ParameterExpression> parameterMap = new Dictionary<Type, ParameterExpression>();
protected override Expression VisitLambda<T>(Expression<T> node)
{
return Expression.Lambda(Visit(node.Body), node.Parameters.Select(Map));
}
protected override Expression VisitParameter(ParameterExpression node) => Map(node);
protected override Expression VisitMember(MemberExpression node)
{
var expression = Visit(node.Expression);
if (expression == node.Expression)
return node;
Expression mappedMember;
if (map.memberMap.TryGetValue(node.Member, out mappedMember))
return Visit(mappedMember);
return Expression.PropertyOrField(expression, node.Member.Name);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Object == null && node.Method.IsGenericMethod)
{
// Static generic method
var arguments = Visit(node.Arguments);
var genericArguments = node.Method.GetGenericArguments().Select(Map).ToArray();
var method = node.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArguments);
return Expression.Call(method, arguments);
}
return base.VisitMethodCall(node);
}
private Type Map(Type type)
{
Type mappedType;
return map.typeMap.TryGetValue(type, out mappedType) ? mappedType : type;
}
private ParameterExpression Map(ParameterExpression parameter)
{
var mappedType = Map(parameter.Type);
ParameterExpression mappedParameter;
if (!parameterMap.TryGetValue(mappedType, out mappedParameter))
parameterMap.Add(mappedType, mappedParameter = Expression.Parameter(mappedType, parameter.Name));
return mappedParameter;
}
}
}
and the usage for your concrete example:
public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
{
var map = new ExpressionMap()
.Add<DomainColour, DtoColour>()
.Add((DomainColour c) => c.People, (DtoColour c) => c.FavouriteColours.Select(fc => fc.Person))
.Add<DomainPerson, DtoPerson>();
var mappedPredicate = ((Expression<Func<DtoColour, bool>>)map.Map(predicate));
return Colours.Where(mappedPredicate.Compile()).Select(c => new DomainColour(c.Name)).ToList();
}
As you can see, it allows you to define a simple mapping from one type to another, and optionally from member of one type to member/expression of another type (as soon as they are compatible) using "fluent" syntax with lambda expressions. The members that have no specified mapping are mapped by name as in the original code.
Once the mappings are defined, the actual processing of course is done by a custom ExpressionVisitor, similar to yours. The difference is that it maps and consolidates ParameterExpressions by type, and also translates every static generic method, thus should work also with Queryable and similar.
Related
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);
}
}
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");
}
}
Consider the following classes:
public class Potato
{
[Key]
public string Farm { get; set; }
[Key]
public int Size { get; set; }
public string Trademark { get; set; }
}
public class Haybell
{
[Key]
public string color { get; set; }
public int StrawCount { get; set; }
}
public class Frog
{
[Key]
public bool IsAlive { get; set; }
[Key]
public bool IsVirulent { get; set; }
public byte LimbCount { get; set; } = 4;
public ConsoleColor Color { get; set; }
}
Each class has properties with [Key] attribute. Is it possible to dynamically group an IEnumerable of any of these classes by their respective [Key] attributes?
I would go for adding extension methods for each your types, like
Option 1:
static class Extensions
{
public static IEnumerable<IGrouping<Tuple<string, int>, Potato>>
GroupByPrimaryKey(this IEnumerable<Potato> e)
{
return e.GroupBy(p => Tuple.Create(p.Farm, p.Size));
}
public static IEnumerable<IGrouping<Tuple<bool, bool>, Frog>>
GroupByPrimaryKey(this IEnumerable<Frog> e)
{
return e.GroupBy(p => Tuple.Create(p.IsAlive, p.IsVirulent));
}
}
If there are lots of types, you may generate the code using t4.
Usage: .GroupByPrimaryKey().
Option 2:
A simpler variation:
static class Extensions
{
public static Tuple<string, int> GetPrimaryKey(this Potato p)
{
return Tuple.Create(p.Farm, p.Size);
}
public static Tuple<bool, bool> GetPrimaryKey(this Frog p)
{
return Tuple.Create(p.IsAlive, p.IsVirulent);
}
}
Usage: .GroupBy(p => p.GetPrimaryKey()).
Option 3:
A solution with reflection is possible, but will be slow. Sketch (far from production-ready!)
class CombinedKey : IEquatable<CombinedKey>
{
object[] _keys;
CombinedKey(object[] keys)
{
_keys = keys;
}
public bool Equals(CombinedKey other)
{
return _keys.SequenceEqual(other._keys);
}
public override bool Equals(object obj)
{
return obj is CombinedKey && Equals((CombinedKey)obj);
}
public override int GetHashCode()
{
return 0;
}
public static CombinedKey GetKey<T>(T instance)
{
return new CombinedKey(GetKeyAttributes(typeof(T)).Select(p => p.GetValue(instance, null)).ToArray());
}
private static PropertyInfo[] GetKeyAttributes(Type type)
{
// you definitely want to cache this
return type.GetProperties()
.Where(p => Attribute.GetCustomAttribute(p, typeof(KeyAttribute)) != null)
.ToArray();
}
}
Usage: GroupBy(p => CombinedKey.GetKey(p))
The challenge here is that you need to build an anonymous type in order to have a GroupBy Expression that can translate to SQL or any other LINQ provider.
I'm not sure that you can do that using reflection (not without some really complex code to create an anonymous type at runtime). But you could create the grouping expression if you were willing to provide an example of the anonymous type as the seed.
public static Expression<Func<TSource, TAnon>> GetAnonymous<TSource,TAnon>(TSource dummy, TAnon example)
{
var ctor = typeof(TAnon).GetConstructors().First();
var paramExpr = Expression.Parameter(typeof(TSource));
return Expression.Lambda<Func<TSource, TAnon>>
(
Expression.New
(
ctor,
ctor.GetParameters().Select
(
(x, i) => Expression.Convert
(
Expression.Property(paramExpr, x.Name), // fetch same named property
x.ParameterType
)
)
), paramExpr);
}
And here's how you would use it (Note: the dummy anonymous type passed to the method is there in order to make the anonymous type a compile-time Type, the method doesn't care what the values are that you pass in for it.) :
static void Main()
{
var groupByExpression = GetAnonymous(new Frog(), new {IsAlive = true, IsVirulent = true});
Console.WriteLine(groupByExpression);
var frogs = new []{ new Frog{ IsAlive = true, IsVirulent = false}, new Frog{ IsAlive = false, IsVirulent = true}, new Frog{ IsAlive = true, IsVirulent = true}};
var grouped = frogs.AsQueryable().GroupBy(groupByExpression);
foreach (var group in grouped)
{
Console.WriteLine(group.Key);
}
}
Which produces:
Param_0 => new <>f__AnonymousType0`2(Convert(Param_0.IsAlive, Boolean), Convert(Param_0.IsVirulent, Boolean))
{ IsAlive = True, IsVirulent = False }
{ IsAlive = False, IsVirulent = True }
{ IsAlive = True, IsVirulent = True }
Somebody had posted a valid answer and removed it later for some reason. Here it is:
Combined key class:
class CombinedKey<T> : IEquatable<CombinedKey<T>>
{
readonly object[] _keys;
public bool Equals(CombinedKey<T> other)
{
return _keys.SequenceEqual(other._keys);
}
public override bool Equals(object obj)
{
return obj is CombinedKey<T> key && Equals(key);
}
public override int GetHashCode()
{
int hash = _keys.Length;
foreach (object o in _keys)
{
if (o != null)
{
hash = hash * 13 + o.GetHashCode();
}
}
return hash;
}
readonly Lazy<Func<T, object[]>> lambdaFunc = new Lazy<Func<T, object[]>>(() =>
{
Type type = typeof(T);
var paramExpr = Expression.Parameter(type);
var arrayExpr = Expression.NewArrayInit(
typeof(object),
type.GetProperties()
.Where(p => (Attribute.GetCustomAttribute(p, typeof(KeyAttribute)) != null))
.Select(p => Expression.Convert(Expression.Property(paramExpr, p), typeof(object)))
.ToArray()
);
return Expression.Lambda<Func<T, object[]>>(arrayExpr, paramExpr).Compile();
}, System.Threading.LazyThreadSafetyMode.PublicationOnly);
public CombinedKey(T instance)
{
_keys = lambdaFunc.Value(instance);
}
}
Caller function and the actual usage:
public static class MyClassWithLogic
{
//Caller to CombinedKey class
private static CombinedKey<Q> NewCombinedKey<Q>(Q instance)
{
return new CombinedKey<Q>(instance);
}
//Extension method for IEnumerables
public static IEnumerable<T> DistinctByPrimaryKey<T>(this IEnumerable<T> entries) where T : class
{
return entries.AsQueryable().GroupBy(NewCombinedKey)
.Select(r => r.First());
}
}
Yes, it is relatively slow, so if it is a problem, then Klaus Gütter's solutions are the way to go.
I'm attempting to create a mapper so PetaPoco can hydrate and persist POCOs with Enumeration class properties. See more about Enumeration classes here or here.
For instance, Take this class.
public class PetType : Headspring.Enumeration<PetType>
{
public static readonly PetType Frog = new PetType(1, "Frog");
public static readonly PetType Cat = new PetType(2, "Cat");
public static readonly PetType Fish = new PetType(3, "Fish");
public static readonly PetType Dog = new PetType(4, "Dog");
private PetType(int value, string displayName) : base(value, displayName) { }
}
Which can be used like so:
var MyPet = PetType.Dog;
Here is the Poco I want to hydrate/persist with the database:
public class Pet
{
public int ID { get; set; }
public string OwnerName { get; set; }
public DateTime DateOfBirth { get; set; }
public string PetName{ get; set; }
public PetType PetType{ get; set; }
}
I have designed a custom mapper that will work with PetType:
class EnumClassMapper : PetaPoco.StandardMapper
{
public override Func<object, object> GetFromDbConverter(System.Reflection.PropertyInfo targetProperty, Type sourceType)
{
if (targetProperty.PropertyType == typeof(PetType))
{
return (x) => PetType.FromValue((int) x);
}
return base.GetFromDbConverter(targetProperty, sourceType);
}
public override Func<object, object> GetToDbConverter(System.Reflection.PropertyInfo sourceProperty)
{
if (sourceProperty.PropertyType == typeof(PetType))
{
return (x) => ((PetType)x).Value;
}
return base.GetToDbConverter(sourceProperty);
}
}
However suppose I create another Enumeration subclass for disposition.
public class Disposition: Headspring.Enumeration<Disposition>
{
public static readonly Friendly = new Disposition(1, "Friendly");
public static readonly Timid = new Disposition(2, "Timid");
public static readonly Aggressive = new Disposition(3, "Aggressive");
private Disposition(int value, string displayName) : base(value, displayName) { }
}
I don't want to have to update my mapper every time I create a new subclass of the Enumeration class. I prefer that the mapping code could recognize that the property type is a descendent of the Enumeration class, and map accordingly. I assume the answer is to make use of reflection, but I don't know how to proceed.
What about
public class EnumClassMapper<T> : PetaPoco.StandardMapper
where T : Headspring.Enumeration<T>
{
public override Func<object, object> GetFromDbConverter(System.Reflection.PropertyInfo targetProperty, Type sourceType)
{
return (x) => Enumeration<T, int>.FromValue((int) x);
}
public override Func<object, object> GetToDbConverter(System.Reflection.PropertyInfo sourceProperty)
{
return (x) => ((T)x).Value;
}
}
var builder = DatabaseConfiguration.Build()
.UsingConnectionStringName("sqlite")
.UsingDefaultMapper<ConventionMapper>(m =>
{
m.FromDbConverter = (targetProperty, sourceType) =>
{
if (targetProperty == null)
return null;
var t = targetProperty.PropertyType;
if (t.BaseType == null || ! t.BaseType.IsGenericType)
return null;
if (t.BaseType.GetGenericTypeDefinition() != typeof(Headspring.Enumeration<>))
return null;
return ((IMapper)Activator.CreateInstance(typeof(EnumClassMapper<>).MakeGenericType(t))).GetFromDbConverter(targetProperty, sourceType);
};
m.ToDbConverter = sourceProperty =>
{
if (sourceProperty == null)
return null;
var t = sourceProperty.PropertyType;
if (t.BaseType == null || !t.BaseType.IsGenericType)
return null;
if (t.BaseType.GetGenericTypeDefinition() != typeof(Headspring.Enumeration<>))
return null;
return ((IMapper)Activator.CreateInstance(typeof(EnumClassMapper<>).MakeGenericType(t))).GetToDbConverter(sourceProperty);
};
});
var db = builder.Create();
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));
}
}