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.63k stars 3.15k forks source link

Nullable object must have a value thrown for a query with DefaultIfEmpty() #30915

Open Luigi6821 opened 1 year ago

Luigi6821 commented 1 year ago

Hi, I am getting error "Nullable object must have a value" using the following scenario:

var categories = from c in dbContext.Set<AMOS_ADDRESSCATEGORY>().AsNoTracking()
                                 select new
                                 {
                                     Id = c.ADDRESSCATEGORYID,
                                     Name = c.DESCRIPTION
                                 };

var addresses = (from address in dbContext.Set<AMOS_ADDRESS>().AsNoTracking()
                                 join c in categories on address.ADDRESSCATEGORYID equals c.Id into aC
                                 from category in aC.DefaultIfEmpty()
                                 select new
                                 {
                                     Id = address.ADDRESSID,
                                     Category = category
                                 }).ToArray();

The above scenarion works pefectly using EF Standard and works in EF Core if I change to:

var addresses = (from address in dbContext.Set<AMOS_ADDRESS>().AsNoTracking()
                                 **join c in dbContext.Set<AMOS_ADDRESSCATEGORY>().AsNoTracking()** 
                                on address.ADDRESSCATEGORYID equals c.ADDRESSCATEGORYID into aC
                                 from category in aC.DefaultIfEmpty()
                                 select new
                                 {
                                     Id = address.ADDRESSID,
                                     Category = category
                                 }).ToArray();

Can you help me please? Thanks in advance Luigi

EF Core version: 8.0.0-preview.4.23259.3 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: NET 6.0 Operating system: IDE: Visual Studio 2022 17.4

ajcvickers commented 1 year ago

This issue is lacking enough information for us to be able to fully understand what is happening. Please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

Luigi6821 commented 1 year ago

Hi @ajcvickers Please find attached a project for reproducing issue.

ReproducibleNullObject.zip

ajcvickers commented 1 year ago

Note for triage: we don't handle assigning null to the anonymous type after calling DefaultIfEmpty, but it seems like this should work, and it works in LINQ-to-Objects.

Luigi6821 commented 1 year ago

Hi @ajcvickers I believe it has same issue with non anonymous type. Example:

var categories = from c in dbContext.Set<AMOS_ADDRESSCATEGORY>().AsNoTracking()
                         select new Category
                         {
                             Id = c.ADDRESSCATEGORYID,
                             Name = c.DESCRIPTION
                         };

 var addresses = (from address in dbContext.Set<AMOS_ADDRESS>().AsNoTracking()
                                 join c in categories
                                on address.ADDRESSCATEGORYID equals c.Id into aC
                                 from category in aC.DefaultIfEmpty()
                                 select new
                                 {
                                     Id = address.ADDRESSID,
                                     Category = category
                                 }).ToArray();
Luigi6821 commented 9 months ago

Hi, Has been solved in version 8 ? I tested and seems still present.. Thanks in advance

ajcvickers commented 9 months ago

This issue is in the Backlog milestone. This means that it is not fixed in EF Core 8.0. We will re-assess the backlog and consider this item in the upcoming planning. However, keep in mind that there are many other high priority features with which it will be competing for resources. Make sure to vote (👍) for this issue if it is important to you.

Luigi6821 commented 9 months ago

Hi, thanks for prompt reply. I believe that this issue should be with high priority because fails on a simple left join operation. Anyway thanks for suggestion

UliPlabst commented 8 months ago

I have the same exception using a simple group by statement, I think the root cause might be the same. You can run this in roslyn pad to reproduce.

#r "nuget: Microsoft.EntityFrameworkCore.Sqlite,8.0.0"
#r "nuget: Microsoft.EntityFrameworkCore,8.0.0"

using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

await using var ctx = new Context();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();

ctx.Steps.Add(new Step
{
    Id = 5
});
await ctx.SaveChangesAsync();

await ctx.Steps
    .GroupBy(e => new
    {
        e.Planned
    }).ToListAsync();

public class Context : DbContext
{
    public DbSet<Step> Steps { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlite(Environment.ExpandEnvironmentVariables("DataSource=%USERPROFILE%/Desktop/test_db.sqlite"))
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();
}

public class Step
{
    [Key]
    public int Id { get; set; }
    public DateTime? Planned { get; set; }
}
Luigi6821 commented 6 months ago

Hi, any news on the above? Thanks in advance

roji commented 6 months ago

This issue is in the Backlog milestone. This means that it is not planned for the next release (EF Core 8.0). We will re-assess the backlog following the this release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources. Make sure to vote (👍) for this issue if it is important to you.

Luigi6821 commented 6 months ago

I will "vote" it for sure but let me say that this issue is really a bug for anyone want to outer join tables.

Regards Luigi

adrius commented 6 months ago

We're also experienced the issue on a piece of code very similar to @Luigi6821 , I'll keep an eye on this issue. Can anybody explain how can we vote this one up?

geraparra commented 6 months ago

Same issue here, how can we vote?

roji commented 6 months ago

@geraparra vote (thumbs up 👍) in the top comment of the issue.

Luigi6821 commented 2 months ago

Hi, I am testing version 9 preview 9.0.0-preview.3.24172.4 and still the error is present. Honestly I am buffled on how this "pillar" functionality is not working and how any other can use EF core without having it fixed.

roji commented 2 months ago

Yes, this issue is still in the backlog milestone - we haven't yet fixed it. Ultimately, it has only 10 upvotes, meaning that very few users run into it, and therefore it's not at the top of our priority list.

Luigi6821 commented 2 months ago

Yes, this issue is still in the backlog milestone - we haven't yet fixed it. Ultimately, it has only 10 upvotes, meaning that very few users run into it, and therefore it's not at the top of our priority list.

I can't believe in users that do not use outer JOIN. There is some workaround?

shaofing commented 1 month ago

This issue has been present since EF Core 5+until now

This is a breaking change when upgrading from EFCore 3.1 to EFCore 5, but it is not mentioned in the Microsoft documentation, causing widespread errors in our production environment.

@ajcvickers @roji Can you fix it ?

shaofing commented 1 month ago
        static void Main(string[] args)
        {
            //Table AppointmentOrder only has 1 record, while table AppointmentDetail has no data.
            var connectionString = $"Data Source=(localdb)\\ProjectModels;Initial Catalog=eftest;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False";
            var builder = new DbContextOptionsBuilder<SqliteContext>();
            builder.UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()));
            builder.UseSqlServer(connectionString);
            var dbContext = new SqliteContext(builder.Options);
            var query = from a in dbContext.AppointmentOrder
                         join cc in dbContext.AppointmentDetail on a.AppointmentId equals cc.AppId into ccQuery
                         from c in ccQuery.DefaultIfEmpty()
                         select new
                         {
                             a.AppointmentId,
                             c.DetailCount
                         };
            var list = query.ToList();
            Console.ReadLine();
        }

    public class AppointmentOrder
    {
        public int AppointmentId { get; set; }
        public string AppointmentNo { get; set; }
    }
    public class AppointmentDetail
    {
        public int AppId { get; set; }
        public int DetailCount { get; set; }
    }

image

shaofing commented 1 month ago

I am using ((int?)c.DetailCount).GetValueOrDefault() instead of it

But we need to make changes one by one

image