Skip to content

The LINQ expression could not be translated. #20175

Closed as not planned
Closed as not planned
@cosmin-ciuc

Description

@cosmin-ciuc

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions