-2

My understanding of C#'s null safety features (from C# 8.0 onwards) is that (possibly) null variables must be typed as nullable and that the compiler will statically detect any parts of the code which may involve a null reference dereference and issue a warning. Coupled with <WarningsAsErrors>Nullable</WarningsAsErrors>, I would expect this to make it impossible for null reference exceptions to occur.

However, the following code compiles but when a Get request is sent to /endpoint, it returns a null reference exception:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connString = "User ID=postgres;Password=password;Host=localhost;Port=5432;Pooling=true;Connection Lifetime=0;Database=MinExample";
builder.Services.AddDbContext<AppDbContext>(options => options.UseNpgsql(connString));
var app = builder.Build();


app.MapGet("/endpoint", long (AppDbContext context) =>
{
    var id = new Service(context).GetBId();
    return id;
});

app.Run();

public record A
{
    public long AId { get; init; }
    public required B B { get; set; }
}

public class B
{
    public required long BId { get; init; }
}

public class Service
{
    private readonly AppDbContext _context;
    public Service(AppDbContext cx)
    {
        _context = cx;
    }

    public long GetBId()
    {
        var a = _context.A.First();
        return a.B.BId;
    }
}

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) 
        : base(options) { }
    public DbSet<A> A { get; set; }

}

My database has a single entry in the A table and a single related entry in the B table.

In fact, the compiler is so confident that id is not null that if I insert Console.WriteLine(id is null) just before the return statement of the API, the compiler fails with the error Cannot convert null to 'long' because it is a non-nullable value type. On the other hand, Console.WriteLine(id == null) compiles and runs fine (and writes True to the console) although JetBrains warns me that The result of the expression is always 'false' because a value of type 'long' is never equal to 'null' of type 'long?'

Why does the compiler not give me an error that the variable I am returning is null? Is this a null-safety violation? What am I missing about how null-safety works in C# 8.0 and up?

(The "solution" to this problem is to replace the query with var a = _context.A.Include(a => a.B).First();, but my question is about why the compiler does not detect that something is wrong, not about how to fix the problem itself).

All of the above has been build with dotnet version 9.0.112.

5
  • These language rules are only implemented in the compiler. There is no guarantee at runtime. Particularly for anything that uses reflection, like serialising an instance of a type from an external source. Though you can tell EF Core that a navigation should always be included learn.microsoft.com/en-us/ef/core/querying/related-data/… Commented 2 hours ago
  • IMHO most navigation properties should be nullable. Not because the relationship isn't required. But because you only want to load a subset of the database into memory. And perhaps EF Core should raise a diagnostic if a navigation is required, but not auto-included. Commented 2 hours ago
  • @JeremyLakeman I'm not sure I understand what you mean by " These language rules are only implemented in the compiler". My understanding was that whenever the compiler could not guarantee that a dereference wasn't of a null variable, it would issue a warning. Are you saying that's not the case? Commented 1 hour ago
  • learn.microsoft.com/en-us/dotnet/csharp/nullable-references "Nullable reference types are a compile time feature. That means it's possible for callers to ignore warnings...". There are no runtime tests to enforce that a field has been assigned. And EF Core is all about creating instances of your types at runtime. EF Core does not know that much about C#. It only complies with the rules of the dotnet runtime, and what extra metadata you have defined in your model. Commented 1 hour ago
  • Specifically learn.microsoft.com/en-us/ef/core/miscellaneous/… Though perhaps that documentation should also reference .AutoInclude(). Commented 1 hour ago

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.