dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.48k stars 3.13k forks source link

EF7 - GroupBy resulting in ArgumentNullException - Value cannot be null. (Parameter 'collection'); #31388

Open TWhidden opened 11 months ago

TWhidden commented 11 months ago

Working on a conversion from Framework EF6 to Core EF7. This code previously was working without error. Converting from net framework over to core, and that comes with moving Entity Framework 6 over to EFCore 7.

I have a keyless View configured:

 modelBuilder.Entity<MySampleView>(entity =>
        {
            entity
                .HasNoKey()
                .ToView("MySampleView");

My query is simple and previously worked fine with EF6 under Framework. Filter, Group, ToDictionary

 var result = await context.MySampleView
     .Where(x => ... SomeConditions)
     .GroupBy(x => new { x.Column1, x.Column2, x.Column3 })
     .ToDictionaryAsync(x => x.Key.Column1,
     x => new { x.Key.Column2, x.Key.Column3, ... });

Note - there will not be a duplicate key here - Column1,2,3 are always the same

However, this will throw an exception that comes from within the EF7 framework.

Value cannot be null. (Parameter 'collection')

Stack trace put this inside the EFCore source:

System.ArgumentNullException
  HResult=0x80004003
  Message=Value cannot be null. (Parameter 'collection')
  Source=System.Private.CoreLib
  StackTrace:
   at System.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument) in /_/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs:line 284

Apologize for the screenshots here, as I can't seem get the full Stack Trace from VS in text format for some reason.

Stack Trace View

Digging through the symbol source, I can see where this is happening, but it shouldn't be happening.

You can see it passing this nullable _preGroupByIdentifier into it. In this case, null. (this is MS Symbol Source) From the stack, this is responsible for the variable MS Ref Source: SelectExpression ref 1

I validated that it should NOT be null, but this is tricky. I am not sure if there is some optimizer issue or just my debugger is lying to me here. the _preGroupByIdentifier should be set to an empty list here, but it's totally skipped as shown here. This is from the MS Symbol Source - Note, for other queries, I have seen it not skip that second breakpoint. What is going on with this? I don't think this error would be happening if this was not skipped over!

Animated GIF revealing _preGroupByIdentifier not being set:

_preGroupByIdentifier not being set inside if statement MS Ref Source: SelectExpression ref 2

As a work around, this works and no errors, but puts the GroupBy/Dictionary into the standard Linq/Entities. This is not the right way to do it, as the projection should be handled by the SQL server + EF, not shaped in memory in the dotnet app.

 var result = (await context.MySampleView
     .Where(x => ... SomeConditions)

     .ToListAsync())  // Running the ToListAsync first - not desirable

     .GroupBy(x => new { x.Column1, x.Column2, x.Column3 })
     .ToDictionaryAsync(x => x.Key.Column1,
     x => new { x.Key.Column2, x.Key.Column3 });

Help me find what I am missing here.

EF Core version: 7 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: .NET 7.0 Operating system: Win11 (dev) IDE: Visual Studio 2022 17.6

TWhidden commented 11 months ago

Confirmed this issue has to do with the Views being KeyLess - You can't run any GroupBy aggregation functions on data from Views that do not have a Key. My work around was to assign a Key to those views, even though they are not really guaranteed a unique RowNumber. Views can have duplicate data based on joins, so I had to ensure the ID was semi-unique, but that isn't optimal. EF6 did not have this requirement.

Can we get EFCore to support GroupBy operations on datasets that do not have Keys?

Work around:

modelBuilder.Entity<MySampleView>(entity =>
        {
            entity.HasKey(e => e.RowNumber);
            entity.ToView("MySampleView");
cmuri commented 7 months ago

We have the same issue with simple tables with primary key setted.

nemesisa commented 7 months ago

Hi guy,

today I migrate to .Net7 today and wondering that this isn't working anymore: var test = conv.T_TEST .Select(val => new { val.ID, ... }) .GroupBy(val => val.ID) .ToDictionary(val => val.Key, val => val.First()); -> I get the same Argument Exception from List.InsertRange

Did I really have to set a ToList() before every GroupBy? Is there a chances to fix this bug?

nzlatev7 commented 5 days ago

In my case, I needed to set the primary key for the table I was using with the following code

 entity.HasKey(e => e.Id);