0

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

Glory Raj
  • 17,397
  • 27
  • 100
  • 203

1 Answers1

1

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(),
    }
}
Svyatoslav Danyliv
  • 21,911
  • 3
  • 16
  • 32
  • i am trying to assign the two properties explicitly and how can i do that here for example `IsApproved and mastersection`. Isapproved i want to set always to true and mastersection is based on key passed to the dictionary and looking for nested properties as well. Here `SourceOfData` is nested inside `libraryAcoustic` – Glory Raj Apr 10 '21 at 20:55
  • is there any way we can achieve with these conditions – Glory Raj Apr 10 '21 at 20:59
  • Looks like I'm lost. Which properties? Post model FROM and model TO. – Svyatoslav Danyliv Apr 10 '21 at 20:59
  • Yes, you can do that with conditions. Add parameters to `ProjectionMap` and use them when building ExpressionTree. For sure in this case you have to call `Compile` every time, so you have to invent new way how to cache such mappers. – Svyatoslav Danyliv Apr 10 '21 at 21:01
  • if you see in question where i am doing with actual select mapping kind of here `Acoustic = sourceMechanicalData.Acoustic .Where(a => a != null) .Select(acoustic => new LibraryAcoustic { Id = acoustic.Id, IsApproved = true, explicit setting NoiseCriteria = acoustic.NoiseCriteria, SourceOfData = acoustic.SourceOfData, SourceOfDataId = acoustic.SourceOfData.Id, // nested MasterSection = masterSectionMappedLibrary["Library Acoustic"] // updating explicitly }).ToList() ?? new(),` – Glory Raj Apr 10 '21 at 21:03
  • No problem to call compile every time but how to pass these conditions, I believe this will work for nested properties as well – Glory Raj Apr 10 '21 at 21:04
  • I do not see any conditions here. – Svyatoslav Danyliv Apr 10 '21 at 21:06
  • the condition in the sense getting mastersection object from this dictionary key `MasterSection = masterSectionMappedLibrary["Library Acoustic"]` and i am using same projectmap for other list objects as well in mechanical data class. We have other list objects same like as Acoustic. In that case the key for `masterSectionMappedLibrary` need to be passed based on source name – Glory Raj Apr 10 '21 at 21:08
  • I will just update the FROM Model and ToModel in the question – Glory Raj Apr 10 '21 at 21:17
  • Do not write, Update1, Update2, just update question with new details. `MasterSection` property has the same name in each object? – Svyatoslav Danyliv Apr 10 '21 at 21:20
  • not the same name its depends on the object name for example for `Library Acoustic` key is `Library Acoustic` and for Enviroment list object the key is `Library Environment` like that it is. I updated from model and to model and `To model` what i am looking for – Glory Raj Apr 10 '21 at 21:23
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/230983/discussion-between-enigma-state-and-svyatoslav-danyliv). – Glory Raj Apr 10 '21 at 21:25
  • the key is always class name including space like for example for this `LIbraryAcoustic` key is `Library Acoustic` and for LibraryEnvironment` it is 'Library Environment` – Glory Raj Apr 10 '21 at 21:28
  • some how it is setting wrong values for sourceOfDataId and isApproved properties – Glory Raj Apr 11 '21 at 22:45
  • Still it is giving the same values any wrong in expression binding – Glory Raj Apr 12 '21 at 14:10
  • Sorry I cannot mark as answer still I am getting wrong values set but thanks for the help – Glory Raj Apr 12 '21 at 14:11
  • Sorry man, you have debugger, you see everything. Maybe there are properties which names are equals without case. This answer is doing exactly what you have requested - generates expression tree for mapping. For sure it needs tuning. – Svyatoslav Danyliv Apr 12 '21 at 14:56
  • hmm, But properties are same with cases as well but i will look into one more time and why isApproved is setting false because of nullable properties – Glory Raj Apr 12 '21 at 14:57
  • is there any way to ignore the case, i did checked both are same – Glory Raj Apr 12 '21 at 15:07
  • `public bool? IsApproved { get; set; } , public Guid? SourceOfDataId { get; set; }` – Glory Raj Apr 12 '21 at 15:08
  • could you please point me in direction where it needs tuning – Glory Raj Apr 12 '21 at 15:25
  • Create class and apply mapper to this class. Check why mapper is not working. There is 20 lines of code which you can debug. – Svyatoslav Danyliv Apr 12 '21 at 15:33
  • thanks for suggestion, i will test and post the results here – Glory Raj Apr 12 '21 at 15:39
  • I am trying to test through on plain object like this and what do i need to pass as first argument here i am looking to pass like as this `TestClassA170 arg1` but it is not taking `var results = PropertyMapper.Mapper(TestClassA170 arg1, masterSectionMappedLibrary);` – Glory Raj Apr 12 '21 at 18:59
  • I have created testClass this time sourceOfdataId is correct but still isApproved is being false and i tried to setting to true and updated the question as well – Glory Raj Apr 12 '21 at 19:32
  • it is looks like the value for IsApproved is null before and then with propertyMapper it is setting that to false and i tried initializing value with false in testClass and tried to set to true through property mapper and it worked.. I think with this it cannot set the values which we want if the SourceProperty is null and this is for bool – Glory Raj Apr 12 '21 at 20:06
  • I can use third party library even with that same problem i beleive – Glory Raj Apr 12 '21 at 21:42
  • Filter out IsApproved here `where d.Name != "MasterSection" && d.Name != "IsApproved"` – Svyatoslav Danyliv Apr 12 '21 at 21:55
  • I did that one as well, the problem is i am trying to set "true" to nullable bool variable and if i try to set to true that is already having false value .. this way is working – Glory Raj Apr 12 '21 at 21:55
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/231047/discussion-between-enigma-state-and-svyatoslav-danyliv). i can use third party library as well – Glory Raj Apr 12 '21 at 22:05
  • Oh man! I fixed isApproved assignment and the remaining is only one `sourceOfDataId` we should assign constant like this `var isApprovedPropertyBind = Expression.Bind(isApprovedProp, Expression.Constant(true, typeof(bool?)));` and not sure how to fix sourceOfDataId ... There should be something wrong with these assignments, Could you please let me know which library you referred to – Glory Raj Apr 13 '21 at 20:28
  • because i am trying to assign source `SourceOfData.Id` to nullable Guid (i.e) `public Guid? SourceOfDataId { get; set; }` – Glory Raj Apr 13 '21 at 20:45
  • 1
    Apply Expression.Convert(..., typeof(Guid?)) for part of assignment – Svyatoslav Danyliv Apr 13 '21 at 22:19
  • sorry where exactly need to apply here in this line `Expression.Bind(sourceOfDataProp, Expression.Property(Expression.Property(itemParam, "SourceOfData"), "Id"));` – Glory Raj Apr 13 '21 at 22:20
  • do you know where to apply conversion exactly – Glory Raj Apr 13 '21 at 22:31
  • oh man! that worked and this is what i have used `Expression.Bind(sourceOfDataProp, Expression.Convert(Expression.Property(Expression.Property(itemParam, "SourceOfData"), "Id"), typeof(Guid?)))` and thanks for the support – Glory Raj Apr 13 '21 at 22:45