I’m using Automapper for the first time in a .net core project that uses dependency injection to transfer data from from an old legacy database to a new microservice architecture, with each microservice connecting to it’s own database (using DDD principles). Unfortunately it’s not possible to transfer the data from sql to sql and I would rather build an app tht pulls data from source and then pushes data to the new application through the aggregate root and models.
I have 3 projects in my solution:
- The main entry point that creates a service collection with the DbContexts (source and target), adds the target API, adds the automapper and creates/instantiates the transfer service class
- Project contains the source/legacy DB context, models and also the Automapper profile to create the map.
- Service project where sourceDB and targetDB contexts exist and where the Map is performed.
I need to add (I think) a “Value Transformation” for the foreign key fields and rebuild the data structures correctly in the new database(s). My problem is, do I add the value transformation in the Profile in point 2) but this cannot access the target context in 3) Only when the service runs, do both source and target contexts exist and I have access to both. I've looked in the Read the Docs for Automapper but cannot find a way to add a handler in project 2) that is only called in 3) when the mapping on the column is tkaing place.
UPDATE
- Code in Program.cs -> .net core 3.1 console app
static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
ServiceProvider serviceProvider;
serviceCollection
.AddDbContext<Source.Models.SourceDbContext>(options => options.UseSqlServer(config.GetConnectionString("Source")))
.AddDbContext<Target.Models.ModelDbContext>(options => options.UseSqlServer(config.GetConnectionString("Target")))
.AddScoped<ILoggerFactory, LoggerFactory>()
.AddScoped<Source.Models.Services.IReadSourceService, Source.Models.Services.ReadSourceService>()
.AddAutoMapper(typeof(Source.Models.Currency).Assembly);
serviceProvider = serviceCollection.BuildServiceProvider();
}
- In source db model project
public class MappingProfile : Profile
{
public DataTakaMappingProfile(IReadSourceService service)
{
this.CreateMap<Source.Models.Instrument, Target.Models.Instruments.Instrument>()
.ForMember(c => c.InstrumentId, opt => opt.Ignore())
.ForMember(c => c.TrackingState, opt => opt.Ignore())
.ForMember(c => c.CurrencyId, opt => opt.ConvertUsing(new CurrencyIdConverter(), c => c.CurrencyID));
}
- In service project
public class ReadSourceService : IReadSourceService
{
private readonly SourceDbContext context;
private readonly Target.Models.ModelDbContext targetContext;
private readonly ILogger<ReadSourceService> logger;
private readonly IMapper mapper;
/// <summary>
/// Initializes a new instance of the <see cref="ReadSourceService"/> class.
/// </summary>
/// <param name="sourceContext">Source DB context </param>
/// <param name="targetContext">Target DB context</param>
/// <param name="logger">logger</param>
/// <param name="mapper">mapper</param>
public ReadSourceService(
SourceDbContext context,
Target.Models.ModelDbContext targetContext,
ILogger<ReadSourceService> logger,
IMapper mapper)
{
this.context = context;
this.targetContext = targetContext;
this.logger = logger;
this.mapper = mapper;
}
/// <summary>
/// Perform tha data comparison
/// </summary>
public void TransferInstrument()
{
// perform the mapping here
}
}
This is where I need to transform for the source CurrencyId in Source.Instruments to the new CurrencyId in Target.Instruments
UPDATE 2
Instrument "ABC" that has a foreign key reference in currency "USD" will be transferred from source to target. The currencies, at this stage have already been transferred. The CurrencyCode: "USD" will most likely have different primary key values, eg:
- SourceDB -> CurrencyID = 23, CurrencyCode = "USD"
- TargetDB -> CurrencyID = 45, CurrencyCode = "USD"
When the instrument record for InstrumentCode: "ABC" is mapped, I need to intercept the mapping on the "CurrencyID" property and change the value from 23 to 45.
My project structure is as follows
- ProjectA - console app.
└─ Program.cs
- ProjectB - source model classes
├─ Source - folder for data models in your source DB
└─ MappingProfile.cs
- ProjectC - source model classes
└─ DataTransferService.cs
- ProjectD - target model classes. This is just a nuget package that contains the new app/updated models in target DB
UPDATE 3 I've tried creating a value resolver and then adding this to the profile ForMember. I also tried using the IReadSourceService interface in the constructor for the resolver with the hope that DI would use that but either way I get an AutoMapper.AutoMapperMappingException for Destination Member: CurrencyId:
Cannot create an instance of type ForeignKeyResolver
this.CreateMap<Source.Models.Instrument, Target.Models.Instruments.Instrument>()
.ForMember(dest => dest.CurrencyId, opt => opt.MapFrom<ST.DataTaka.Models.Services.ForeignKeyResolver>());
public class ForeignKeyResolver : IValueResolver<object, object, int>
{
private readonly List<Tuple<int, int>> mappings;
public ForeignKeyResolver(List<Tuple<int, int>> mappings)
{
this.mappings = mappings;
}
public int Resolve(object source, object dest, int destMember, ResolutionContext context)
{
return mappings.Where(c => c.Item1 == (int)source).FirstOrDefault().Item2;
}
}