-1

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:

  1. 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
  2. Project contains the source/legacy DB context, models and also the Automapper profile to create the map.
  3. 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

  1. 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();
    }
  1. 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));
    }
  1. 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;
    }
  }
Andrew
  • 1
  • 2
  • I'm a little confused about your setup. You mention several source and target DbContexts and API, so it's not clear what lives where - and how is dependency injection relevant in this question? What is a "value transformation"? Is that not what the Automapper profile is? – Xerillio Apr 11 '20 at 21:16
  • 2
    Perhaps you also have some code to show and explain why that's not doing what you want? – Xerillio Apr 11 '20 at 21:17

1 Answers1

0

Thanks for the added details, however, it's still not quite clear what you're asking for. If you're asking how to read the source data from DB, map it to the target models, and then write it to the target DB, it seems to me that all you need is to just fill out TransferInstrument(). A simplified example (guessing a bit on what your contexts look like):

public void TransferInstrument()
{
    foreach (var sourceInstrument in this.context.Instruments)
    {
        var targetInstrument =
            this.mapper.Map<Target.Models.Instruments.Instrument>(sourceInstrument);
        this.targetContext.Instruments.Add(targetInstrument);
    }
    this.targetContext.SaveChanges();
}

Is this what you are looking for? If no, then please explain what it is you think you are missing in your code. And please show us what you have tried and tell us why it's not working the way you intend.


From my understanding all you need here is an application to transfer data from one database to another where the data models have slightly changed. A simple setup I can think of is the following project structure:

- ProjectA                    - console app.
  ├─ Program.cs
  ├─ MappingProfile.cs
  └─ DataTransferService.cs
- ProjectB                    - model classes
  ├─ Source                   - folder for data models in your source DB
  └─ Target                   - folder for data models in your target DB

Based on what you've shown so far dependency injection seems a bit overkill, unless there's some other purpose for it that you haven't shown. ProjectB will contain your SourceDbContext and ModelDbContext (target) and classes for the models used within those two contexts. If the mapping logic and transferring data from source to target is only relevant in this console application, I don't see a reason to create a separate project for it - but again: maybe there's more to it, than shown in the question?

ProjectA handles all the business logic and DB connections while ProjectB only defines how the models look.

Xerillio
  • 4,855
  • 1
  • 17
  • 28