Inserting a node/property in an Expression - c#

I have an Expression where I like to insert one more node. I found this SO question similar question which adds a Property at the end of an Expression.
I'm quite new to Expressions and I can't figure out to do it between nodes.
I did this simple console test application to show the problem I have:
public class Program
{
private static void Main()
{
var provider = new Provider<Foo>();
var foo = new Foo {Label = "myLabel", Owner = "Me"};
provider.AddData(foo);
provider.AddExpression(f => f.Label);
// This writes: f => f.Label
Console.WriteLine(provider.OriginalExpression.ToString());
// This should write: f => f.WrappedData.Label
// At the moment it writes: NULL
Console.WriteLine(provider.WrapperExpression == null ? "NULL" : provider.WrapperExpression.ToString());
Console.ReadKey();
}
}
public class Foo
{
public string Label { get; set; }
public string Owner { get; set; }
}
public class Wrapper<T> where T : class
{
public string Id { get; set; }
public T WrappedData { get; set; }
}
public class Provider<T> where T : class
{
public Wrapper<T> _internalWrapper = new Wrapper<T>();
// The expression for the Property when using T (i.e. Foo)
public Expression<Func<T, string>> OriginalExpression { get; private set; }
// The expression for the Property when using _internalWrapper
public Expression<Func<Wrapper<T>, string>> WrapperExpression { get; private set; }
public void AddData(T data)
{
_internalWrapper = new Wrapper<T> { WrappedData = data, Id = "myId" };
}
public void AddExpression(Expression<Func<T, string>> valueExpression)
{
OriginalExpression = valueExpression;
BuildWrapperExpression();
}
private void BuildWrapperExpression()
{
// HERE IS THE PROBLEM:
// Here I have to insert the node "WrappedData" in the OriginalExpression and save it as WrapperExpression
// So {f => f.Label} must result into {f => f.WrappedData.Label}
// It should work as well for deeper nodes. I.e. when OriginalExpression is something like {f => f.Bar.Prop2.Label}
}
}
I've already tried a view versions in the BuildWrapperExpression() method but none delivers f => f.WrappedData.Label.
I get something like
f => Invoke(value (ConsoleApplication1.Provide1+<>c__DisplayClass1[ConsoleApplication1.Foo]).lambda, f.WrappedData) or
x => Invoke(f => f.Label, Invoke(e => e.WrappedData, x))
For my further use of the expression is has to be x => x.WrappedData.Label
Thanks a lot guys

Can't you simply:
private void BuildWrapperExpression()
{
var lambda = OriginalExpression.Compile();
WrapperExpression = x => lambda(x.WrappedData);
}
Alternatively, you can implement a ExpressionVistor. Check Marc's answer: https://stackoverflow.com/a/9132775/1386995

I came up with a great solution using the link Nikita provided.
I'm using this new class ExpressionChainer:
public static class ExpressionChainer
{
public static Expression<Func<TOuter, TInner>> Chain<TOuter, TMiddle, TInner>(
this Expression<Func<TOuter, TMiddle>> left, Expression<Func<TMiddle, TInner>> right)
{
return ChainTwo(left, right);
}
public static Expression<Func<TOuter, TInner>> ChainTwo<TOuter, TMiddle, TInner>(
Expression<Func<TOuter, TMiddle>> left, Expression<Func<TMiddle, TInner>> right)
{
var swap = new SwapVisitor(right.Parameters[0], left.Body);
return Expression.Lambda<Func<TOuter, TInner>>(
swap.Visit(right.Body), left.Parameters);
}
private class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
}
Then all I have to do in is:
private void BuildWrapperExpression()
{
Expression<Func<Wrapper<T>, T>> expression = x => x.WrappedData;
WrapperExpression = expression.Chain(OriginalExpression);
}
Which gives me x => x.WrappedData.Label as WrapperExpression

Related

Accessing nested properties and collections using Expression

