0

I'm trying to map a class Function to another one called FunctionDTO using AutoMapper. The classes look like this:

public class Function
{
     ...
     public int MasterFunctionId { get; set; }
     public Function MasterFunction { get; set; }
     ...
}

public class FunctionDTO
{
     ...
     public int MasterFunctionId { get; set; }
     public FunctionDTO MasterFunction { get; set; }
     ...
}

The mapping works perfectly for properties such as MasterFunctionId, but MasterFunction is always null, even when the Function object has a value in that property.

The call to the mapper is done in the following way (P.S. the variable config is injected into the class):

query.ProjectTo<FunctionModel>(config)

I can't use the following because the I get an error message, probably because Mapper is not initialized:

CreateMap<FLHFunction, FunctionModel>()
    .ForMember(f => f.PRNummerMaster, opt => opt.MapFrom(src => Mapper.Map<FLHFunction, FunctionModel>(src)));

Is there any way I can configure the mapping in order to make this work? I tried similar solutions to my last piece of code, but I must be missing something.

atiyar
  • 7,762
  • 6
  • 34
  • 75
Corvo
  • 147
  • 1
  • 11
  • `ForMember(f => f.PRNummerMaster, opt => opt.MapFrom(src => src))` – Lucian Bargaoanu Feb 28 '21 at 17:11
  • already tried that, and the property is still null – Corvo Feb 28 '21 at 17:44
  • @Leaky thank you for the kind words. Originally, it was the way you described, but it was still empty all the time. I don't use mapper.Map, I use ProjectTo with an EF query and I pass a config which contains the AutoMapper profile, in which I just have several CreateMap statements. – Corvo Feb 28 '21 at 20:41
  • @Corvo, ah, I understand; it's with `ProjectTo()`. It still sounds odd to me that it couldn't handle a composite pattern like that. I kind of feel lazy, but maybe I'll look into it. :) – Leaky Feb 28 '21 at 20:58
  • How would you do it without AM, with an EF linq query? – Lucian Bargaoanu Mar 01 '21 at 05:24

3 Answers3

1

I looked into it, and unfortunately it seems that this (i.e. self-referencing, recursive mapping) will not work with ProjectTo().

A couple of GitHub issues:

https://github.com/AutoMapper/AutoMapper/issues/3195 https://github.com/AutoMapper/AutoMapper/issues/2171 https://github.com/AutoMapper/AutoMapper/issues/1149

What JBogard always seems to suggest is to use explicitly named hierarchical DTOs for each level. With that approach it's quite trivial to solve the issue (assuming that you just want e.g. 2 levels).

For example:

    public class Function
    {
         public int MasterFunctionId { get; set; }
         public Function MasterFunction { get; set; }
    }

    public class FunctionChildDTO : FunctionDTO {}
    
    public class FunctionDTO
    {
         public int MasterFunctionId { get; set; }
         public FunctionChildDTO MasterFunction { get; set; }
    }

    ...
    
    var config = new MapperConfiguration(cfg =>
    {
        cfg.CreateMap<Function, FunctionDTO>();
        cfg.CreateMap<Function, FunctionChildDTO>();
        
    });

But let's hope I'm wrong, and someone will be able to provide a proper solution. I'll just leave this answer here meanwhile for information.

Leaky
  • 3,088
  • 2
  • 26
  • 35
0

You need to map the property classes too but since it's referencing itself it's not possible to map a recursive class on to itself.

    public class Function
{
     ...
     public int MasterFunctionId { get; set; }
     public ClassA MasterFunction { get; set; }
     ...
}

public class FunctionDTO
{
     ...
     public int MasterFunctionId { get; set; }
     public ClassB MasterFunction { get; set; }
     ...
}

you must map nested classes first CreateMap<ClassA,ClassB>()

then you can map the wrapper classes which can then use the previous mapping.

  • I understand, but ClassA and ClassB in this case are Function and FunctionDTO respectively. How do I go about replacing them for your classes A and B? – Corvo Feb 28 '21 at 18:57
  • 1
    It looks like a design issue. If you try to look at that directly you could figure it out more easily. Mapper is just doing the manual job automatically so in the first place you could be able to do it manually. If you try to instantiate the function class and map it to FunctionDTO 's masterfunction property it will get into an endless loop. So you need to change the design of the classes. I don't really know what you tring to achive by referencing the class as it's own property at the first place. – Kemal Yıldırım Feb 28 '21 at 21:49
0

As far as I can determine, Automapper does not support direct self-referencing mapping via projections. The documentation is a bit foggy around this as it would seem that you are just missing a MaxDepth configuration option for the recursive associations but I tested that rather thoroughly and while it works for .Map(), it does not appear to work at all for .ProjectTo() even though the documentation indicates that PreserveReferences does not apply to ProjectTo() but MaxDepth does.

What does work is to structure your DTOs to reflect the hierarchy. For example, instead of:

public class FunctionDTO
{
     ...
     public int? MasterFunctionId { get; set; }
     public FunctionDTO MasterFunction { get; set; }
}

For 1 level of self reference you can use:

public class FunctionDTO
{
     ...
     public int? MasterFunctionId { get; set; }
     public MasterFunctionDTO MasterFunction { get; set; }
}

public class MasterFunctionDTO : FunctionDTO
{
    public new FunctionDTO MasterFunction 
    { 
        get{ return null;} 
    }
}  

Where your configuration maps Function to both FunctionDTO and MasterFunctionDTO. The fields in the DTOs would be essentially identical. MasterFunctionDTO's MasterFunction would not be populated, so for clarity it's configuration option should be set to Ignore().

For 2 levels of hierarchy:

public class FunctionDTO
{
     ...
     public int? MasterFunctionId { get; set; }
     public MasterFunctionDTO MasterFunction { get; set; }
}

public class MasterFunctionDTO : FunctionDTO
{
    public new MasterMasterFunctionDTO MasterFunction {get; set;}
}    

public class MasterMasterFunctionDTO : FunctionDTO
{
    public new FunctionDTO MasterFunction 
    { 
        get{ return null;} 
    }
}

With all 3 DTO's mapped back to Function and the final level's "Master" property set to Ignore. Automapper can resolve these references and project them.

Steve Py
  • 26,149
  • 3
  • 25
  • 43