In AutoMapper how does one project from an abstract base class to an interface

758 views Asked by At

I am trying to map a number of entities that come from EF Core backed by CosmosDB to an equivalent set of concrete types that implement an interface. For simplicity, In the example below I have just stripped it down to a List<T>.

When I run my code I get an exception about IAccount not having a default constructor.

`IAccount' does not have a default constructor (Parameter 'type')'

The error happens at var query = mapper.ProjectTo<IAccount>(repo);. I have tried every combination of configuration that I can think of but I am stuck.

My current version is as follows, which is stripped down from my original classes. This is why AccountBase exists, which isn't obvious from the example.

Source Types

public abstract class AccountEntity
{
    public Guid Id { get; set; }
    public abstract string Name { get; set; }        
}

public class UserEntity : AccountEntity
{        
    public string Username { get; set; } = null!;
    public string Email { get; set; } = null!;
    public string FirstName { get; set; } = null!;
    public string LastName { get; set; } = null!;
    public override string Name { get; set; } = null!;
}

public class OrganizationEntity : AccountEntity
{
    public override string Name { get; set; } = null!;
    public IEnumerable<string> Industries { get; set; } = null!;
    public string? Website { get; set; }
}

Destination Types

public interface IAccount
{
    Guid Id { get; }
    string Name { get; }        
}

public abstract class AccountBase : IAccount
{
    public Guid Id { get; set; }
    public string Name { get; set; } = null!;
}

public class User : AccountBase
{
    public string Username { get; set; } = null!;
    public string Email { get; set; } = null!;
    public string FirstName { get; set; } = null!;
    public string LastName { get; set; } = null!;        
}

public class Organization : AccountBase
{
    public IEnumerable<string> Industries { get; } = null!;
    public string? Website { get; set; }
}

Test

var config = new MapperConfiguration(c =>
{
    c.CreateMap<AccountEntity, IAccount>()
        .IncludeAllDerived();

    c.CreateMap<UserEntity, IAccount>()
        .As<User>();

    c.CreateMap<UserEntity, User>();

    c.CreateMap<OrganizationEntity, IAccount>()
        .As<Organization>();

    c.CreateMap<OrganizationEntity, Organization>();
});

config.AssertConfigurationIsValid();

var mapper = config.CreateMapper();

var repo = new List<AccountEntity>()
{
    new UserEntity()
    {
        Id = Guid.NewGuid(),
        FirstName = "First",
        LastName = "User"
    },    
    new OrganizationEntity()
    {
        Id = Guid.NewGuid(),
        Industries = new [] { "SPACETRAVEL" },
        Name = "Org 1"
    }
}.AsQueryable();

var queryProjection = mapper.ProjectTo<IAccount>(repo);
var results = queryProjection.ToList();

My goal is to get an Organization when OrganizationEntity is encountered and likewise an User for an UserEntity.

I have tried .DisableCtorValidation() and .ConvertUsing() but those don't help in my testing.

1

There are 1 answers

3
Clint Singer On

Based on the response from github issue 3293, it appears not to be possible. Though it feels like it should be because the underlying provider, at least in my case the CosmosDB provider, does support inheritance via a Discriminator.

Maybe AutoMapper will improve to support this but for now I need to figure out a workaround...

I'll gladly still take suggestions :)