I have the following classes:
public class Person
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("age")]
public int Age { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
[JsonProperty("profession")]
public Profession Profession { get; set; }
[JsonProperty("hobbies")]
public List<string> Hobbies { get; set; }
}
public class Profession
{
[JsonProperty("name")]
public string ProfessionName { get; set; }
[JsonProperty("industry")]
public string Industry { get; set; }
[JsonProperty("salary")]
public string AverageSalary { get; set; }
[JsonProperty("activities")]
public List<WorkActivity> WorkActivities { get; set; }
}
public class WorkActivity
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("rooms")]
public List<string> Rooms { get; set; }
}
public class PropertiesVisitor : ExpressionVisitor
{
private readonly Expression param;
public List<string> Names { get; } = new List<string>();
public PropertiesVisitor(Expression parameter)
{
param = parameter;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == param)
{
Names.Add(node.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName);
}
return base.VisitMember(node);
}
}
I call the methods in my app like this:
private static List<string> FindPropertyNames<T>(Expression<Func<T, object>> e)
{
var visitor = new PropertiesVisitor(e.Parameters[0]);
visitor.Visit(e);
return visitor.Names;
}
public static void Main(string[] args)
{
var names = FindPropertyNames<Person>(x => new { x.Age, x.Country, x.Profession.AverageSalary, x.Hobbies });
Console.WriteLine(string.Join(" ", names));
}
It works fine, except for one minor detailed - it doesn't check for nested properties. The output is the following: age country profession hobbies. I want it to be age country profession.salary hobbies.
I've been trying to fix the issue using a different approach, but I am unable to do so fully. I've tried the following:
public static MemberExpression GetMemberExpression(Expression e)
{
if (e is MemberExpression)
{
return (MemberExpression)e;
}
else if (e is LambdaExpression)
{
var le = e as LambdaExpression;
if (le.Body is MemberExpression)
{
return (MemberExpression)le.Body;
}
else if (le.Body is UnaryExpression)
{
return (MemberExpression)((UnaryExpression)le.Body).Operand;
}
}
return null;
}
public static string GetPropertyPath<T>(Expression<Func<T, object>> expr)
{
var path = new StringBuilder();
MemberExpression me = GetMemberExpression(expr);
do
{
if (path.Length > 0)
{
path.Insert(0, ".");
}
path.Insert(0, me.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName);
me = GetMemberExpression(me.Expression);
}
while (me != null);
return path.ToString();
}
It kind of does the job - but it can't take more than one property.
I call it like this:
var x = GetPropertyPath<Person>(p => p.Profession.AverageSalary);
Console.WriteLine(x);
I want to be able to send multiple properties, as in the first version. Also, I am unsure how to pass the following person.Profession.WorkActivities.Rooms as a parameter, because it is a list. I want to get profession.activities.rooms as output for it.
I would write a method that takes an Expression and returns all the members in the chain:
public static IEnumerable<MemberExpression> MemberClauses(this Expression expr) {
if (expr is not MemberExpression mexpr) {
yield break;
}
foreach (var item in MemberClauses(mexpr.Expression)) {
yield return item;
}
yield return mexpr;
}
Then, you can unwrap an entire method chain and LINQ to get the JSON property names:
public class PropertiesVisitor : ExpressionVisitor {
private readonly Expression param;
public List<string> Names { get; } = new List<string>();
public PropertiesVisitor(Expression parameter) => param = parameter;
[return: NotNullIfNotNull("node")]
public override Expression Visit(Expression node) {
var chain = node.MemberClauses().ToList();
if (chain.Any() && chain.First().Expression == param) {
var name = string.Join(".", chain.Select(
mexpr => mexpr.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName
));
Names.Add(name);
return node;
} else {
return base.Visit(node);
}
}
}
and you can then get the property path as follows:
public static class Functions {
public static List<string> GetPropertyPath<T>(Expression<Func<Person, T>> expr) {
var visitor = new PropertiesVisitor(expr.Parameters[0]);
visitor.Visit(expr);
return visitor.Names;
}
}
var names = Functions.GetPropertyPath(p => new { p.Age, p.Country, p.Profession.AverageSalary, p.Hobbies });
foreach (var name in names) {
Console.WriteLine(name);
}

Generic Class for return enumerated results based on Electrical Engineering formula input and condition criteria c#

