System.InvalidOperationException: Nullable object must have a value. — an EF
Core Max() over an int column ran on a filter that matched no
rows, and the empty result could not become an int. Cast the selector to
int? (Max(c => (int?)c.Id)) and check the result for
null.
The message does not mention Max, the table or the filter, so it is easy to
look in the wrong place. The trace in the reproduction ended in
Queryable.Max, which is the clue.
The error
PS> dotnet run -- max-bad
...
Unhandled exception. System.InvalidOperationException: Nullable object must have a value.
at lambda_method42(Closure, QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
...
at System.Linq.Queryable.Max[TSource,TResult](IQueryable`1 source, Expression`1 selector)
at Program.<Main>$(String[] args) in ...\EfLab\Program.cs:line 100
The cuts hide NuGet audit warnings printed by dotnet run and EF Core frames.
The top frame takes a DbDataReader: the exception was raised while EF Core
read the query result.
Why it happens
int maxId = db.Customers.Where(c => c.City == "Oslo").Max(c => c.Id);
The table held two customers, both in Lima, so no row matched City == "Oslo".
With no rows there is no maximum, and the code asked for a plain int, which
has no way to say "nothing". The trace shows the exception raised inside the enumerator
that reads the query's result, called from Queryable.Max at line 100, the statement that assigns the
result of Max() to int maxId.
The fix
int? maxId = db.Customers.Where(c => c.City == "Oslo").Max(c => (int?)c.Id);
Console.WriteLine(maxId is null ? "No customers in Oslo" : maxId);
PS> dotnet run -- max-fix
...
No customers in Oslo
With the selector cast to int?, Max() returned
null for the empty filter and the program printed the message instead of
crashing. The cast sits inside the lambda, on c.Id, which turns the
whole Max() call into one that returns int?; the variable type
changed to match.
How it was reproduced
A console project from dotnet new console -n EfLab on .NET SDK 10.0.403 with
Microsoft.EntityFrameworkCore.Sqlite 10.0.14 and a SQLite file shop.db, seeded
with two fictional customers in Lima. dotnet run -- max-bad took the maximum
Id of customers in Oslo into an int and threw;
dotnet run -- max-fix ran the int? version. Both runs started by
deleting and recreating shop.db and seeding the same two rows, so the Oslo
filter was empty both times and only the selector differed.
Frequently asked
- Why does EF Core Max throw Nullable object must have a value?
- The filter matched no rows, so there was no maximum, and the result was read into a non-nullable int. In the reproduction Max(c => c.Id) over customers in Oslo threw it.
- How do I get Max on an empty table without an exception in EF Core?
- Cast the selector to a nullable type, for example Max(c => (int?)c.Id), and check the result for null. In the reproduction that returned null and printed a message instead of throwing.
- Where does Nullable object must have a value come from in the stack trace?
- In the reproduction the top frame was a compiled EF Core lambda that takes a DbDataReader, and the trace ended in System.Linq.Queryable.Max called from Program.cs.
More decoded errors in the Fixes category. A related
empty-result error from First() is covered in
Sequence contains no elements.