0

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.

  • Have you checked https://stackoverflow.com/questions/29815761/ef6-automapper-inheritance-abstract-class-errors. Seems to be a limitation with LINQ. – Raju Joseph May 11 '20 at 03:37
  • The error they were getting is different than the one I am getting but it is probably due to the same limitation. That's a shame as I would think this is fairly common requirement. Specifically I'm also using HotChocolate which supports IQueryable<> for paging and sorting directly. I may have to come up with an intermediary. – Clint Singer May 11 '20 at 16:40
  • If I replace IAccount with a non-abstract base class I don't get the specific error about IAccount anymore but I'm also not getting versions of the derived classes returned either. That seems to be another issue but still relevant to the second half of the question. Will need to investigate further. – Clint Singer May 11 '20 at 16:41

1 Answers1

0

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 :)

  • Discriminator inheritance and AM inheritance are unrelated. If you can write a LINQ query to achieve what you want, then you'll be able to do it with `ProjectTo`. – Lucian Bargaoanu May 11 '20 at 17:31
  • @LucianBargaoanu, I did try emulating the scenario as seen in the original question but was unable to make it work. I was able to use IQueryable directly to the datasource and got what I needed. It just meant I had to use my DB entity as the type I was exposing; which was what I was trying to avoid. Perhaps I had missed something. Do you have an example? – Clint Singer May 12 '20 at 04:08
  • If you cannot do it with LINQ, surely you cannot do it with `ProjectTo`. `ProjectTo` just delegates to the provider after all. – Lucian Bargaoanu May 12 '20 at 05:18