I work on a program that needs to do engineering formula checking/validation.
Very similar to Unit Testing but I need something very generic so I don't have to make a ton of classes.
I need to pass an input formula:
Example: x = input voltage, y = voltage reference to find decibel.
Equation is 20*log10(x/y)
And validate the result for the use-case application
Examples:
If Application can't exceed 60 decibels and value is 72 then result is fail.
I have so many of these to do that it would be nice to have one generic class, pass in the input formula in as a Func<>, then have a Dictionary<[criteria],Enum.ResultType]> (or something) that will return the results for that application.
Here's what I was thinking/needing:
Non-working code, just to illustrate concept
public enum CiteriaCheckResult
{
Inconclusive,
Fail,
Warning,
Pass
}
//T is the return type of the specific engineering formula
public class CriteriaCheck<T>
{
public Dictionary<?, CiteriaCheckResult> Criteria { get; set; } = new Dictionary<?, CiteriaCheckResult>();
//T1 and T2 are the inputs for a specific engineering formula
//T is the output value to test against the criteria conditions
public CiteriaCheckResult Validate<T1, T2>(Func<T1, T2, T> input)
{
//nested if's?
//switch case?
//Criteria.FirstOrDefault(k => k.Key == input result == criteria)?
return CiteriaCheckResult base on dictionary or other user configurable criteria type;
}
}
Desired Usage:
class Program
{
static void Main(string[] args)
{
var test1 = new CriteriaCheck<double>();
//add criteria for specific application
Criteria.Add("criteria to check against input result", CiteriaCheckResult.Inconclusive);
Criteria.Add("value is < [] && value is > []", CiteriaCheckResult.Fail);
Criteria.Add("value is <= Room.Value", CiteriaCheckResult.Pass);
Criteria.Add("if value is > -6", CiteriaCheckResult.Warning);
double x = 1.23;
double y = .7549;
//run func and return enum based on user set conditions
Console.WriteLine($"Test 1: {test1.Validate<double, double>((x,y) => 20 * Math.Log10(x/y))}");
Console.ReadKey();
}
}
There are obviously a lot of problems with what I'm showing (like not having unique values for dictionary keys) but hopefully the intent of what I'm trying to accomplish is clear and ya'll can think of a clever way to do this.
Here is my suggestion:
class Program
{
static void Main(string[] args)
{
var checker = new CriteriaCheck<double>
{
FailValidator = (input => input < 0)
};
var result = checker.Validate((x, y) => x * y, 1.2, 0.7);
}
}
public enum CiteriaCheckResult
{
Inconclusive,
Fail,
Warning,
Pass
}
public class CriteriaCheck<T>
{
public Func<T, bool> InconclusiveValidator { get; set; } = p => false;
public Func<T, bool> FailValidator { get; set; } = p => false;
public Func<T, bool> WarningValidator { get; set; } = p => false;
public CiteriaCheckResult Validate<T1, T2>(Func<T1, T2, T> input, T1 t1, T2 t2)
{
if (InconclusiveValidator(input(t1, t2)))
return CiteriaCheckResult.Inconclusive;
else if (FailValidator(input(t1, t2)))
return CiteriaCheckResult.Fail;
else if (WarningValidator(input(t1, t2)))
return CiteriaCheckResult.Warning;
else
return CiteriaCheckResult.Pass;
}
}
A possible solution. I tried to add comments to make it self-explanatory.
public enum CiteriaCheckResult
{
Inconclusive,
Fail,
Warning,
Pass
}
//T is the return type of the specific engineering formula
public class CriteriaCheck<T>
{
public Dictionary<Type, object[]> Criteria = new Dictionary<Type, object[]>();
//T1 and T2 are the inputs for a specific engineering formula
//T is the output value to test against the criteria conditions
public CiteriaCheckResult Validate<T1, T2>(Func<T1, T2, T> input, T1 x, T2 y)
{
if(Criteria.Keys.Contains(input.GetType()))
{
// Calculate/Run the Func the value from incoming parameters
T result = input(x, y);
object[] m1 = Criteria[input.GetType()];
// Calculate/Run the Func existing in the Array
T m2 = (m1[0] as Func<T1, T2, T>)(x, y);
if(result.Equals(m2))
{
// If the values match then:
return (CiteriaCheckResult)m1[1];
}
}
return CiteriaCheckResult.Inconclusive;
}
}
class Program
{
static void Main(string[] args)
{
Func<decimal, decimal, int> n1 = (x,y) => 1;
Func<int, int> n2 = (x) => 5;
Func<decimal, decimal, decimal> n3 = (x, y) => 3;
CriteriaCheck<decimal> test = new CriteriaCheck<decimal>();
object[] obj0 = { n1, CiteriaCheckResult.Fail };
test.Criteria.Add(n1.GetType(), obj0);
object[] obj1 = { n2, CiteriaCheckResult.Inconclusive };
test.Criteria.Add(n2.GetType(), obj1);
object[] obj2 = { n3, CiteriaCheckResult.Pass };
test.Criteria.Add(n3.GetType(), obj2);
var result = test.Validate(n3, 1, 2);
}
}
Thanks for the time and effort guys.
I found out this morning that the requirements have changed including validation messages why the criteria failed need to be displayed on the GUI.
So I'm just going to make a base class implementing INotifyDataErrorInfo and make a class for each validation type. will be a lot of more classes but a generic class won't handle all the new requirements.
Example Usage
public class EquipmentRackSizeCriteriaCheck : CriteriaCheckItemBase
{
public override void Initialize()
{
//set description
CheckProperty = "Check Equipment Enclosure Size";
//set validation rules
AddValidationRule<EquipmentRackSizeCriteriaCheck, double>(CiteriaCheckResult.Fail, "Equipment is too tall for the Enclosure", t => t.EquipmentRUHeight > t.EnclosureRUHeight);
AddValidationRule<EquipmentRackSizeCriteriaCheck, double>(CiteriaCheckResult.Warning, "Enclosure is taller than the Equipment", t => t.EquipmentRUHeight < t.EnclosureRUHeight);
AddValidationRule<EquipmentRackSizeCriteriaCheck, double>(CiteriaCheckResult.Pass, "Equipment height fits in the Enclosure", t => t.EquipmentRUHeight == t.EnclosureRUHeight);
AddValidationRule<EquipmentRackSizeCriteriaCheck, double>(CiteriaCheckResult.Fail, "Equipment is too wide for the Enclosure", t => t.EquipmentRUWidth > t.EnclosureRUWidth);
AddValidationRule<EquipmentRackSizeCriteriaCheck, double>(CiteriaCheckResult.Warning, "Enclosure is wider than the Equipment", t => t.EquipmentRUWidth < t.EnclosureRUWidth);
AddValidationRule<EquipmentRackSizeCriteriaCheck, double>(CiteriaCheckResult.Pass, "Equipment width fits in the Enclosure", t => t.EquipmentRUWidth == t.EnclosureRUWidth);
//initialize complete
base.Initialize();
}
public int EquipmentRUHeight { get; set; }
public int EquipmentRUWidth { get; set; }
public int EnclosureRUHeight { get; set; }
public int EnclosureRUWidth { get; set; }
}
Base Class
public class CriteriaCheckItemBase : INotifyDataErrorInfo, ISupportInitializeNotification
{
private Dictionary<CiteriaCheckResultType, Dictionary<string, LambdaExpression>> _validationDictionary = new Dictionary<CiteriaCheckResultType, Dictionary<string, LambdaExpression>>();
public CriteriaCheckItemBase()
{
//intialize object
BeginInit();
}
public virtual void Initialize()
{
//set description
//CheckProperty = "";
//set validation rules
//initialize complete
EndInit();
}
public string CheckProperty { get; set; }
public CiteriaCheckResultType CheckResult { get; set; }
public string CheckMessage { get; set; }
public ObservableCollection<CriteriaCheckResult> ResultsCollection { get; set; } = new ObservableCollection<CriteriaCheckResult>();
public void AddValidationRule<T1, T2>(CiteriaCheckResultType checkResult, string message, Expression<Func<T1, bool>> rule) where T1 : CriteriaCheckItemBase
{
if (!_validationDictionary.ContainsKey(checkResult))
{
_validationDictionary.TryAdd(checkResult, new Dictionary<string, LambdaExpression>());
}
if (_validationDictionary.TryGetValue(checkResult, out Dictionary<string, LambdaExpression> rules))
{
rules.TryAdd(message ?? "No Message for this Result", rule);
}
}
#region INotifyDataErrorInfo
public bool HasErrors => (GetErrors("") is Dictionary<CiteriaCheckResultType, List<string>> c) ? c.Count > 0 : false;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
var results = _validationDictionary.OrderBy(o => (int)o.Key).ToDictionary(k => k.Key, v => v.Value.Where(c => (bool)c.Value.Compile().DynamicInvoke(this)).Select(s => s.Key).ToList());
ResultsCollection.Clear();
foreach (CiteriaCheckResultType resultType in results.Keys)
{
foreach (string msg in results[resultType])
{
ResultsCollection.Add(new CriteriaCheckResult() { CheckResult = resultType, CheckMessage = msg });
}
}
return ResultsCollection;
}
#endregion
#region ISupportInitializeNotification
public event EventHandler Initialized;
private bool _isInitialized;
public bool IsInitialized
{
get { return _isInitialized; }
private set
{
_isInitialized = value;
if (_isInitialized) { RaiseInitialized(); }
}
}
public void BeginInit()
{
if (IsInitialized) { IsInitialized = false; }
else { Initialize(); }
}
public void EndInit()
{
IsInitialized = true;
}
#endregion
protected void RaiseInitialized()
{
this.Initialized?.Invoke(this, new EventArgs());
}
public void RaisErrorChanged([CallerMemberName] string propertyName = null)
{
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
}

