Search code examples
c#.net.net-coreentity-framework-coreexpression-trees

set nested properties of object while mapping


I am using an expression tree as an alternative to automapper to map source properties to target properties using below code

What i am doing is, I have created static method inside static class for mapping and assigning inner child object property to outer object property

public static class PropertyMapper<TSource, TDest>
{
    private static Expression<Func<TSource, Dictionary<string, MasterSection>, TDest>> _mappingExpression;
    private static Func<TSource, Dictionary<string, MasterSection>, TDest> _mapper;
    static PropertyMapper()
    {
        _mappingExpression = ProjectionMap();
        _mapper = _mappingExpression.Compile();
    }

    public static Func<TSource, Dictionary<string, MasterSection>, TDest> Mapper => _mapper;

    public static Expression<Func<TSource, Dictionary<string, MasterSection>, TDest>> ProjectionMap()
    {
        var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
        var targetProperties= typeof(TDest).GetProperties().Where(p => p.CanWrite);
        var propertyMap =
            from d in targetProperties
            join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
            where d.Name != "SourceOfDataId" && d.Name!= "SourceOfData"
            select new { Source = s, Dest = d };
        var itemParam = Expression.Parameter(typeof(TSource), "item");   
        var memberBindings = propertyMap.Select(p => (MemberBinding)Expression.Bind(p.Dest, Expression.Property(itemParam, p.Source))).ToList();
               
       var sourceOfDataIdProp = targetProperties.FirstOrDefault(s => s.Name == "SourceOfDataId");
       if (sourceOfDataIdProp != null)
       { 
            memberBindings.Add(Expression.Bind(sourceOfDataIdProp,Expression.Convert(Expression.Property(Expression.Property(itemParam, "SourceOfData"),"Id"),typeof(Guid?))));
       }       
       var sourceOfDataProp = targetProperties.FirstOrDefault(s => s.Name == "SourceOfData");
       if(sourceOfDataProp != null)
       {
          // here i would like to update `sourceOfData` object property "isApproved"
       }    
       var newExpression = Expression.New(typeof(TDest));
       var memberInitExpression = Expression.MemberInit(newExpression, memberBindings);
       var projection = Expression.Lambda<Func<TSource, Dictionary<string, MasterSection>, TDest>>(memberInitExpression, itemParam, dictParam);
       return projection;
    }
} 

and i am using the above method here in below to map the source properties to target properties

AirflowsLab = sourceMechanicalData.AirflowsLab.Select(a => PropertyMapper<LibraryLabAirflow, LibraryLabAirflow>.Mapper(a, masterSectionMappedLibrary)).ToList();

and the structure for LibraryLabAirflow is looks like as below

public class LibraryLabAirflow
{
    [ForeignKey("SourceOfData")]
    public Guid? SourceOfDataId { get; set; }
    public virtual CodeStandardGuideline SourceOfData { get; set; }
}

Above mapping is working fine, what i am trying is now that i need to access the sourceOfData child object of target and update the property for sourceOfData and map that updated child object to source child object sourceOfData.

below is the sourceOfData object details

"SourceOfData":{
                "Id": "c5bf3585-50b1-4894-8fad-0ac884343935",
                "IsApproved": null, // trying to set this to true instead of null inside target object
                "MasterSection": null
              },

I am not sure how to access the child object property using an expression tree in this scenario and i cannot use automapper library. Could any one please let me know how to access the child object property and update and assign back to target.

What i am trying to generate an expression looks like this source.SourceOfData = target.SourceOfData but before this i need to update one of the property of target.SourceOfData

Many thanks in advance

Desired expression :

   AirflowsLab = sourceMechanicalData.AirflowsLab.Where(a => a != null).Select(item => new LibraryLabAirflow()
   {
        SourceOfData = new CodeStandardGuideline()
        {
             IsApproved = true,// trying to set this through expression tree
             City = item.SourceOfData.City
             ......
             .......
        }
   }).ToList(),

trying like this is not working as well, 1

 var sourceOfDataProp = targetProperties.FirstOrDefault(s => s.Name == "SourceOfData");           
 if(sourceOfDataProp != null)
 {
      // here need to get the sourceofdata properties 
      var sourceOfDataProperty = Expression.Property(Expression.Constant(sourceOfDataProp), "IsApproved");                    
 }

Update:

i have implemented the logic inside if block of sourceOfDataProp != null but getting an error

if (sourceOfDataProp != null)
{
    var targetitemParam = Expression.Parameter(typeof(TTarget), "item");
    var sourceOfDataPropertiesFilter = new List<string>()
    {
       "IsApproved"
    };
    var sourceItem = Expression.Property(itemParam, typeof(TSource).GetProperty("SourceOfData"));
    var sourcePropertyInfo = sourceItem.Type.GetProperties().Where(p => p.CanRead);
    var targetItem = Expression.Property(targetitemParam, typeof(TTarget).GetProperty("SourceOfData"));
    var targetPropertyInfo = targetItem.Type.GetProperties().Where(p => p.CanWrite);
    var sourceOfDataPropertyMap = from tp in targetPropertyInfo
                                  join sp in sourcePropertyInfo
                      on new { tp.Name, tp.PropertyType } equals new { sp.Name, sp.PropertyType }
                                  where !sourceOfDataPropertiesFilter.Contains(tp.Name)
                                  select new { Source = sp, Target = tp };
    // getting error at below line type of arguments does not match
    var sourceOfDataMemberBindings = sourceOfDataPropertyMap.Select(p => Expression.Bind(p.Target, Expression.PropertyOrField(targetitemParam, "SourceOfData"))).ToList();                  
}

Solution

  • I have solved this problem like as below

    if (sourceOfDataProp != null)
    {
        var targetItemParam = Expression.Parameter(typeof(TTarget), "item");
        var sourceOfDataPropertiesFilter = new List<string>()
        {
           "IsApproved"
        };
        var sourceItem = Expression.Property(itemParam, typeof(TSource).GetProperty("SourceOfData"));
        var sourceOfDataSourceProperties = sourceItem.Type.GetProperties().Where(p => p.CanRead);
        var targetItem = Expression.Property(targetItemParam, typeof(TTarget).GetProperty("SourceOfData"));
        var sourceOfDataTargetProperties = targetItem.Type.GetProperties().Where(p => p.CanWrite);
    
        var sourceOfDataPropertyMap = sourceOfDataTargetProperties.Join(sourceOfDataSourceProperties,
                                                                        t => new { t.Name, t.PropertyType },
                                                                        s => new { s.Name, s.PropertyType },
                                                                        (t, s) => new { Source = s, Target = t })
                                                                  .Where(t => !sourceOfDataPropertiesFilter.Contains(t.Target.Name));
    
        var sourceOfDataMemberBindings = sourceOfDataPropertyMap.Select(p => Expression.Bind(p.Target, Expression.Property(sourceItem, p.Source))).ToList();
    
        var sourceOfDataIsApprovedProp = sourceOfDataTargetProperties.FirstOrDefault(s => s.Name == "IsApproved");
        if (sourceOfDataIsApprovedProp != null)
        {
            sourceOfDataMemberBindings.Add(Expression.Bind(sourceOfDataIsApprovedProp, Expression.Constant(true, typeof(bool?))));
        }          
    
        var sourceOfDataExpression = Expression.New(typeof(DesignHub.Entities.CodeStandardGuideline));
        var sourceOfDataMemberInitExpression = Expression.MemberInit(sourceOfDataExpression, sourceOfDataMemberBindings);
        memberBindings.Add(Expression.Bind(sourceOfDataProp, sourceOfDataMemberInitExpression));
    }