0
votes

i am trying to use projection map to map the property values from source to destination as mentioned here in this question Projection mapping looks like as below but getting error near select statement

Error is : The type arguments for the method 'Enumerable.Select<TSource, Tresult>(IEnumerable, Func<Tsource, int, Tresult>)' cannot be inferred from usage.Try specifying type arguments explicitly

and below is the code sample

    public static IQueryable<TDest> ProjectionMap<TSource, TDest>(IQueryable<TSource> sourceModel)
where TDest : new()
    {
        var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
        var destProperties = typeof(TDest).GetProperties().Where(p => p.CanWrite);
        var propertyMap = from d in destProperties
                          join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
                          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)));
        var newExpression = Expression.New(typeof(TDest));
        var memberInitExpression = Expression.MemberInit(newExpression, memberBindings);
        var projection = Expression.Lambda<Func<TSource, TDest>>(memberInitExpression, itemParam);
        return sourceModel.Select(projection);
    }

and then i am using above method below

    private static MechanicalData TransformMechanicalData(MechanicalData sourceMechanicalData, Dictionary<string, MasterSection> masterSectionMappedLibrary)
    {
        return new MechanicalData()
        {
            Acoustic = sourceMechanicalData.Acoustic
                       .Where(a => a != null)
                       .Select(ProjectionMap<LibraryAcoustic, LibraryAcoustic>(sourceMechanicalData.Acoustic.AsQueryable())).ToList() ?? new(),
        }
    }

Could any one please let me know where I am doing wrong, many thanks in advance.

Update:

Acoustic = sourceMechanicalData.Acoustic
                 .Where(a => a != null)
    .Select(acoustic => new LibraryAcoustic
    {
        Id = acoustic.Id,                
        IsApproved = true,                 
        NoiseCriteria = acoustic.NoiseCriteria,
        SourceOfData = acoustic.SourceOfData,
        SourceOfDataId = acoustic.SourceOfData.Id,
        MasterSection = masterSectionMappedLibrary["Library Acoustic"]
    }).ToList() ?? new(),

calling that transformMechanicalData method in below

   if (spaceTypeReader.HasRows)
    {
        while (spaceTypeReader.Read())
        {
            var id = spaceTypeReader.IsDBNull(0) ? default : Guid.Parse(spaceTypeReader.GetString(0));
            var mechanicalDataJson = spaceTypeReader.IsDBNull(1) ? "null" : spaceTypeReader.GetString(1);
            var srcMechanicalDataJson = JsonConvert.DeserializeObject<MechanicalData>(mechanicalDataJson);
            fixedSpaceTypesMechanicalData[id] = TransformMechanicalData(srcMechanicalDataJson, masterSectionMappedLibrary);
        }
    }

and mechanical data class be like

public class MechanicalData
{
    public List<LibraryAcoustic> Acoustic { get; set; }
    .........
}

Update 2:

model for libraryAcoustic

public class LibraryAcoustic 
{
    public double? NoiseCriteria { get; set; }
    [ForeignKey("SourceOfData")]
    public Guid? SourceOfDataId { get; set; }
    public virtual CodeStandardGuideline SourceOfData { get; set; }
    public Guid Id { get; set; }
    public MasterSection MasterSection { get; set; }
    public bool? IsApproved { get; set; }
}

FROM Model

 "Acoustic": [
  {
        "Id": "d9254132-d11d-48dd-9b74-c0b60d1e4b8a",
        "IsApproved": null,
        "SourceOfData": {
            "Id": "c5bf3585-50b1-4894-8fad-0ac884343935",
            "CodeStandardGuidelineType": "GUIDELINE_OR_STANDARD"
        },
        "MasterSection": null,
        "SourceOfDataId": null,
        "NoiseCriteria": 1,
    }
],

TO model:

 "Acoustic": [
  {
        "Id": "d9254132-d11d-48dd-9b74-c0b60d1e4b8a",
        "IsApproved": true,
        "SourceOfData": {
            "Id": "c5bf3585-50b1-4894-8fad-0ac88434393",
            "CodeStandardGuidelineType": "GUIDELINE_OR_STANDARD"
        },
        "MasterSection": {Name:"test"},
        "SourceOfDataId": "c5bf3585-50b1-4894-8fad-0ac884343935",
        "NoiseCriteria": 1,
    }
],