C# : Combine two generic expression trees

I have the two expression trees : one to get a navigation property of a class and another to filter on the values from this navigation property :
I am trying to combine them .
class Program
{
public interface IDeletable
{
bool IsDeleted { get; set; }
}
public class User
{
public int Id { get; set; }
public IEnumerable<BlogPost> BlogPosts;
}
public class BlogPost : IDeletable
{
public string Text { get; set; }
public bool IsDeleted { get; set; }
}
static void Main(string[] args)
{
var user = new User()
{
Id = 1,
BlogPosts = new List<BlogPost> {
new BlogPost {IsDeleted=false,Text="hello" },
new BlogPost {IsDeleted=true,Text="this is deleted" }
}
};
Expression<Func<User, IEnumerable<BlogPost>>> notDeletedExpression = Combine<User, BlogPost>(x => x.BlogPosts, x => !x.IsDeleted);
Console.ReadLine();
}
public static Expression<Func<T, IEnumerable<TChild>>> Combine<T, TChild>
(
Expression<Func<T, IEnumerable<TChild>>> navigationProperty,
Expression<Func<TChild, bool>> filter
)
where T : class
where TChild : class, IDeletable
{
//TODO
// shourld return x=>x.Posts.Where(p=>IsDeleted==false) ;
return null;
}
}
In the sample below the two Expressions from your sample are combined using Enumerable.Where method:
public static Expression<Func<T, IEnumerable<TChild>>> Combine<T, TChild>
(
Expression<Func<T, IEnumerable<TChild>>> navigationProperty,
Expression<Func<TChild, bool>> filter
)
where T : class
where TChild : class, IDeletable
{
// Consider caching the MethodInfo object:
var whereMethodInfo = GetEnumerableWhereMethodInfo<TChild>();
// Produce an Expression tree like:
// Enumerable.Where(<navigationProperty>, <filter>)
var filterExpr = Expression
.Call(
whereMethodInfo,
navigationProperty.Body,
filter
);
// Create a Lambda Expression with the parameters
// used for `navigationProperty` expression
return Expression
.Lambda<Func<T, IEnumerable<TChild>>>(
filterExpr,
navigationProperty.Parameters
);
}
private static MethodInfo GetEnumerableWhereMethodInfo<TSource>()
{
// Get a MethodInfo definition for `Enumerable.Where<>`:
var methodInfoDefinition = typeof(Enumerable)
.GetMethods()
.Where(x => x.Name == nameof(Enumerable.Where))
.First(x =>
{
var parameters = x.GetParameters();
return
parameters.Length == 2 &&
parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>) &&
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>);
});
// Get a MethodInfo object for `Enumerable.Where<TSource>`:
var methodInfo = methodInfoDefinition.MakeGenericMethod(typeof(TSource));
return methodInfo;
}

