Click here to Skip to main content
15,887,027 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I would like to propagate entity framework include properties across architecture.
1. Starting at the service layer where these include properties come as an argument
IEnumerable<UserVM> GetUsers(user => user.Classes);


2. The method getUsers must translate this expressions from ViewModel to Model
C#
IEnumerable<UserVM> GetUser(Expression<Func<UserVm, object>> includePropertyVM)
{
   Expression<Func<User, object>> includeProperty Mapper.Map<Expression<Func<User, object>>>(includePropertyVM);

  Repository.GetUsers(includeProperty);

}


But it does not work!
Autommaper can not handle it. Have you any experience with it? I want to translate this lamda expressions to each other.
Posted

1 solution

I don't know if it can be done with AutoMapper. But this is how you could do it yourself, not including the Repository-call. There's a generic and non-generic version of the mapping-method; I think you should be able to use the generic one.
C#
using System;
using System.Linq.Expressions;

namespace MemberMapping
{
    class User
    {
        public string Name { get; set; }
    }

    class UserVM
    {
        public string Name { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            User testUser = new User() { Name = "Paul" };

            Expression<Func<User, object>> genericMapping = MapMemberLambda<UserVM, User>(user => user.Name);
            Expression<Func<User, object>> nonGenericMapping = MapUserMemberLambda(user => user.Name);

            string testGenericMapping = (string)genericMapping.Compile().Invoke(testUser);
            string testNonGenericMapping = (string)nonGenericMapping.Compile().Invoke(testUser);

            Console.WriteLine(testGenericMapping); // writes "Paul"
            Console.WriteLine(testNonGenericMapping); // writes "Paul"
        }

        static Expression<Func<TTarget, object>> MapMemberLambda<TSource, TTarget>(Expression<Func<TSource, object>> includePropertyVM)
        {
            ParameterExpression objectParam = Expression.Parameter(typeof(TTarget), "x");

            Expression memberAccess = Expression.PropertyOrField(objectParam, ((MemberExpression)includePropertyVM.Body).Member.Name);

            return Expression.Lambda<Func<TTarget, object>>(memberAccess, objectParam);
        }

        static Expression<Func<User, object>> MapUserMemberLambda(Expression<Func<UserVM, object>> lambda)
        {
            ParameterExpression objectParam = Expression.Parameter(typeof(User), "x");

            Expression memberAccess = Expression.PropertyOrField(objectParam, ((MemberExpression)lambda.Body).Member.Name);

            return Expression.Lambda<Func<User, object>>(memberAccess, objectParam);
        }
    }
}


Edit: The mapping methods expect an exact match of the member names. If you counted on AutoMappers ability to "fuzzy-match" member names like "User" to "UserName" you'll have to do this on your own here. You would have to take the source-member-name from ((MemberExpression)lambda.Body).Member.Name, find the best matching member name of the members of the target type (see typeof(TTarget).GetMembers()) and put that member name where ((MemberExpression)lambda.Body).Member.Name is now.

