Degusto
Degusto

Reputation: 291

How to map from ViewModel to Model with base properties

I want to map ViewModel to Model with base class but without adding base class to ViewModel.

I would like to keep my classes' structure.

I have tried to use Include, IncludeBase, IncludeDerivered but I can't find good configuration.

Here is my code

public class Program
{
    private static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");

        var mapper = new MapperConfiguration(mc => mc.AddProfile(new MappingProfile())).CreateMapper();

        var model = new RegisterViewModel { Name = "TEST" };

        var x = mapper.Map<Register>(model, Guid.NewGuid());
    }
}

public interface ICommand
{
    Guid Id { get; }

    Guid AggregateId { get; }
}

public abstract class Command : ICommand
{
    protected Command() => Id = Guid.NewGuid();

    public Guid Id { get; }

    public Guid AggregateId { get; private set; }
}

public sealed class Register : Command
{
    public string Name { get; private set; }
}

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

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<RegisterViewModel, Command>()
           .ForMember(x => x.Id, opt => opt.Ignore())
           .AddCommandFields()
           .Include<RegisterViewModel, Register>();

        CreateMap<RegisterViewModel, Register>().IncludeBase<RegisterViewModel, Command>().AddCommandFields();
    }
}

public static class MapperExtensions
{
    private static readonly string AGGREGATE_KEY = "AGGREGATE";

    public static TDestination Map<TDestination>(this IMapper mapper, object source, Guid aggregateId)
    {
        return mapper.Map<TDestination>(source, opt =>
        {
            opt.Items[AGGREGATE_KEY] = aggregateId;
        });
    }

    public static IMappingExpression<TSource, TDestination> AddCommandFields<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
        where TDestination : ICommand => expression.ForMember(x => x.AggregateId, opt => opt.MapFromAggregate());

    private static void MapFromAggregate<TSource, TDestination>(this IMemberConfigurationExpression<TSource, TDestination, Guid> expression) => expression.MapFrom((_, __, ___, context) => context.Items[AGGREGATE_KEY]);
}

For derived properties Automapper correctly works but base properties are not mapped, although MapFromAggregate is invoked.

EDIT

As @Lucian Bargaoanu suggested I wanted to remove extension methods but then I figured out that problem is with method "AddCommandFields".

This code works correctly

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<RegisterViewModel, Command>()
           .ForMember(x => x.Id, opt => opt.Ignore())
           .ForMember(x => x.AggregateId, opt => opt.MapFromAggregate())
           .Include<RegisterViewModel, Register>();

        CreateMap<RegisterViewModel, Register>()
           .IncludeBase<RegisterViewModel, Command>()
          .ForMember(x => x.AggregateId, opt => opt.MapFromAggregate());
    }
}

public static class MapperExtensions
{
    private static readonly string AGGREGATE_KEY = "AGGREGATE";

    public static TDestination Map<TDestination>(this IMapper mapper, object source, Guid aggregateId)
    {
        return mapper.Map<TDestination>(source, opt =>
        {
            opt.Items[AGGREGATE_KEY] = aggregateId;
        });
    }

    public static void MapFromAggregate<TSource, TDestination>(this IMemberConfigurationExpression<TSource, TDestination, Guid> expression) => expression.MapFrom((_, __, ___, context) => context.Items[AGGREGATE_KEY]);
}

I have no idea why this extension method breaks my code.

Upvotes: 0

Views: 94

Answers (1)

Lucian Bargaoanu
Lucian Bargaoanu

Reputation: 3516

The problem is that the generic constraint is for ICommand and there is no setter for ICommand.AggregateId. You can change the constraint to Command or add the setter for ICommand.AggregateId.

Upvotes: 1

Related Questions