Rewriting Linq Select to a new subpath

I am currently trying to dynamically build linq queries. I want to be able to reuse Linq expressions in other Linq expressions.
For example:
public class Dummy
{
public string Test { get; set; }
public Sub Sub { get; set; }
}
public class Sub
{
public static Expression<Func<Sub, string>> Converter
{
get
{
return x => x.Id + ": " + x.Text;
}
}
public int Id { get; set; }
public string Text { get; set; }
}
When writing the converter for the Dummy class, the converter should be able to reuse the Sub.Converter. For thi spurpose I have written a DynamicSelect<> extension method:
var result = Query
.Where(x=>x.Sub != null)
.DynamicSelect(x => new Result())
.Select(x => x.SubText, x => x.Sub,Sub.Converter)
.Apply()
.ToList();
DynamicSelect creates a new SelectionBuilder,
the select part takes as first input the targetproperty (Result.SubText),
as second property the input property which we want to convert and as third input the converter for the Sub Property.
The .Apply call then returns the builtup expression tree.
I managed to get it working for a simpler usecase (without the subpath):
var result = Query.DynamicSelect(x => new Result())
.Select(x => x.ResolvedTest, x => x.Inner == null ? x.Test : x.Inner.Test)
.Select(x => x.SubText, x => x.Sub == null ? null : x.Sub.Text)
.Apply()
.ToList();
But how do I rebase an expression to another subpath?
Code so far:
public static class SelectBuilder
{
public static LinqDynamicConverter<TInput,TOutput> DynamicSelect<TInput,TOutput>(
this IQueryable<TInput> data,
Expression<Func<TInput, TOutput>> initialConverter) where TOutput : new()
{
return new LinqDynamicConverter<TInput,TOutput>(data, initialConverter);
}
}
public class LinqDynamicConverter<TInput,TOutput> where TOutput: new()
{
#region inner classes
private class MemberAssignmentVisitor : ExpressionVisitor
{
private IDictionary<MemberInfo, MemberAssignment> SinglePropertyToBinding { get; set; }
public MemberAssignmentVisitor(IDictionary<MemberInfo, MemberAssignment> singlePropertyToBinding)
{
SinglePropertyToBinding = singlePropertyToBinding;
}
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
SinglePropertyToBinding[node.Member] = node;
return base.VisitMemberAssignment(node);
}
}
private class MemberInfoVisitor : ExpressionVisitor
{
internal MemberInfo SingleProperty { get; private set; }
public MemberInfoVisitor()
{
}
protected override Expression VisitMember(MemberExpression node)
{
SingleProperty = node.Member;
return base.VisitMember(node);
}
}
#endregion
#region properties
private IQueryable<TInput> Data { get;set; }
private Expression<Func<TInput, TOutput>> InitialConverter { get;set;}
private IDictionary<MemberInfo, MemberAssignment> SinglePropertyToBinding { get; set; }
#endregion
#region constructor
internal LinqDynamicConverter(IQueryable<TInput> data,
Expression<Func<TInput, TOutput>> initialConverter)
{
Data = data;
InitialConverter = x => new TOutput(); // start with a clean plate
var replace = initialConverter.Replace(initialConverter.Parameters[0], InitialConverter.Parameters[0]);
SinglePropertyToBinding = new Dictionary<MemberInfo, MemberAssignment>();
MemberAssignmentVisitor v = new MemberAssignmentVisitor(SinglePropertyToBinding);
v.Visit(initialConverter);
}
#endregion
public LinqDynamicConverter<TInput,TOutput> Select<TProperty,TConverted>(
Expression<Func<TOutput, TConverted>> initializedOutputProperty,
Expression<Func<TInput, TProperty>> subPath,
Expression<Func<TProperty, TConverted>> subSelect)
{
//????
return this;
}
// this one works
public LinqDynamicConverter<TInput,TOutput> Select<TProperty>(
Expression<Func<TOutput, TProperty>> initializedOutputProperty,
Expression<Func<TInput, TProperty>> subSelect)
{
var miv = new MemberInfoVisitor();
miv.Visit(initializedOutputProperty);
var mi = miv.SingleProperty;
var param = InitialConverter.Parameters[0];
Expression<Func<TInput, TProperty>> replace = (Expression<Func<TInput, TProperty>>)subSelect.Replace(subSelect.Parameters[0], param);
var bind = Expression.Bind(mi, replace.Body);
SinglePropertyToBinding[mi] = bind;
return this;
}
public IQueryable<TOutput> Apply()
{
var converter = Expression.Lambda<Func<TInput, TOutput>>(
Expression.MemberInit((NewExpression)InitialConverter.Body, SinglePropertyToBinding.Values), InitialConverter.Parameters[0]);
return Data.Select(converter);
}
}
Found the solution. I needed to write a new Expression visitor that added the extra member acces(es):
/// <summary>
/// rebinds a full expression tree to a new single property
/// Example: we get x => x.Sub as subPath. Now the visitor starts visiting a new
/// expression x => x.Text. The result should be x => x.Sub.Text.
/// Traversing member accesses always starts with the rightmost side working toward the parameterexpression.
/// So when we reach the parameterexpression we have to inject the whole subpath including the parameter of the subpath
/// </summary>
/// <typeparam name="TConverted"></typeparam>
/// <typeparam name="TProperty"></typeparam>
private class LinqRebindVisitor<TConverted, TProperty> : ExpressionVisitor
{
public Expression<Func<TInput, TConverted>> ResultExpression { get; private set; }
private ParameterExpression SubPathParameter { get; set; }
private Expression SubPathBody { get; set; }
private bool InitialMode { get; set; }
public LinqRebindVisitor(Expression<Func<TInput, TProperty>> subPath)
{
SubPathParameter = subPath.Parameters[0];
SubPathBody = subPath.Body;
InitialMode = true;
}
protected override Expression VisitMember(MemberExpression node)
{
// Note that we cannot overwrite visitparameter because that method must return a parameterexpression
// So whenever we detect that our next expression will be a parameterexpression, we inject the subtree
if (node.Expression is ParameterExpression && node.Expression != SubPathParameter)
{
var expr = Visit(SubPathBody);
return Expression.MakeMemberAccess(expr, node.Member);
}
return base.VisitMember(node);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
bool initialMode = InitialMode;
InitialMode = false;
Expression<T> expr = (Expression<T>)base.VisitLambda<T>(node);
if (initialMode)
{
ResultExpression = Expression.Lambda<Func<TInput, TConverted>>(expr.Body,SubPathParameter);
}
return expr;
}
}
The single property Select method is then rather trivial:
public LinqDynamicConverter<TInput,TOutput> Select<TProperty,TConverted>(
Expression<Func<TOutput, TConverted>> initializedOutputProperty,
Expression<Func<TInput, TProperty>> subPath,
Expression<Func<TProperty, TConverted>> subSelect)
{
LinqRebindVisitor<TConverted, TProperty> rebindVisitor = new LinqRebindVisitor<TConverted, TProperty>(subPath);
rebindVisitor.Visit(subSelect);
var result = rebindVisitor.ResultExpression;
return Property<TConverted>(initializedOutputProperty, result);
}

