Getting started with NRT exposes confusing behaviour.
See original GitHub issueI’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:
- to use
{ get; set; } = new DbSet<Named>()
(or now possibly target typednew()
). 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. - 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
Issue Analytics
- State:
- Created 2 years ago
- Comments:6 (4 by maintainers)
@Wraith2 Personally, I’m a fan of
The
DbSet
object is cached anyway, so this is functionally the same as initializing all the sets in the constructor. I don’t think we would ever have done the auto-initialization stuff had this syntax been available in 2010.I know this is probably obvious but sometimes simple things can get obscured:
DbSet<T>
properties aren’t required and you can always just useSet<T>()
. I’m not trying to convince anyone not to use the convenience properties, just wanted to say that among all the options there are around getting them to play nicely with NRT, one option is not to have them at all.