Search code examples
c#entity-frameworklinqautomapperautomapper-2

Automapper projection and union


I have a problem with union and automapper projections.

I have two entities:

public class Entity
{
    public DateTime ActionDate { get; set; }
    public int SomeProp { get; set; }
}

public class ExtendedEntity
{
    public DateTime ActionDate { get; set; }
    public int SomeProp { get; set; }
    public int SomeOtherProp { get; set; }
}

and projection:

public class EntityProjection
{
    public DateTime ActionDate { get; set; }
    public int SomeProp { get; set; }
    public int SomeOtherProp { get; set; }
    public string Source { get; set; }
}

i map entities to one projection, Entity does not have SomeOtherProp so i set 0 to it:

public class EntityProfile : Profile
{
    protected override void Configure()
    {
        CreateMap<ExtendedEntity, EntityProjection>()
            .ForMember(dest => dest.Source, opt => opt.MapFrom(src => "ext entity"));

        CreateMap<Entity, EntityProjection>()
            .ForMember(dest => dest.SomeOtherProp, opt => opt.MapFrom(src => 0))
            .ForMember(dest => dest.Source, opt => opt.MapFrom(src => "entity"));
    }
}

when i try to use next code i get error:

 var entities = context.Set<Entity>()
            .Project().To<EntityProjection>();
 var extEntities = context.Set<ExtendedEntity>()
            .Project().To<EntityProjection>();
 var result = entities.Union(extEntities).OrderBy(p => p.ActionDate).ToList();

Error text: The type 'UserQuery+EntityProjection' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be...

That means that properties in projection must be initialized in same order, how i can set projection properties initialization order by automapper?


Solution

  • Very late answer, and the short version seems to be "You can't".

    I had exactly the same question (Can I force Automapper to initialise properties in a certain order?) and ended up mapping everything within a LINQ select statement.

    For ease, I made it a static method within my DTO (cut-down code):

            public static IQueryable<MyDto> QueryableFromTaskType1(
            IQueryable<TaskType1> query)
        {
            return query.Select(src => new MyDto()
            {
                TaskId = src.Id,
                AssetTypeName = src.Asset.AssetType.Name,
                AssetId = src.Asset.Id,
                AssetCode = src.Asset.Code,
                AssetName = src.Asset.Name,
            });
        }
    
            public static IQueryable<MyDto> QueryableFromTaskType2(
            IQueryable<TaskType2> query)
        {
            return query.Select(src => new MyDto()
            {
                TaskId = src.Id,
                AssetTypeName = src.AssetTypeName,
                AssetId = src.AssetId,
                AssetCode = src.AssetCode,
                AssetName = src.AssetName,
            });
        }
    

    then you can get your objects, as an IQueryable, simply pass them through the appropriate static method (which appends a select into the DTO - or projects as it's otherwise known) and then Union or Concat the resulting IQueryables.

    The only downside is that Automapper will normally deal with recursive automapping, although I'm pretty certain that wouldn't map to SQL well anyway, so you probably don't lose much.