Skip to content

Getting started with NRT exposes confusing behaviour. #26859

@Wraith2

Description

@Wraith2

I'm learning to use EF rather than using direct ADO so I've started a new NET6 project with EFCore and am using. I'm working through getting the basic things I need but I'm finding that there are some confusing things for beginners that could be improved on.

I've started with a very basic context and added a set property. NRT are enabled and I want the property to be non-null but if I don't assign to the property in the ctor i'll get a complier warning in the ctor and as a good developer I want to ensure my code builds without warning or suppressions. So how do I make it work?
The usual way to do this would simply be to set it in the ctor Entities = null!;, this is wrong. To know that is wrong you have to know that EF is going to set the property in the parameterless ctor so in the second ctor the property is already initialized. If you're not aware of this implementation detail the null assignment will break the property.

I asked about this on the c# discord and Reacher there patiently explained to me and provided the accepted patterns to work around it, those being:

  1. to use { get; set; } = new DbSet<Named>() (or now possibly target typed new()). This feels a little strange that I need to setup the collection myself and could get unwieldy for complex or immutable collection types. It's also not what the docs tell you do do, they leave it as a pure auto property without initialization.
  2. to use { get; init; } = null!. This just looks wrong because i'm creating an init only property and setting it to null in order to explain the the compiler that the property will never be null. All the code I can see tells me it will be null, at runtime it will not be. I didn't see this pattern in the docs, it's probably there but I haven't hit the page that explains it yet.

I've worked through the docs and pretty early fallen into a pit (well, a pothole) of failure. There might be something that can be improved on here.

using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using System.Diagnostics;

using (var context = new DatabaseContext())
{
	context.Database.EnsureCreated();
	Debug.Assert(context.Entities != null);
}

public class DatabaseContext : DbContext
{
	public DatabaseContext()
		: this("datbase.db")
	{
	}
	public DatabaseContext(string databaseFilePath)
	{
		DatabaseFilePath = databaseFilePath ?? throw new ArgumentNullException(databaseFilePath);
		// Entities = null!; // uncomment this assignment for failure
	}

	public string DatabaseFilePath { get; init; }

	public DbSet<Named> Entities { get; set; }

	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<Named>().HasKey(nameof(Named.Name));
	}

	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
	{
		SqliteConnectionStringBuilder connectionBuilder = new SqliteConnectionStringBuilder();
		connectionBuilder.DataSource = DatabaseFilePath;
		connectionBuilder.Mode = SqliteOpenMode.ReadWriteCreate;
		optionsBuilder.UseSqlite(connectionBuilder.ConnectionString);
	}
}

public class Named
{
	public string? Name { get; set; }
}

Include provider and version information

EF Core version: 6.0
Database provider:Microsoft.EntityFrameworkCore.SqlLite
Target framework: NET 6.0
Operating system:
IDE: vs 2022

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions