Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoMapper ProjectTo inheritance issue

How can I make ProjectTo map different derived types correctly without it casting them down to the base type?

This works fine with Mapper.Map, but not with ProjectTo.

Source classes: (EntityFramework models)

public class FailureAlertEntity : AlertEntity
{
    public FailureAlertEntity(int id, string description) : base(id)
    {
        Description = description;
    }

    public string Description { get; set; }
}

public class AlertEntity
{
    public AlertEntity(int id)
    {
        ID = id;
    }

    public int ID { get; set; }
}

Target classes (DTO models):

public class FailureAlert : Alert
{
    public FailureAlert(int id, string description) : base(id)
    {
        Description = description;
    }

    public string Description { get; set; }
}

public class Alert
{
    public Alert(int id)
    {
        ID = id;
    }

    public int ID { get; set; }
}

There are multiple classes deriving from AlertEntity and Alert, and I would like to map between these two types without the derived types being casted down to their base types.

Configuration:

Mapper.Initialize(cfg => {
    cfg.CreateMap<AlertEntity, Alert>()
          .Include<FailureAlertEntity, FailureAlert>();
    cfg.CreateMap<FailureAlertEntity, FailureAlert>();
});

Test code:

var entities = new List<AlertEntity>()
{
    new FailureAlertEntity(1, "foo"),
    new FailureAlertEntity(2, "bar")
};

var alerts = entities.AsQueryable().ProjectTo<Alert>();

Result:

nope

ProjectTo doesn't seem to consider the types of the items in the list, but casts them to the type of List itself. If the type of the List is FailureAlertEntity, then obviously it would work, but the list could contain other types deriving from AlertEntity.

If I wanted to do the same for an object using Mapper.Map, it works just fine:

var faEntity = new FailureAlertEntity(1, "asd");
var dto = Mapper.Map<Alert>(faEntity);

Result:

yep

How can I make ProjectTo map types like Mapper.Map does? I assumed they would both use the same configuration.

like image 374
Shahin Dohan Avatar asked Mar 30 '17 09:03

Shahin Dohan


People also ask

What is projectto<orderlinedto> in automapper?

The .ProjectTo<OrderLineDTO> () will tell AutoMapper’s mapping engine to emit a select clause to the IQueryable that will inform entity framework that it only needs to query the Name column of the Item table, same as if you manually projected your IQueryable to an OrderLineDTO with a Select clause.

How does automapper work with nested collections?

Because the LINQ projection built by AutoMapper is translated directly to a SQL query by the query provider, the mapping occurs at the SQL/ADO.NET level, and not touching your entities. All data is eagerly fetched and loaded into your DTOs. Nested collections use a Select to project child DTOs:

What are queryable extensions in automapper?

Queryable Extensions ¶ When using an ORM such as NHibernate or Entity Framework with AutoMapper’s standard mapper.Map functions, you may notice that the ORM will query all the fields of all the objects within a graph when AutoMapper is attempting to map the results to a destination type.

Why does automapper map the generic payment type to dtocashpayment?

So my theory is that when AutoMapper tries to map CashPayment to DtoCashPayment and the payment passed in is of the proxy type AutoMapper sees it as a "non match" and then maps the generic Payment type. But since Payment is an abstract class AutoMapper bombs with a "System.InvalidOperationException: Instances of abstract classes cannot be created."


1 Answers

You cannot do that, but there is a good reason why. ProjectTo is extension of IQueryable, not IEnumerable. What is IQueryable?

Provides functionality to evaluate queries against a specific data source

The IQueryable interface is intended for implementation by query providers.

So IQueryable represents a query to data source (like sql server database) via query provider (like Entity Framework). ProjectTo will build a Select expression, it will not actually project anything in memory. So it will do rougly this:

entities.AsQueryable().Select(c => new Alert() {Property = c.Property})

Where thing inside Select is expression (not a function) which will be translated by query provider to the data source query (like SELECT Property from ... to sql server). At this moment, query is not executed and it's not even possible to know elements of which subtypes (if any) it will return. You can select only one subset of columns, you cannot say "give me this columns if Alert is FailureAlert, but another one if it's AnotherAlert", such expression cannot be translated to data storage query. Now, Entity Framework for example can handle inheritance of it's entities (like table-per-type inheritance) but it has more info (which tables are used to handle inheritance for example) than automapper at this point. Automapper only has type to map from and type to map to.

So given the above - it's just not possible for AutoMapper to do it another way.

Now, in your example you are using "fake" IQueryable. If that is the case in real application - just use IEnumerable and:

var alerts = Mapper.Map<List<Alert>>(entities);

or

var alerts = entities.Select(Mapper.Map<Alert>).ToList();

If you have real IQueryable, for example Entity Framework query that can really return list of AlertEntity with subtypes - you have to materialize your query before mapping it with automapper (of course this will materialize entities with all properties):

var alerts = yourQuery.AsEnumerable().Select(Mapper.Map<Alert>).ToList();
like image 121
Evk Avatar answered Sep 24 '22 17:09

Evk