JWrightII
JWrightII

Reputation: 1084

AutoMapper 12.0 Throwing Unknown LINQ expression of type 'Default' when using ProjectTo

We're in the process of doing a large .net upgrade (moving from 4.8 to 7) and we're having issues with some of our projections. This was all working just fine with automapper 8 and .net 4.8, but since upgrading to 12 we now get NotSupportedException: Unknown LINQ expression of type 'Default'. when trying to project to an entity that has optional related children.

Feels like what we're doing is pretty basic so not sure what's going wrong.

Entity:

[Table("PaymentUploads")]
public class PaymentUpload
{
    [Key]
    [Column("PaymentUploadId")]
    public int Id { get; set; }
    [Required]
    [MaxLength(200)]
    public string FileName { get; set; }
    [Required]
    public byte[] RawFileContents { get; set; }
    public DateTime? ProcessedOn { get; set; }
    public Guid? ProcessedById { get; set; }

    [ForeignKey("ProcessedById")]
    public virtual ApplicationUser? ProcessedBy { get; set; }
}

Dto:

public class PaymentUploadDto
{
    public int Id { get; set; }
    public string FileName { get; set; }
    public DateTime TransactionDate { get; set; }
    public decimal TotalPaymentAmount { get; set; }
    public DateTime? ProcessedOn { get; set; }
    public Guid? ProcessedById { get; set; }

    public ApplicationUserPreview? ProcessedBy { get; set; }
}

Profile:

CreateProjection<PaymentUpload, PaymentUploadDto>();

Request:

DbContext.PaymentUploads.AsNoTracking().Where(g => g.CreatedOn >= createdOnOrAfter).ProjectTo<PaymentUploadDto>(Mapper.ConfigurationProvider);

If we change the profile to:

CreateProjection<PaymentUpload, PaymentUploadDto>()
    .ForMember(dest => dest.ProcessedBy, opt => opt.DoNotAllowNull());

Then as long as every ProcessedById column entry has a value everything returns as expected, but if any do contain null for the ProcessedById now it instead throws: 'The cast to value type 'System.Guid' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.' All of the associated properties seem to be properly declared as nullable so we aren't sure why that's not translating down to whatever automapper is trying to execute.

Tried AllowNull() instead but then it just goes back to throwing Unknown LINQ expression of type 'Default'.

Upvotes: 1

Views: 507

Answers (1)

Tom&#225;š Herceg
Tom&#225;š Herceg

Reputation: 1640

Unfortunately, this issue makes AutoMapper very difficult to use with EF 6 running on the new versions of .NET (a use case supported by Microsoft that is really helpful in migrating old applications).

I solved it using a simple rewriting technique (replacing the Default expression with Constant). Replace ProjectTo with the following extension method ProjectToWithDefaultExpressionFix:

public static class QueryExtensions
{
    public static IQueryable<TDestination> ProjectToWithDefaultExpressionFix<TDestination>(this IQueryable source, IConfigurationProvider configuration)
    {
        var projectedExpression = source.ProjectTo<TDestination>(configuration);
        var rewrittenExpression = new FixAutoMapperExpressionRewriter().Visit(projectedExpression.Expression);
        return projectedExpression.Provider.CreateQuery<TDestination>(rewrittenExpression);
    }

    class FixAutoMapperExpressionRewriter : ExpressionVisitor
    {
        protected override Expression VisitDefault(DefaultExpression node)
        {
            return Expression.Constant(node.Type.IsValueType ? Activator.CreateInstance(node.Type) : null, node.Type);
        }
    }
}

Upvotes: 1

Related Questions