Test Class update:

        SourceOfData sourceOfData = new SourceOfData()
        {
            Id = new Guid("c5bf3585-50b1-4894-8fad-0ac884343935"),
            Name = "test"
        };
        TestClassA170 testClassA170 = new TestClassA170()
        {
            Category = "test",
            SourceOfData = sourceOfData,
            SourceOfDataId = null,
            IsApproved = true,
            MinOutdoorAirACH = 1,
            MinTotalAirACH = 2,
            DirectExhaust = DirectExhaust.NO,
            PressureRelationship = PressureRelationship.NEGATIVE,
            RecirculatedAir = RecirculatedAir.NO,
            SpaceFunction = "10"       
        };
        List<TestClassA170> list = new List<TestClassA170>();
        list.Add(testClassA170);

enter image description here

1
Well, you are trying to invent Automapper with it's ProjectTo. Also there is already function to do that: stackoverflow.com/a/66334073/10646316Svyatoslav Danyliv
for this do i need to pass fields as comma separated stringsEnigma State
at the same time for two fields i need to set the values explicitly and not to map the values from source with your solution mentioned in that link, could you please suggest how to achieve thisEnigma State
@SvyatoslavDanyliv, could you please let me know how to apply the same and with those explicitly assigning fields and i will update the question to reflect the original codeEnigma State
@SvyatoslavDanyliv, i am getting an error here sourceMechanicalData.Acoustic.Where(a => a != null).Select(BuildSelector<LibraryAcoustic, LibraryAcoustic>("id,SourceOfData.Id ")).ToList() ?? new(), 'IEnumerable <LibraryAcoustic> does not contain definition for select ....Enigma State

1 Answers

1
votes

You have to create mapping helper class which accespts additionally Dictionary as paraneter:

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 string MasterKeyFromClassName(string className)
    {
        // you have to do that yourself
        throw new NotImplementedException();
    }

    public static Expression<Func<TSource, Dictionary<string, MasterSection>, TDest>> ProjectionMap()
    {
        var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
        var destProperties = typeof(TDest).GetProperties().Where(p => p.CanWrite);
        var propertyMap = 
            from d in destProperties
            join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
            where d.Name != "MasterSection"
            select new { Source = s, Dest = d };
        var itemParam = Expression.Parameter(typeof(TSource), "item");
        var dictParam = Expression.Parameter(typeof(Dictionary<string, MasterSection>), "dict");
        var memberBindings = propertyMap.Select(p => (MemberBinding)Expression.Bind(p.Dest, Expression.Property(itemParam, p.Source))).ToList();

        var masterSectionProp = destProperties.FirstOrDefault(s => s.Name == "MasterSection");
        if (masterSectionProp != null)
        {
            Expression<Func<Dictionary<string, MasterSection>, string, MasterSection>> dictRetrievalTemplate = (dict, value) => dict[value];
            var masterPropertyBind = Expression.Bind(masterSectionProp, ExpressionReplacer.GetBody(dictRetrievalTemplate, dictParam, Expression.Constant(MasterKeyFromClassName(typeof(TSource).Name)));
            memberBindings.Add(masterPropertyBind);
        }

        var sourceOfDataProp = destProperties.FirstOrDefault(s => s.Name == "SourceOfDataId");
        if (sourceOfDataProp != null)
        {
            memberBindings.Add(Expression.Bind(sourceOfDataProp, Expression.Property(Expression.Property(itemParam, "SourceOfData"), "Id")));
        }

        var isApprovedProp = destProperties.FirstOrDefault(s => s.Name == "IsApproved");
        if (isApprovedProp != null)
        {
            memberBindings.Add(Expression.Bind(isApprovedProp, Expression.Constant(true)));
        }

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

    class ExpressionReplacer : ExpressionVisitor
    {
        readonly IDictionary<Expression, Expression> _replaceMap;

        public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
        {
            _replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
        }

        public override Expression Visit(Expression exp)
        {
            if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
                return replacement;
            return base.Visit(exp);
        }

        public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
        {
            return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
        }

        public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
        {
            return new ExpressionReplacer(replaceMap).Visit(expr);
        }

        public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
        {
            if (lambda.Parameters.Count != toReplace.Length)
                throw new InvalidOperationException();

            return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
                .ToDictionary(i => (Expression) lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
        }
    }
}

Then rewrite your function:

private static Expression<MechanicalData TransformMechanicalData(MechanicalData sourceMechanicalData, Dictionary<string, MasterSection> masterSectionMappedLibrary)
{
    return new MechanicalData()
    {
        Acoustic = sourceMechanicalData.Acoustic
                    .Where(a => a != null)
                    .Select(a => PropertyMapper<LibraryAcoustic, LibraryAcoustic>.Mapper(a, masterSectionMappedLibrary)).ToList(),
    }
}