2

How I can use MapContext.Current in nested mappings? For example:

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

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

public class Src {
    public IEnumerable<Foo> Foos { get; set; }  
}

public class Dst {
    public IEnumerable<Bar> Bars { get; set; }  
    public string Name { get; set; }
}

TypeAdapterConfig<Foo, Bar>
    .NewConfig()
    .Map(d => d.Name, s => (string)MapContext.Current.Parameters["prefix"] + s.Name);

TypeAdapterConfig<Src, Dst>
    .NewConfig()
    .Map(d => d.Bars, s => s.Foos)
    .Map(d => d.Name, s => (string)MapContext.Current.Parameters["prefix"]);
    
var src = new Src 
{
    Foos = new [] { "test" }
};  

var dst = src
    .BuildAdapter()
    .AddParameter("prefix", "!")
    .AdaptToType<Dst>();

When i try to map a src into dst i get Null reference exception for MapContext.Current on attempt to map Foos into Bars. Context works for top level mappings (Dst.Name will be set) but is not accessible on nested mappings. How can I solve that?

Razvan Socol
  • 5,426
  • 2
  • 20
  • 32
Krzysztof
  • 498
  • 5
  • 23

2 Answers2

1

It works for me if I explicitly add the call to create the MapScopeContext:

using var contextScope = new MapContextScope();

Anyway, the ServiceMapper implementation should already add it as found in source code. Additionally, it used to work for me, but in a new XUnit test project I'm getting the exception:

System.InvalidOperationException: Mapping must be called using ServiceAdapter
            at Mapster.TypeAdapterExtensions.GetService[TService](MapContext context)

and debugging I found that the MapContext.Current property is null.

I would like to understand why I'm getting this inconsistent behavior.

fra
  • 3,488
  • 5
  • 38
  • 61
0

When using item.Adapt<OtherClass>(mapper.Config) I get the exception as mentioned above, however when using mapper.Map<OtherClass>(item) it works fine with MapContext.Current.GetService<SomeService>() in the mapping configuration.

Given that you use DI and an instance of TypeAdapterConfig is registered as Singleton and ServiceMapper (not Mapper because ServiceMapper creates a new Context when calling Map<T> which Mapper does not do) is registered via IMapper as scoped service. Internally Mapster ensures that MapContext is not null when using Map<T> instead of Adapt<T>

So like this for DI:

var typeAdapterConfig = new TypeAdapterConfig();
typeAdapterConfig.Scan(...);
typeAdapterConfig.Compile();

services
    .AddSingleton(typeAdapterConfig)
    .AddScoped<IMapper, ServiceMapper>();

Mapping:

public class IdMappingConfiguration : IRegister
{
    public void Register(TypeAdapterConfig config)
    {
        _ = config ?? throw new ArgumentNullException(nameof(config));

        _ = config
            .NewConfig<PocoId, DtoId>()
            .Map(dest => dest.Id, src => MapContext.Current.GetService<SomeService>().Do(src.Id));
    }
}

And resolving:

_mapper.Map<DtoId>(idPoco);

Internally Mapster works like this MapsterMapper.ServiceMapper making sure the context is present:

public override TDestination Map<TDestination>(object source)
{
    using var scope = new MapContextScope();
    scope.Context.Parameters[DI_KEY] = _serviceProvider;
    return base.Map<TDestination>(source);
}

In case you want to update a POCO by using a DTO:

var poco = await this._dataContext.SomeEntities.FirstAsync();
            
_mapper.From(dto).AdaptTo(poco);

await _dataContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);

Adding this open issue on GitHub for reference https://github.com/MapsterMapper/Mapster/issues/230

asymetrixs
  • 51
  • 5