I have the following entity classes
public class Customer
{
public long Id { get; set; }
[StringLength(100)]
public string Name { get; set; } = string.Empty;
[StringLength(254)]
public string? Email { get; set; }
public List<Order> Orders { get; set; } = new List<Order>();
}
public class Order
{
public long Id { get; set; }
public long CustomerId { get; set; }
[ForeignKey(nameof(CustomerId))]
public Customer? Customer { get; set; }
public DateOnly Date { get; set; }
public List<OrderLine> Lines { get; set; } = [];
}
public class OrderLine
{
public long Id { get; set; }
public long OrderId { get; set; }
[ForeignKey(nameof(OrderId))]
public Order? Order { get; set; }
[StringLength(100)]
public string Item { get; set; } = string.Empty;
public decimal Amount { get; set; }
}
And, there are corresponding data transfer objects (DTO):
public class CustomerDto
{
public long Id { get; set; }
public string Name { get; set; } = string.Empty;
}
public class OrderDto
{
public long Id { get; set; }
public long CustomerId { get; set; }
public CustomerDto? Customer { get; set; }
public DateOnly Date { get; set; }
public List<OrderLineDto> Lines { get; set; } = [];
}
public class OrderLineDto
{
public long Id { get; set; }
public long OrderId { get; set; }
public string Item { get; set; } = string.Empty;
public decimal Amount { get; set; }
}
I made the following AutoMapper configuration:
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<Customer, CustomerDto>()
.ReverseMap()
.ForPath(o => o.Id, o => o.Ignore());
CreateMap<Order, OrderDto>()
.ReverseMap()
.ForPath(o => o.Id, o => o.Ignore());
CreateMap<OrderLine, OrderLineDto>()
.ReverseMap()
.ForPath(o => o.Id, o => o.Ignore());
}
}
The above configuration is registered to DI of my ASP.NET web application:
services.AddAutoMapper(typeof(AutoMapperProfile));
Now, I'd like to search Orders entity with specific search criteria and map it to OrderDto and send to my web client. For that I do the following:
var config = new MapperConfiguration(cfg => cfg.CreateProjection<Order, OrderDto>());
IQueryable<Order> query = db.Orders
.Include(o => o.Customer)
.Include(o => o.Lines);
// Apply some Where criteria here
var orderDtos = await query
.AsNoTracking()
.OrderBy(o => o.Id)
.ProjectTo<OrderDto>(config)
.ToListAsync();
However, I got the following error:
Unable to create a map expression from Order.Customer to CustomerDto.Customer ...
I guess similar issue exists for OrderLineDto even though error message doesn't tell anything about it yet. I can I solve the issue? It looks like I should edit my configuration with ForMember method, but I couldn't make it work.
The initialization of your mapping config is a bit suspect. If you are using an Automapper Profile, try this:
var config = new MapperConfiguration(cfg => cfg.AddProfile(new AutoMapperProfile());
var orderDtos = await db.Orders
.OrderBy(o => o.Id)
.ProjectTo<OrderDto>(config)
.ToListAsync();
Note that when using projection you do not need to eager load related entities (Include
) or worry about tracking. (AsNoTracking
). No entities are populated or tracked, and EF will join the necessary tables automatically based on the mapped projections.