Polymorph entity (TPH) with select-mapping and FirstOrDefault - could not be translated
File a bug
I have a polymorph entity stored as TPH with the following config:
public class Job
{
public Guid Id { get; set; }
public JobType Type { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public uint xmin { get; set; }
}
public class ProtocolJob : Job
{
public string? Source { get; set; }
public ProtocolSeverity Severity { get; set; }
public string Message { get; set; }
public List<string> Tags { get; set; }
}
public class EmailJob : Job
{
public string Receiver { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
}
public class JobConfiguration : IEntityTypeConfiguration<Job>
{
public void Configure(EntityTypeBuilder<Job> builder)
{
builder.HasDiscriminator(t => t.Type)
.HasValue<Job>(JobType.Unknown)
.HasValue<EmailJob>(JobType.Email)
.HasValue<ProtocolJob>(JobType.Protocol);
}
}
So now i run all queries with a DTO-Mapping inside select.
Like that:
return await _database.Jobs.Select(j => (j is ProtocolJob) ? new ProtocolJobDto
{
Source = ((ProtocolJob)j).Source,
Severity = ((ProtocolJob)j).Severity,
Message = ((ProtocolJob)j).Message,
Tags = EF.Property<List<string>>((ProtocolJob)j, "Tags"),
Id = ((ProtocolJob)j).Id,
Type = ((ProtocolJob)j).Type,
Name = ((ProtocolJob)j).Name,
Description = ((ProtocolJob)j).Description,
xmin = ((ProtocolJob)j).xmin
}
: (j is EmailJob) ? new EmailJobDto
{
Receiver = ((EmailJob)j).Receiver,
Subject = ((EmailJob)j).Subject,
Body = ((EmailJob)j).Body,
Id = ((EmailJob)j).Id,
Type = ((EmailJob)j).Type,
Name = ((EmailJob)j).Name,
Description = ((EmailJob)j).Description,
xmin = ((EmailJob)j).xmin
}
: new JobDto
{
Id = j.Id,
Type = j.Type,
Name = j.Name,
Description = j.Description,
xmin = j.xmin
})
.ToListAsync(cancellationToken)
That works absolute amazing!
The issue i am facing, is if i add a FirstOrDefault to this. FirstOrDefault without filterExpression is working also perfect, but when i add a filterExpression it fails:
return await _database.Jobs.Select(j => (j is ProtocolJob) ? new ProtocolJobDto
{
Source = ((ProtocolJob)j).Source,
Severity = ((ProtocolJob)j).Severity,
Message = ((ProtocolJob)j).Message,
Tags = EF.Property<List<string>>((ProtocolJob)j, "Tags"),
Id = ((ProtocolJob)j).Id,
Type = ((ProtocolJob)j).Type,
Name = ((ProtocolJob)j).Name,
Description = ((ProtocolJob)j).Description,
xmin = ((ProtocolJob)j).xmin
}
: (j is EmailJob) ? new EmailJobDto
{
Receiver = ((EmailJob)j).Receiver,
Subject = ((EmailJob)j).Subject,
Body = ((EmailJob)j).Body,
Id = ((EmailJob)j).Id,
Type = ((EmailJob)j).Type,
Name = ((EmailJob)j).Name,
Description = ((EmailJob)j).Description,
xmin = ((EmailJob)j).xmin
}
: new JobDto
{
Id = j.Id,
Type = j.Type,
Name = j.Name,
Description = j.Description,
xmin = j.xmin
})
.FirstOrDefaultAsync(cancellationToken) // <-- WORKS
//.FirstOrDefaultAsync(j => j.Id == request.Id, cancellationToken) // <-- DONT WORK
In that case i receive the following error:
The LINQ expression 'DbSet<Job>()
.Where(j => (j is ProtocolJob) ? (JobDto)new ProtocolJobDto{
Source = ((ProtocolJob)j).Source,
Severity = ((ProtocolJob)j).Severity,
Message = ((ProtocolJob)j).Message,
Tags = EF.Property<List<string>>((ProtocolJob)j, "Tags"),
Id = ((ProtocolJob)j).Id,
Type = ((ProtocolJob)j).Type,
Name = ((ProtocolJob)j).Name,
Description = ((ProtocolJob)j).Description,
xmin = ((ProtocolJob)j).xmin
}
: (j is EmailJob) ? (JobDto)new EmailJobDto{
Receiver = ((EmailJob)j).Receiver,
Subject = ((EmailJob)j).Subject,
Body = ((EmailJob)j).Body,
Id = ((EmailJob)j).Id,
Type = ((EmailJob)j).Type,
Name = ((EmailJob)j).Name,
Description = ((EmailJob)j).Description,
xmin = ((EmailJob)j).xmin
}
: new JobDto{
Id = j.Id,
Type = j.Type,
Name = j.Name,
Description = j.Description,
xmin = j.xmin
}
.Id == __request_Id_0)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
provider and version information
EF Core version: Microsoft.EntityFrameworkCore 8.0.1 Database provider: Npgsql.EntityFrameworkCore.PostgreSQL 8.0.0 Target framework: .NET 8 Operating system: Windows IDE: Rider
/cc @maumar Can you take a look at this? Could be a dupe.
example of a dupe: https://github.com/dotnet/efcore/issues/31279
.FirstOrDefaultAsync(j => j.Id == request.Id, cancellationToken) is effectively
.Where(j => j.Id == request.Id).FirstOrDefaultAsync(cancellationToken)
so now your custom DTO ctors are not in final projection (which allows us to run it as the last step), but rather need to be evaluated before the filter. Since we have no way of knowing what logic is inside those DTO ctors, we throw.
@flobiber In this particular case you can move filter before DTO projections, and things will work.
return await _database.Jobs.Where(j => j.Id == request.Id).Select(j => (j is ProtocolJob) ? new ProtocolJobDto
{
Source = ((ProtocolJob)j).Source,
Severity = ((ProtocolJob)j).Severity,
Message = ((ProtocolJob)j).Message,
Tags = EF.Property<List<string>>((ProtocolJob)j, "Tags"),
Id = ((ProtocolJob)j).Id,
Type = ((ProtocolJob)j).Type,
Name = ((ProtocolJob)j).Name,
Description = ((ProtocolJob)j).Description,
xmin = ((ProtocolJob)j).xmin
}
: (j is EmailJob) ? new EmailJobDto
{
Receiver = ((EmailJob)j).Receiver,
Subject = ((EmailJob)j).Subject,
Body = ((EmailJob)j).Body,
Id = ((EmailJob)j).Id,
Type = ((EmailJob)j).Type,
Name = ((EmailJob)j).Name,
Description = ((EmailJob)j).Description,
xmin = ((EmailJob)j).xmin
}
: new JobDto
{
Id = j.Id,
Type = j.Type,
Name = j.Name,
Description = j.Description,
xmin = j.xmin
})
.FirstOrDefaultAsync(cancellationToken);