Efcore: ThenInclude not working in join since 2.1.1

Created on 16 Jul 2018  路  8Comments  路  Source: dotnet/efcore

The following statement was added about a year ago and worked till update 2.1.1:

c# var data = await (from c in db.Checks join ct in db.CheckTemplates .Include(a => a.Implementers).ThenInclude(u => u.User) .Include(o => o.ObservationLine) on c.CheckTemplateId equals ct.CheckTemplateId where c.CheckId == checkId select new { CheckId = c.CheckId, Abbreviation = ct.Abbreviation, Label = ct.Label, Deadline = c.Deadline, Started = c.Start, Description = ct.Description, Implementers = ct.Implementers, NumExaminers = ct.Examiners.Count }).SingleAsync();
Now (EF Core 2.1.1) the ThenInclude(u => u.User) does not include the User class anymore! So for example data.Implementers.First().User.LastName leads to a null reference exception now.

The above statement leads to the following sql-code:

Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (1ms) [Parameters=[@__checkId_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [c].[CheckId], [ct].[Abbreviation], [ct].[Label], [c].[Deadline], [c].[Start] AS [Started], [ct].[Description], (
SELECT COUNT(*)
FROM [CheckTemplateExaminers] AS [c0]
WHERE [ct].[CheckTemplateId] = [c0].[CheckTemplateId]
) AS [NumExaminers], [ct.ObservationLine].[EmailFromAdress], [ct.ObservationLine].[EmailFromDisplayName], [ct].[CheckTemplateId]
FROM [Checks] AS [c]
INNER JOIN [CheckTemplates] AS [ct] ON [c].[CheckTemplateId] = [ct].[CheckTemplateId]
LEFT JOIN [ObservationLines] AS [ct.ObservationLine] ON [ct].[ObservationLineId] = [ct.ObservationLine].[ObservationLineId]
WHERE [c].[CheckId] = @__checkId_0
ORDER BY [ct].[CheckTemplateId]
Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (11ms) [Parameters=[@__checkId_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT [ct.Implementers].[CheckTemplateId], [ct.Implementers].[UserId], [t].[CheckTemplateId]
FROM [CheckTemplateImplementers] AS [ct.Implementers]
INNER JOIN (
SELECT TOP(1) [ct0].[CheckTemplateId]
FROM [Checks] AS [c1]
INNER JOIN [CheckTemplates] AS [ct0] ON [c1].[CheckTemplateId] = [ct0].[CheckTemplateId]
LEFT JOIN [ObservationLines] AS [ct.ObservationLine0] ON [ct0].[ObservationLineId] = [ct.ObservationLine0].[ObservationLineId]
WHERE [c1].[CheckId] = @__checkId_0
ORDER BY [ct0].[CheckTemplateId]
) AS [t] ON [ct.Implementers].[CheckTemplateId] = [t].[CheckTemplateId]
ORDER BY [t].[CheckTemplateId]

This is a severe issue I think (for me anyway) because this has already worked and is used widely in my app.

Further technical details

EF Core version: 2.1.1
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Win10 x64
IDE: Visual Studio 2017 15.7.4

closed-duplicate customer-reported regression type-bug

Most helpful comment

@smitpatel thanks for directing me to #12181!

I am sorry to say but it is really annoying that with every new version some of the statements that already worked for some time are not working after the update.

Changing a behaviour like this that is already used in production scenarios should be done much more wisely! Like with some kind of deprecated message in the log or something that way.

After searching the solution for that kind of statements I have to investigate more than 160 statements if they are affected by the change!

All 8 comments

@ManuelHaas thanks for reporting this. Could you provide a more complete repro?

@maumar could you please investigate? This is a possible regression in 2.1.1.

Duplicate of #12181

That dup bug was closed by-design. So we don't need to patch anything?

@smitpatel thanks for directing me to #12181!

I am sorry to say but it is really annoying that with every new version some of the statements that already worked for some time are not working after the update.

Changing a behaviour like this that is already used in production scenarios should be done much more wisely! Like with some kind of deprecated message in the log or something that way.

After searching the solution for that kind of statements I have to investigate more than 160 statements if they are affected by the change!

@maumar will be working on providing an easier workaround for this issue.

@ManuelHaas you can try the following workaround:

var checkId = 1;

var data = (from c in db.Checks
             join ct in db.CheckTemplates
                  .Include(a => a.Implementers).ThenInclude(u => u.User)
             on c.CheckTemplateId equals ct.CheckTemplateId
             where c.CheckId == checkId
             select new
             {
                 CheckId = c.CheckId,
                 Description = ct.Description,
                 Implementers = ProjectImplementers(ct),
                 NumExaminers = ct.Examiners.Count
             }).FirstOrDefault();

        public static List<Implementer> ProjectImplementers(CheckTemplate checkTemplate) => checkTemplate.Implementers;

This mimics what EFCore was doing under the covers before 2.1.

@maumar thank you for providing a workaround!

Once EF 2.2 is shipped, you should be able to use easier workaround:

        public static T Client<T>(T s) => s;

and in the your query's projection simply use:

             select new
             {
                 CheckId = c.CheckId,
                 Description = ct.Description,
                 Implementers = Client(ct).Implementers,
                 NumExaminers = ct.Examiners.Count

but for now the navigation access needs to be "hidden" from EF behind the custom method call.

Was this page helpful?
0 / 5 - 0 ratings