Edit 2: As requested per comment: Allowing the mapping of nested member access like user => user.Foo.Bar.Baz:
C#
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace MemberMapping
{
    class Foo
    {
        public string Bar { get; set; }
    }

    class Address
    {
        public string Street { get; set; }
        public Foo Foo { get; set; }
    }

    class User
    {
        public string Name { get; set; }
        public Address Address { get; set; }
    }

    class FooVM
    {
        public string Bar { get; set; }
    }

    class AddressVM
    {
        public string Street { get; set; }
        public FooVM Foo { get; set; }
    }

    class UserVM
    {
        public string Name { get; set; }
        public AddressVM Address { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            User testUser = new User()
            {
                Name = "Paul",
                Address = new Address()
                {
                    Street = "Freeway",
                    Foo = new Foo() { Bar = "Baz" }
                }
            };

            Expression<Func<User, object>> nameMapping = MapMemberLambda<UserVM, User>(user => user.Name);
            Expression<Func<User, object>> streetMapping = MapMemberLambda<UserVM, User>(user => user.Address.Street);
            Expression<Func<User, object>> barMapping = MapMemberLambda<UserVM, User>(user => user.Address.Foo.Bar);

            string nameMappingResult = (string)nameMapping.Compile().Invoke(testUser);
            string streetMappingResult = (string)streetMapping.Compile().Invoke(testUser);
            string barMappingResult = (string)barMapping.Compile().Invoke(testUser);

            Console.WriteLine(nameMappingResult);   // writes "Paul"
            Console.WriteLine(streetMappingResult); // writes "Freeway"
            Console.WriteLine(barMappingResult);    // writes "Baz"
            Console.ReadLine();
        }

        static Expression<Func<TTarget, object>> MapMemberLambda<TSource, TTarget>(Expression<Func<TSource, object>> sourceLambdaExpr)
        {
            // We need to build the mapped expression in the reverse order we
            // can traverse the source expression. So we use a stack to push
            // the names of the members that are being accessed onto it and
            // then pop them off the stack again while building the mapped
            // expression.

            Stack<string> memberNames = new Stack<string>();

            MemberExpression currentMemberExpr = (MemberExpression)sourceLambdaExpr.Body;
            while (true)
            {
                memberNames.Push(currentMemberExpr.Member.Name);
                if (currentMemberExpr.Expression is ParameterExpression)
                    break;
                currentMemberExpr = (MemberExpression)currentMemberExpr.Expression;
            }

            ParameterExpression objectParamExpr = Expression.Parameter(typeof(TTarget), "x");
            Expression currentMappedExpr = objectParamExpr;
            while (memberNames.Count > 0)
            {
                currentMappedExpr = Expression.PropertyOrField(currentMappedExpr, memberNames.Pop());
            }

            return Expression.Lambda<Func<TTarget, object>>(currentMappedExpr, objectParamExpr);
        }
    }
}
 
Share this answer
 
v7
Comments
BillWoodruff 18-Jan-16 5:17am    
+5 wow !
Sascha Lefèvre 18-Jan-16 5:36am    
Thank you, Bill :)
vacho2 18-Jan-16 15:58pm    
Great! It works!
But what about something like this:
Expression<func<user, object="">> genericMapping = MapMemberLambda<uservm, user="">(user => user.XXXX.YYYY);

Assume that both classes have the same properties
Sascha Lefèvre 18-Jan-16 17:05pm    
I updated my post above with a solution for this (starting at "Edit 2"). Please feel free to formally accept the solution if I helped you :-)
vacho2 18-Jan-16 17:17pm    
Yes, you definitely did!!! Thanks a lot.

Can you do it more like entity framework. I have searched how the entity framework does it. Would you be able and kind to rewrite this method using your solution?
Hier is the source code:
public static bool TryParsePath(Expression expression, out string path)
{
path = null;
var withoutConvert = expression.RemoveConvert(); // Removes boxing
var memberExpression = withoutConvert as MemberExpression;
var callExpression = withoutConvert as MethodCallExpression;

if (memberExpression != null)
{
var thisPart = memberExpression.Member.Name;
string parentPart;
if (!TryParsePath(memberExpression.Expression, out parentPart))
{
return false;
}
path = parentPart == null ? thisPart : (parentPart + "." + thisPart);
}
else if (callExpression != null)
{
if (callExpression.Method.Name == "Select"
&& callExpression.Arguments.Count == 2)
{
string parentPart;
if (!TryParsePath(callExpression.Arguments[0], out parentPart))
{
return false;
}
if (parentPart != null)
{
var subExpression = callExpression.Arguments[1] as LambdaExpression;
if (subExpression != null)
{
string thisPart;
if (!TryParsePath(subExpression.Body, out thisPart))
{
return false;
}
if (thisPart != null)
{
path = parentPart + "." + thisPart;
return true;
}
}
}
}
return false;
}

return true;
}

public static class Exas
{
public static Expression RemoveConvert(this Expression expression)
{
while (expression.NodeType == ExpressionType.Convert
|| expression.NodeType == ExpressionType.ConvertChecked)
{
expression = ((UnaryExpression)expression).Operand;
}

return expression;
}
}

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900