20
votes

I ran into an interesting problem when I tried to use Entity Framework Core with the new nullable reference types in C# 8.0.

The Entity Framework (various flavors) allows me to declare DBSet properties that I never initalize. For example:

    public class ApplicationDbContext : IdentityDbContext
      {
    #pragma warning disable nullable
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
          : base(options)
        { }
    #pragma warning restore nullable

        public DbSet<Probe> Probes { get; set; }
        public DbSet<ProbeUnitTest> ProbeUnitTests { get; set; }
    }

The DbContext constructor reflects over the type and initializes all of the DbSet properties, so I know that all the properties will be non-null by the conclusion of the constructor. If I omit the #pragma's i get the expected warnings because my code does not initialize these properties.

 Data\ApplicationDbContext.cs(10,12,10,32): warning CS8618: Non-nullable property 'Probes' is uninitialized.
 Data\ApplicationDbContext.cs(10,12,10,32): warning CS8618: Non-nullable property 'ProbeUnitTests' is uninitialized.

Turning off the warnings seems like a blunt instrument when all I want to do is inform the compiler that a property will not be null?

If turns out I can fool the compiler like this:

     public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
      : base(options)
    {
      Probes = Probes;
      ProbeUnitTests = ProbeUnitTests;
    }

This code has the advantage that it is very narrow -- it only applies to the specific property's initialization and will not suppress other warnings. The disadvantage is that this is nonsense code because assigning a property to itself really ought to do nothing.

Is there a preferred idiom for informing the compiler that it just does not know that the property has been initialized?

3
"The disadvantage is that this is nonsense code because assigning a property to itself really ought to do nothing." With 95% chance the JiT will just detect it is dead code and cut it out. So at least performance is not a thing you have to worry about. - Christopher
Isn't the Nullable Reference type to 100% about this Compiler Warning? Unlike nullable value types, it does not seem to "do" anything besides allowing the compiler to warn you (wich is pretty usefull). So the obvious solution would be to not tell the compiler "warn me if I forget to assign it." - Christopher
The solution you came up with is rather fascinating. I can actually sort of understand the logic the compiler must have used to determine that doing Probes = Probes should be okay (you're assigning Probes with something that's non-nullable: Probes), but we know that it's actually not okay, at this point Probes is still null, so you've probably actually found a bug in the compiler's nullable analysis. - Dave M

3 Answers

33
votes

Whenever you want to tell the compiler "shut up, I know what I'm doing" in regards to nullable reference types, use the ! operator. You can fix your issue by declaring your properties like so:

public DbSet<Probe> Probes { get; set; } = null!;
public DbSet<ProbeUnitTest> ProbeUnitTests { get; set; } = null!;
3
votes

Microsoft has 2 recommendations for nullable references in the context of DbContext and DbSet.

The common practice of having uninitialized DbSet properties on context types is also problematic, as the compiler will now emit warnings for them. This can be fixed as follows:

    // ...
    public DbSet<Customer> Customers => Set<Customer>();
    public DbSet<Order> Orders => Set<Order>();
    // ...

And the other is what @dave-m answered:

Another strategy is to use non-nullable auto-properties, but to initialize them to null, using the null-forgiving operator (!) to silence the compiler warning.

0
votes

If you are using .NET 5, you can now use the MemberNotNullAttribute to indicate which members will be not null after the method returns.

public class ApplicationDbContext : IdentityDbContext
{
    [MemberNotNull(nameof(Probes))]
    [MemberNotNull(nameof(ProbeUnitTests))]
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
      : base(options)
    { }

    public DbSet<Probe> Probes { get; set; }
    public DbSet<ProbeUnitTest> ProbeUnitTests { get; set; }
}