Closed as not planned
Description
I have a perfectly functional query which is executed against a Microsoft SQL Server Database. I have optimized the query for EF Core 2.2 to be executed server-side not client-side. The same query in EF Core 3.1 produces the exception:
The LINQ expression 'DbSet<DocumentEntity>
.Where(d => !(d.IsDeleted))
.Join(
outer: DbSet<DocumentRelationEntity>
.Where(d0 => !(d0.IsDeleted)),
inner: d => new {
DocumentId = d.Id,
ObjectType = 0
},
outerKeySelector: d0 => new {
DocumentId = d0.DocumentId,
ObjectType = d0.ObjectType
},
innerKeySelector: (d, d0) => new TransparentIdentifier<DocumentEntity, DocumentRelationEntity>(
Outer = d,
Inner = d0
))
.Join(
outer: DbSet<DocumentCaseEntity>
.Where(d1 => !(d1.IsDeleted)),
inner: ti => ti.Inner.ObjectId.ToLower(),
outerKeySelector: d1 => d1.Id.ToString().ToLower(),
innerKeySelector: (ti, d1) => new TransparentIdentifier<TransparentIdentifier<DocumentEntity, DocumentRelationEntity>, DocumentCaseEntity>(
Outer = ti,
Inner = d1
))' 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 either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
I do not understand why EF Core 2.2 was able to translate the query and EF Core 3.1 is not.
Steps to reproduce
The query is build like this:
var query = context.Documents.AsQueryable()
.Include(document => document.TypeFsCode)
.Join(
context.DocumentRelations.AsQueryable(),
doc => new
{
DocumentId = doc.Id,
ObjectType = (int)ObjectType.DocumentCase,
},
relation => new
{
relation.DocumentId,
relation.ObjectType,
},
(doc, rel) => new
{
DocumentEntity = doc,
DocumentCaseId = rel.ObjectId,
})
.Join(//// We assume that every document is part of a DoumentCaseArchive. Documents that do not have a DocumentCase relation won't be returned
context.DocumentCases.AsQueryable(),
doc => doc.DocumentCaseId.ToLower(),
documentCase => documentCase.Id.ToString().ToLower(),
(doc, documentCase) => new DocumentSearchEntity
{
DocumentEntity = doc.DocumentEntity,
DocumentCaseId = documentCase.Id,
DocumentCaseNumber = documentCase.Number,
LatestVersion = doc.DocumentEntity.Versions
.Where(verFinal => verFinal.Status == (int)VersionStatus.Final)
.OrderByDescending(verFinal => verFinal.CreatedOn)
.FirstOrDefault(),
DraftVersion = includeDraftVersionInResult
? doc.DocumentEntity.Versions
.Where(verDraft => verDraft.Status == (int)VersionStatus.Draft)
.OrderByDescending(verDraft => verDraft.CreatedOn)
.FirstOrDefault()
: null,
})
.Where(entry => entry.DocumentEntity.Id == id)
.Select(entry => entry.TransferPropertiesToDocumentEntity(requestInfo))
.SingleOrDefaultAsync();
The TransferPropertiesToDocumentEntity method is this one:
public DocumentEntity TransferPropertiesToDocumentEntity(IRequestInfo requestInfo)
{
if (DocumentEntity == null)
{
return null;
}
switch (requestInfo.GetRequestCulture().Name.Substring(0, 2).ToLower())
{
case "en":
DocumentEntity.TypeName = DocumentEntity.TypeFsCode?.DisplayNameEn;
break;
case "sr":
DocumentEntity.TypeName = DocumentEntity.TypeFsCode?.DisplayNameSr;
break;
default:
DocumentEntity.TypeName = DocumentEntity.TypeFsCode?.DisplayNameSq;
break;
}
DocumentEntity.DraftVersion = DraftVersion;
DocumentEntity.LatestVersion = LatestVersion;
DocumentEntity.DocumentCaseId = DocumentCaseId;
DocumentEntity.DocumentCaseNumber = DocumentCaseNumber;
return DocumentEntity;
}
With EF Core 2.2 this query gets translated into several SQL statements:
exec sp_executesql N'SELECT TOP(2) [e].[Id], [e].[CreatedBy], [e].[CreatedOn], [e].[DeletedBy], [e].[DeletedOn], [e].[IsDeleted], [e].[Number], [e].[ParentId], [e].[Position], [e].[Type], [e].[UpdatedBy], [e].[UpdatedOn], [document.TypeFsCode].[Id], [document.TypeFsCode].[DisplayNameEn], [document.TypeFsCode].[DisplayNameSq], [document.TypeFsCode].[DisplayNameSr], [t0].[Id], [t0].[Number]
FROM [Document] AS [e]
INNER JOIN [_FsCodes] AS [document.TypeFsCode] ON [e].[Type] = [document.TypeFsCode].[Id]
INNER JOIN (
SELECT [e0].*
FROM [DocumentRelation] AS [e0]
WHERE [e0].[IsDeleted] = 0
) AS [t] ON ([e].[Id] = [t].[DocumentId]) AND (0 = [t].[ObjectType])
INNER JOIN (
SELECT [e1].*
FROM [DocumentCase] AS [e1]
WHERE [e1].[IsDeleted] = 0
) AS [t0] ON LOWER([t].[ObjectId]) = LOWER(CONVERT(VARCHAR(36), [t0].[Id]))
WHERE ([e].[IsDeleted] = 0) AND ([e].[Id] = @__id_1)',N'@__id_1 uniqueidentifier',@__id_1='830D9550-75C0-46F4-5A84-08D7AE2E3249'
exec sp_executesql N'SELECT TOP(1) [e2].[Id], [e2].[CreatedBy], [e2].[CreatedOn], [e2].[DeletedBy], [e2].[DeletedOn], [e2].[DocumentId], [e2].[IsDeleted], [e2].[Label], [e2].[Major], [e2].[Minor], [e2].[Revision], [e2].[Status], [e2].[Type], [e2].[UpdatedBy], [e2].[UpdatedOn]
FROM [Version] AS [e2]
WHERE (([e2].[IsDeleted] = 0) AND ([e2].[Status] = 1)) AND (@_outer_Id = [e2].[DocumentId])
ORDER BY [e2].[CreatedOn] DESC',N'@_outer_Id uniqueidentifier',@_outer_Id='830D9550-75C0-46F4-5A84-08D7AE2E3249'
exec sp_executesql N'SELECT TOP(1) [e3].[Id], [e3].[CreatedBy], [e3].[CreatedOn], [e3].[DeletedBy], [e3].[DeletedOn], [e3].[DocumentId], [e3].[IsDeleted], [e3].[Label], [e3].[Major], [e3].[Minor], [e3].[Revision], [e3].[Status], [e3].[Type], [e3].[UpdatedBy], [e3].[UpdatedOn]
FROM [Version] AS [e3]
WHERE (([e3].[IsDeleted] = 0) AND ([e3].[Status] = 0)) AND (@_outer_Id1 = [e3].[DocumentId])
ORDER BY [e3].[CreatedOn] DESC',N'@_outer_Id1 uniqueidentifier',@_outer_Id1='830D9550-75C0-46F4-5A84-08D7AE2E3249'
exec sp_executesql N'SELECT [e].[Id], [e].[CreatedBy], [e].[CreatedOn], [e].[DeletedBy], [e].[DeletedOn], [e].[DocumentId], [e].[IsDeleted], [e].[Label], [e].[Major], [e].[Minor], [e].[Revision], [e].[Status], [e].[Type], [e].[UpdatedBy], [e].[UpdatedOn]
FROM [Version] AS [e]
WHERE ([e].[IsDeleted] = 0) AND ([e].[DocumentId] = @__get_Item_0)',N'@__get_Item_0 uniqueidentifier',@__get_Item_0='830D9550-75C0-46F4-5A84-08D7AE2E3249'
exec sp_executesql N'SELECT [e].[Id], [e].[BinaryFileId], [e].[CreatedBy], [e].[CreatedOn], [e].[DeletedBy], [e].[DeletedOn], [e].[IsDeleted], [e].[Type], [e].[UpdatedBy], [e].[UpdatedOn], [e].[VersionId]
FROM [Content] AS [e]
WHERE ([e].[IsDeleted] = 0) AND ([e].[VersionId] = @__get_Item_0)',N'@__get_Item_0 uniqueidentifier',@__get_Item_0='97CE469C-2270-4C13-CF2C-08D7AE2E324F'
exec sp_executesql N'SELECT [e].[Id], [e].[CreatedBy], [e].[CreatedOn], [e].[DeletedBy], [e].[DeletedOn], [e].[DocumentId], [e].[IsDeleted], [e].[ObjectId], [e].[ObjectType], [e].[UpdatedBy], [e].[UpdatedOn]
FROM [DocumentRelation] AS [e]
WHERE ([e].[IsDeleted] = 0) AND ([e].[DocumentId] = @__get_Item_0)',N'@__get_Item_0 uniqueidentifier',@__get_Item_0='830D9550-75C0-46F4-5A84-08D7AE2E3249'
SELECT [e].[Id], [e].[CreatedBy], [e].[CreatedOn], [e].[DeletedBy], [e].[DeletedOn], [e].[IsDeleted], [e].[Number], [e].[UpdatedBy], [e].[UpdatedOn]
FROM [DocumentCase] AS [e]
WHERE ([e].[IsDeleted] = 0) AND [e].[Id] IN ('17bb6116-1968-4822-4803-08d7ae212f07')
Further technical details
EF Core version: 3.1.2
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET Core 3.1
Operating system: Windows 10 x64
IDE: Visual Studio 2019 16.4.3