Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problem with EF-Core Code first throwing error because of backing field

I'm having trouble getting my backing fields to work, i've tried my way through using this documentation: https://learn.microsoft.com/en-us/ef/core/modeling/backing-field with no luck.

When i try to add the migration i'm greeted with this error:

The property 'Workflow._step1' is of type 'Step1' which is not supported by current database provider. Either change the property CLR type or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

Multiple workflows can use the same step, so i want it to be saved like this

Workflow 
{
    Id,
    Step1Id
    Step2Id
}

Example that does not work code:

public class Workflow 
{
    private Step1 _step1;
    private Step2 _step2;

    public Guid Id { get; set; } = Guid.NewGuid();

    public bool Step1Enabled => true;
    public Step1 Step1 => Step1Enabled ? _step1 : null;

    public bool Step2Enabled => _step1.Completed;
    public Step2 Step2 => _step2Enabled ? _step2 : null;
}

public class Step1 
{
    public Guid Id { get; set; } = Guid.NewGuid();
    public bool StatusUniqueToStep1 { get; set; }
    public bool Completed {get; set; }
}

public class Step2 
{
    public Guid Id { get; private set; } = Guid.NewGuid();
    public bool StatusUniqueToStep2 { get; set; }
    public bool Completed {get; set; }
}

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) :base(options)
    {}

    public DbSet<Workflow> Workflows { get; set; }
    // Tried adding these, does not work.
    // public DbSet<Step1> Step1 { get; set; }
    // public DbSet<Step2> Step2 { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Tried adding these, does not work.
        // modelBuilder.Entity<Step1>();
        // modelBuilder.Entity<Step2>();

        modelBuilder.Entity<Workflow>()
            .Property<Step1>("_step1");

        modelBuilder.Entity<Workflow>()
            .Property<Step2>("_step2");
    }
}
like image 556
Tangooe Avatar asked Dec 11 '18 11:12

Tangooe


1 Answers

Here is the case. By EF Core terminology these are not properties, but navigation properties, so they cannot be configured with Property fluent API (and in general are not returned by any metadata/entry method having Property / Properties in the name).

Instead, they are configured through relationship related fluent APIs. The problem with mapping the backing field though is that there is no natural fluent API for that similar to "properties", so you have to use directly the metadata.

The configuration could be like this:

modelBuilder.Entity<Workflow>()
    .HasOne(e => e.Step1)
    .WithMany()
    .Metadata.DependentToPrincipal.SetField("_step1");

modelBuilder.Entity<Workflow>()
    .HasOne(e => e.Step2)
    .WithMany()
    .Metadata.DependentToPrincipal.SetField("_step2");

or taking into account that the backing field names follow one of the EF Core naming conventions:

modelBuilder.Entity<Workflow>()
    .HasOne(e => e.Step1)
    .WithMany()
    .Metadata.DependentToPrincipal.SetPropertyAccessMode(PropertyAccessMode.Field);

modelBuilder.Entity<Workflow>()
    .HasOne(e => e.Step2)
    .WithMany()
    .Metadata.DependentToPrincipal.SetPropertyAccessMode(PropertyAccessMode.Field);

But this is also the EF Core default behavior. So the actual problem is not the backing field, but the fact that EF Core by default does not include read only (no setter) properties (simple or navigation, doesn't matter). Hence the minimal configuration to make this work is like this:

modelBuilder.Entity<Workflow>()
    .HasOne(e => e.Step1);

modelBuilder.Entity<Workflow>()
    .HasOne(e => e.Step2);

Update: In order to force EF Core to use the backing field for both set (when reading from database) and get (change tracking, storing to database), use the second configuration - with .Metadata.DependentToPrincipal.SetPropertyAccessMode(PropertyAccessMode.Field).

like image 138
Ivan Stoev Avatar answered Nov 19 '22 06:11

Ivan Stoev