EF Core 5.0 with Cosmos - New properties and backing fields

Viewed 480

I'm using EF Core 5.0 and Cosmos, and I'm trying to work out how to handle the situation where a new property might get added to a model, but older documents don't have this new property. For example, if the original model looks like:

{
   public int Id { get; set; }
   public string Title { get; set; }
}

... and then some time later a new property is added ...

{
   public int Id { get; set; }
   public string Title { get; set; }
   public bool IsEnabled { get; set; }
}

... I then run into problems when I try to retrieve a document that only has the original properties (I get a 'non-nullable field must have a value' error). If I change to data type to bool?, it works ... which is understandable. But I'm trying to avoid using nullable types because of the extra code I would then need to add to check for nulls, etc.

I've tried using 'Backing Fields' to get this to work, like so:

{
   public int Id { get; set; }
   public string Title { get; set; }
   
   private bool? _isEnabled { get; set; }
   public bool IsEnabled { 
      get => _isEnabled ?? false; 
      set => _isEnabled = value; 
   }
}

... and then using the Fluent API in the model builder as follows:

   modelBuilder.Entity<MyEntity>()
      .Property(m => m.IsEnabled)
      .HasField("_isEnabled")
      .UsePropertyAccessMode(PropertyAccessMode.Field);

But this generates the following error for me:

System.InvalidOperationException: The specified field '_isEnabled' could not be found for property 'MyEntity.IsEnabled'

What am I doing wrong? Is this never going to work because the property IsEnabled doesn't exist in the document?

Update: The question is really a question of how to handle a 'schema' change with EF Core and Cosmos. I know NoSql is 'schema-less' but the reality is that data models change over time and since EF Core migrations don't work with Cosmos, what is the best way to handle this?

3 Answers

I think, the field _isEnabled must be public.

The documentation mentions, if you chose PropertyAccessMode.Field, an exception will be thrown if it's not possible to read from or write to the field.

What you did above should work fine. I do something similar in certain scenarios and it works fine. (EF 6)

EF6 can even infer the backing field so your binding need only be

modelBuilder.Entity<MyEntity>().Property(m => m.IsEnabled)

Its only when the backing field can not be inferred do you need to manually define the backing field (my guess is this is something they fixed in EF6)

When IsEnabled field is not present in a document, why are you populating it with false values?

Consider a situation in which a document has a field with actual IsEnabled = false and a document which is artificially having IsEnabled = false, how will you be able to differentiate between them? Add another field?

The same questions will be applicable to other properties with different data types.

This approach looks wrong to me. Rather than this, if you really don't want to use nullable properties, you can use versioning for your models.

Related