Flatten a nested object to map its properties to the target object

I'm trying to use AutoMapper to map classes like this:
class FooDTO
{
public int X { get; set; }
public EmbeddedDTO Embedded { get; set; }
public class EmbeddedDTO
{
public BarDTO Y { get; set; }
public BazDTO Z { get; set; }
}
}
To classes like this:
class Foo
{
public int X { get; set; }
public Bar Y { get; set; }
public Baz Z { get; set; }
}
(FooDTO is a HAL resource)
I know I can do it by creating the map explicitly like this:
Mapper.CreateMap<FooDTO, Foo>()
.ForMember(f => f.Y, c => c.MapFrom(f => f.Embedded.Y))
.ForMember(f => f.Z, c => c.MapFrom(f => f.Embedded.Z));
Or even with a trick like this:
Mapper.CreateMap<FooDTO, Foo>()
.AfterMap((source, dest) => Mapper.Map(source.Embedded, dest));
But the problem is that I will have many similar HAL resources to map, and I'd rather not have to configure each one separately. I actually have a generic object model that looks like this:
class HalResource
{
[JsonProperty("_links")]
public IDictionary<string, HalLink> Links { get; set; }
}
class HalResource<TEmbedded> : HalResource
{
[JsonProperty("_embedded")]
public TEmbedded Embedded { get; set; }
}
class HalLink
{
[JsonProperty("href")]
public string Href { get; set; }
}
With this model, the FooDTO class is actually declared like this
class FooDTO : HalResource<FooDTO.EmbeddedDTO>
{
public int X { get; set; }
public class EmbeddedDTO
{
public int Y { get; set; }
public int Z { get; set; }
}
}
Is there a way to configure the mapping globally for all classes that inherit HalResource<TEmbedded>, so that the properties of the DTO's Embedded property are mapped directly to the target object? I tried to do it with a custom IObjectMapper, but it proved more challenging than I expected...
If your use case is as limited as presented in the question, that is:
A one-way mapping from HalResource derived instances to straight POCOS (vs bidirectional mapping)
Mapping of properties of the same name and type
The exact embedded structure you presented here
than it may make sense to setup a specific mapping yourself that takes into account this structure. This is something I tend to do if I have a very narrowly defined need for mapping with some clear mapping conventions (instead of relying on a generic mapper such as AutoMapper). For this purpose I have some building blocks that I tend to reuse in different contexts. I whipped together a mapper that applies to the problem you described from these building blocks, as shown below:
public class Mapper
{
private const BindingFlags DestConstructorFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private const BindingFlags DestFlags = BindingFlags.Instance | BindingFlags.Public;
private const BindingFlags SrcFlags = BindingFlags.Instance | BindingFlags.Public;
private static readonly object[] NoArgs = new object[0];
private static readonly Type GenericEmbeddedSourceType = typeof(HalResource<>);
private readonly Dictionary<Type, Func<object, object>> _oneWayMap = new Dictionary<Type, Func<object, object>>();
public void CreateMap<TDestination, TSource>()
where TDestination : class
where TSource : HalResource
{
CreateMap(typeof(TDestination), typeof(TSource));
}
public void CreateMap(Type destType, Type srcType)
{
_oneWayMap[srcType] = InternalCreateMapper(destType, srcType);
}
public object Map<TSource>(TSource toMap) where TSource : HalResource
{
var mapper = default(Func<object, object>);
if (!_oneWayMap.TryGetValue(typeof(TSource), out mapper))
throw new KeyNotFoundException(string.Format("No mapping for {0} is defined.", typeof(TSource)));
return mapper(toMap);
}
public TDestination Map<TDestination, TSource>(TSource toMap)
where TDestination : class
where TSource : HalResource
{
var converted = Map(toMap);
if (converted != null && !typeof(TDestination).IsAssignableFrom(converted.GetType()))
throw new InvalidOperationException(string.Format("No mapping from type {0} to type {1} has been configured.", typeof(TSource), typeof(TDestination)));
return (TDestination)converted;
}
public void Clear()
{
_oneWayMap.Clear();
}
private static Func<object, object> InternalCreateMapper(Type destType, Type srcType)
{
// Destination specific constructor + setter map.
var destConstructor = BuildConstructor(destType.GetConstructor(DestConstructorFlags, null, Type.EmptyTypes, null));
var destSetters = destType
.GetProperties(DestFlags)
.Where(p => p.CanWrite)
.ToDictionary(k => k.Name, v => Tuple.Create(v.PropertyType, BuildSetter(v)));
// Source specific getter maps
var srcPrimPropGetters = CreateGetters(srcType);
var srcEmbeddedGetter = default(Func<object, object>);
var srcEmbeddedPropGetters = default(IDictionary<string, Tuple<Type, Func<object, object>>>);
var baseType = srcType.BaseType;
while (baseType != null && baseType != typeof(object))
{
if (baseType.IsGenericType && GenericEmbeddedSourceType.IsAssignableFrom(baseType.GetGenericTypeDefinition()))
{
var genericParamType = baseType.GetGenericArguments()[0];
if (srcPrimPropGetters.Any(g => g.Value.Item1.Equals(genericParamType)))
{
var entry = srcPrimPropGetters.First(g => g.Value.Item1.Equals(genericParamType));
srcPrimPropGetters.Remove(entry.Key);
srcEmbeddedGetter = entry.Value.Item2;
srcEmbeddedPropGetters = CreateGetters(entry.Value.Item1);
break;
}
}
baseType = baseType.BaseType;
}
// Build mapper delegate function.
return (src) =>
{
var result = destConstructor(NoArgs);
var srcEmbedded = srcEmbeddedGetter != null ? srcEmbeddedGetter(src) : null;
foreach (var setter in destSetters)
{
var getter = default(Tuple<Type, Func<object, object>>);
if (srcPrimPropGetters.TryGetValue(setter.Key, out getter) && setter.Value.Item1.IsAssignableFrom(getter.Item1))
setter.Value.Item2(result, getter.Item2(src));
else if (srcEmbeddedPropGetters.TryGetValue(setter.Key, out getter) && setter.Value.Item1.IsAssignableFrom(getter.Item1))
setter.Value.Item2(result, getter.Item2(srcEmbedded));
}
return result;
};
}
private static IDictionary<string, Tuple<Type, Func<object, object>>> CreateGetters(Type srcType)
{
return srcType
.GetProperties(SrcFlags)
.Where(p => p.CanRead)
.ToDictionary(k => k.Name, v => Tuple.Create(v.PropertyType, BuildGetter(v)));
}
private static Func<object[], object> BuildConstructor(ConstructorInfo constructorInfo)
{
var param = Expression.Parameter(typeof(object[]), "args");
var argsExp = constructorInfo.GetParameters()
.Select((p, i) => Expression.Convert(Expression.ArrayIndex(param, Expression.Constant(i)), p.ParameterType))
.ToArray();
return Expression.Lambda<Func<object[], object>>(Expression.New(constructorInfo, argsExp), param).Compile();
}
private static Func<object, object> BuildGetter(PropertyInfo propertyInfo)
{
var instance = Expression.Parameter(typeof(object), "instance");
var instanceCast = propertyInfo.DeclaringType.IsValueType
? Expression.Convert(instance, propertyInfo.DeclaringType)
: Expression.TypeAs(instance, propertyInfo.DeclaringType);
var propertyCast = Expression.TypeAs(Expression.Property(instanceCast, propertyInfo), typeof(object));
return Expression.Lambda<Func<object, object>>(propertyCast, instance).Compile();
}
private static Action<object, object> BuildSetter(PropertyInfo propertyInfo)
{
var setMethodInfo = propertyInfo.GetSetMethod(true);
var instance = Expression.Parameter(typeof(object), "instance");
var value = Expression.Parameter(typeof(object), "value");
var instanceCast = propertyInfo.DeclaringType.IsValueType
? Expression.Convert(instance, propertyInfo.DeclaringType)
: Expression.TypeAs(instance, propertyInfo.DeclaringType);
var call = Expression.Call(instanceCast, setMethodInfo, Expression.Convert(value, propertyInfo.PropertyType));
return Expression.Lambda<Action<object, object>>(call, instance, value).Compile();
}
}
Some optimizations can be performed, but performance is likely sufficient for most problems. This can then be used like:
public abstract class HalResource
{
public IDictionary<string, HalLink> Links { get; set; }
}
public abstract class HalResource<TEmbedded> : HalResource
{
public TEmbedded Embedded { get; set; }
}
public class HalLink
{
public string Href { get; set; }
}
public class FooDTO : HalResource<FooDTO.EmbeddedDTO>
{
public int X { get; set; }
public class EmbeddedDTO
{
public int Y { get; set; }
public int Z { get; set; }
}
}
public class MyMappedFoo
{
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }
}
class Program
{
public static void Main(params string[] args)
{
// Configure mapper manually
var mapper = new Mapper();
mapper.CreateMap<MyMappedFoo, FooDTO>();
var myDTO = new FooDTO
{
X = 10,
Embedded = new FooDTO.EmbeddedDTO { Y = 5, Z = 9 }
};
var mappedFoo = mapper.Map<MyMappedFoo, FooDTO>(myDTO);
Console.WriteLine("X = {0}, Y = {1}, Z = {2}", mappedFoo.X, mappedFoo.Y, mappedFoo.Z);
Console.WriteLine("Done");
Console.ReadLine();
}
}
If your source and destination types can be discovered by convention, you can go a step further and have a builder that encodes these conventions populate the map as in the example below (again not the most optimal implementation, but there to illustrate the point):
public static class ByConventionMapBuilder
{
public static Func<IEnumerable<Type>> DestinationTypesProvider = DefaultDestTypesProvider;
public static Func<IEnumerable<Type>> SourceTypesProvider = DefaultSourceTypesProvider;
public static Func<Type, Type, bool> TypeMatcher = DefaultTypeMatcher;
public static Mapper Build()
{
var mapper = new Mapper();
var sourceTypes = SourceTypesProvider().ToList();
var destTypes = DestinationTypesProvider();
foreach (var destCandidateType in destTypes)
{
var match = sourceTypes.FirstOrDefault(t => TypeMatcher(t, destCandidateType));
if (match != null)
{
mapper.CreateMap(destCandidateType, match);
sourceTypes.Remove(match);
}
}
return mapper;
}
public static IEnumerable<Type> TypesFromAssembliesWhere(Func<IEnumerable<Assembly>> assembliesProvider, Predicate<Type> matches)
{
foreach (var a in assembliesProvider())
{
foreach (var t in a.GetTypes())
{
if (matches(t))
yield return t;
}
}
}
private static IEnumerable<Type> DefaultDestTypesProvider()
{
return TypesFromAssembliesWhere(
() => new[] { Assembly.GetExecutingAssembly() },
t => t.IsClass && !t.IsAbstract && !t.Name.EndsWith("DTO"));
}
private static IEnumerable<Type> DefaultSourceTypesProvider()
{
return TypesFromAssembliesWhere(
() => new[] { Assembly.GetExecutingAssembly() },
t => typeof(HalResource).IsAssignableFrom(t) && !t.IsAbstract && t.Name.EndsWith("DTO"));
}
private static bool DefaultTypeMatcher(Type srcType, Type destType)
{
var stn = srcType.Name;
return (stn.Length > 3 && stn.EndsWith("DTO") && destType.Name.EndsWith(stn.Substring(0, stn.Length - 3)));
}
}
class Program
{
public static void Main(params string[] args)
{
// Configure mapper by type scanning & convention matching
var mapper = ByConventionMapBuilder.Build();
var myDTO = new FooDTO
{
X = 10,
Embedded = new FooDTO.EmbeddedDTO { Y = 5, Z = 9 }
};
var mappedFoo = mapper.Map<MyMappedFoo, FooDTO>(myDTO);
Console.WriteLine("X = {0}, Y = {1}, Z = {2}", mappedFoo.X, mappedFoo.Y, mappedFoo.Z);
Console.WriteLine("Done");
Console.ReadLine();
}
}
If you have other reasons to want to hang on to AutoMapper, I suggest creating a similar map builder that encodes both the type matching and the embedded property mapping.

Categories

Resources