EntityFramework.Docs
EntityFramework.Docs copied to clipboard
FirstOrDefault() returning null in .NET 6 but empty object in .NET Core 3
We are in the process of migrating our app from .NET Core 3.1.28 to .NET 6.0.8
So far the transition has been pretty painless, but we've found an annoying case where we get an InvalidOperationException
when using FirstOrDefault() and immediately calling a value on the result.
In .NET Core 3, FirstOrDefault() returned an empty object initialized with default values, and the query worked fine
In .NET 6, the same query now returns null
and the query logically crashes
Here is a (very) simplified repro of our use case. There are of course workarounds, but I'd like to know if this change was intended or if it is a regression.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace FirstOrDefaultBehavior
{
class Program
{
static void Main(string[] args)
{
using (var context = new MyContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
context.Item.Add(new Item() { Code = 1 });
context.Item.Add(new Item() { Code = 2 });
context.SaveChanges();
context.StockMovement.Add(new StockMovement() { ItemFk = context.Item.First().Id });
context.SaveChanges();
// Always works since Item 1 has attached StockMovement
var ok = context.Item
.Where(i => i.Code == 1)
.Select(i => new
{
i.Id,
i.Code,
SomeStockValue = i.StockMovement.FirstOrDefault().ItemFk
})
.ToList();
// Crashes in .Net 6, worked fine in .Net Core 3.1
var crash = context.Item
.Where(i => i.Code == 2)
.Select(i => new
{
i.Id,
i.Code,
SomeStockValue = i.StockMovement.FirstOrDefault().ItemFk
})
.ToList();
}
}
}
public class MyContext : DbContext
{
public virtual DbSet<Item> Item { get; set; }
public virtual DbSet<StockMovement> StockMovement { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connectionString = "Server=.;Database=FirstOrDefaultBehavior;Trusted_Connection=True;MultipleActiveResultSets=true";
optionsBuilder.UseSqlServer(connectionString)
.EnableSensitiveDataLogging()
.UseLoggerFactory(new LoggerFactory());
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Item>(entity =>
{
entity.HasIndex(e => e.Code, "UK_Item_Code")
.IsUnique();
entity.Property(e => e.Id).HasColumnName("id");
});
modelBuilder.Entity<StockMovement>(entity =>
{
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.ItemFk).HasColumnName("item_fk");
entity.HasOne(d => d.ItemFkNavigation)
.WithMany(p => p.StockMovement)
.HasForeignKey(d => d.ItemFk)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_StockMovement_Item");
});
}
}
public partial class Item
{
public Item()
{
StockMovement = new HashSet<StockMovement>();
}
public int Id { get; set; }
public int Code { get; set; }
public virtual ICollection<StockMovement> StockMovement { get; set; }
}
public partial class StockMovement
{
public StockMovement()
{
InverseParentFkNavigation = new HashSet<StockMovement>();
}
public int Id { get; set; }
public int ItemFk { get; set; }
public virtual ICollection<StockMovement> InverseParentFkNavigation { get; set; }
public virtual Item ItemFkNavigation { get; set; }
}
}
Result in .NET 6 :
Unhandled exception. System.InvalidOperationException: Nullable object must have a value.
at lambda_method7(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at FirstOrDefaultBehavior.Program.Main(String[] args) in C:\Users\FirstOrDefaultBehavior\Program.cs:line 37
Result in .NET Core 3 :
No crash, the value of SomeStockValue
in case 2 is 0
Include provider and version information
EF Core version: 6.0.8 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: .NET 6.0.8 Operating system: Windows 10 x64 IDE: Visual Studio 2022
The 6.0 behavior you're describing is the correct one, in the sense that it corresponds to the LINQ to Objects behavior for FirstOrDefault, which returns null for reference types. So while I'm not seeing the breaking change in our release notes, this would be a bug fix rather than a regression or a bug.
The 6.0 behavior you're describing is the correct one, in the sense that it corresponds to the LINQ to Objects behavior for FirstOrDefault, which returns null for reference types. So while I'm not seeing the breaking change in our release notes, this would be a bug fix rather than a regression or a bug.
Thank you for the answer ! I figured the 6.0 behavior was expected, but as you've mentioned I didn't find a trace in the breaking changes of either 5.0 or 6.0, hence this issue
When you say "this would be a bug fix" I understand that the new behavior "fixed" the previous one and no further change should be expected, correct ? As in, FirstOrDefault will always return null for reference types from now on ?
@fschlaef yeah, that's right. We'll check and add a breaking change note if necessary.
https://github.com/dotnet/efcore/commit/3ebfde7e51aa08692ed1278232e3f1d4beb779be