Simon
Simon

Reputation: 415

How to Conditionally MapFrom in Automapper using ProjectTo

I want to create a query for EFCore 5 using projection from Automapper 10.1.1 that will conditionally include associations. If I were to write this in a select statement, I would do the following:

_dbContext.Employees
.Select(x => new EmployeeDto()
{
 Id = x.Id,
 Contract = includeContract ? new ContractDto()
 {
  Id = x.Contract.Id
 } : null
})
.FirstAsync();

I attempted it like so:

Models

//Source
  public class Employee
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public Contract ActiveContract { get; set; }
  }

  public class Contract
  {
    public int Id { get; set; }
    public DateTimeOffset StartDate { get; set; }
    public DateTimeOffset EndDate { get; set; }
    public Employee Employee { get; set; }
    public int EmployeeId { get; set; }
  }


//Destination
  public class EmployeeDto
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public ContractDto ActiveContract { get; set; }
  }

  public class ContractDto
  {
    public int Id { get; set; }
    public DateTimeOffset StartDate { get; set; }
    public DateTimeOffset EndDate { get; set; }
  }

AutoMapper Profile

public class EmployeeProfile : Profile
{
  public EmployeeProfile()
  {
    bool includeContract = false;

    CreateMap<Employee, EmployeeDto>()
    .ForMember(x => x.ActiveContract, opt => opt.MapFrom(x => includeContract ? x.ActiveContract : null));

  }
}

Instead of null, I've tried default(Contract) and (Contract)null these produce the same result. Also I have a mapping for Contracts, just a simple <Contract, ContractDto>.

Query

bool includeContract = someCondition;
var result = await _dbContext.Employees
      .ProjectTo<EmployeeDto>(_mapper.ConfigurationProvider, new { includeContract })
      .FirstAsync(x => x.id == id);

My expected result is, I will be returned a EmployeeDto with the Contract being null, unless the includeContract is set to true.

However if the includeContract is false, the following error is thrown:

System.InvalidOperationException: Expression 'NULL' in SQL tree does not have type mapping assigned

the query expression produced:

       Compiling query expression: 
       'DbSet<Employee>()
           .Select(dtoEmployee => new EmployeeDto{ 
               Age = dtoEmployee.Age, 
               Id = dtoEmployee.Id, 
               Name = dtoEmployee.Name, 
               ActiveContract = null == null ? null : new ContractDto{ 
                   Id = null.Id, 
                   StartDate = null.StartDate, 
                   EndDate = null.EndDate 
               }
                
           }
           )
           .First(x => x.Id == __id_0)'

I know that is achievable by explicitly defining the expression within a ConvertUsing, but would like to avoid writing out my whole DTOs if possible.

Upvotes: 1

Views: 968

Answers (1)

Simon
Simon

Reputation: 415

Referring to the documentation linked via @Lucian in their comment.

The solution was to the adjust the AutoMapper config to the following:

CreateMap<Employee, EmployeeDto>()
.ForMember(x => x.ActiveContract, opt => opt.ExplicitExpansion())

And to adjust the query to the following:

var result = await _dbContext.Employees
.ProjectTo<EmployeeDto>(_mapper.ConfigurationProvider, 
  null,
  dest => dest.ActiveContract
)
.FirstAsync(x => x.id == id);

Upvotes: 1

Related Questions