SapientGuardian.EntityFrameworkCore.MySql icon indicating copy to clipboard operation
SapientGuardian.EntityFrameworkCore.MySql copied to clipboard

The entity type is part of a hierarchy, but with no discriminator property configured

Open ts95 opened this issue 7 years ago • 10 comments

I tried to migrate from using SQLite to MySQL for my project, and I keep getting this error: System.InvalidOperationException: The entity type 'Loan' is part of a hierarchy, but does not have a discriminator property configured.

My Loan model looks like this:

public class Loan
{
    [Key]
    public int LoanId { get; set; }

    public int EquipmentId { get; set; }

    public virtual Loaner Loaner { get; set; }

    [Required]
    public virtual Equipment Equipment { get; set; }

    [Required]
    public DateTime StartDate;

    public DateTime EndDate;

    public bool Applicable => this.EndDate < DateTime.Now;
}

Adding a Discriminator property did not change anything.

I have a different model that inherits from the Loan model:

public class Maintenance : Loan
{
    public string Note { get; set; }
}

What could be causing this problem and how can I solve it?

ts95 avatar Mar 05 '17 09:03 ts95

I found a solution. Apparently, this provider doesn't automatically do registrations for derived classes, so you need to do them manually by overriding the #OnModelCreating() method in your DbContext-subclass.

Is there a particular reason as to why I need to do this? Shouldn't the provider do this automatically?

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

    public DbSet<User> Users { get; set; }
    public DbSet<Equipment> Equipments { get; set; }
    public DbSet<Loan> Loans { get; set; }
    public DbSet<Maintenance> Maintenances { get; set; }
    public DbSet<Project> Projects { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        // Manually configure discriminators since the
        // MySQL provider doesn't do it implicitly.

        builder
            .Entity<Loan>()
            .HasDiscriminator<string>("Discriminator")
            .HasValue<Loan>(nameof(Loan))
            .HasValue<Maintenance>(nameof(Maintenance));
        
        builder
            .Entity<Loaner>()
            .HasDiscriminator<string>("Discriminator")
            .HasValue<Loaner>(nameof(Loaner))
            .HasValue<User>(nameof(User))
            .HasValue<Project>(nameof(Project));
    }
}

ts95 avatar Mar 05 '17 09:03 ts95

I'm not sure. Do you know of other providers that don't behave this way?

SapientGuardian avatar Mar 05 '17 13:03 SapientGuardian

The SQLite provider didn't behave this way. I haven't tested the other ones.

ts95 avatar Mar 05 '17 13:03 ts95

I took a quick search through the SQLite provider code, as well as through npgsql, and didn't see anything obvious around discriminators. What is the name of the column that SQLite creates as your discriminator column?

SapientGuardian avatar Mar 05 '17 13:03 SapientGuardian

The name of the column is simply Discriminator. It was created automatically when I added my initial migration.

This is the generated migration code for the Loan model:

migrationBuilder.CreateTable(
    name: "Loaner",
    columns: table => new
    {
        LoanerId = table.Column<int>(nullable: false)
            .Annotation("MySQL:AutoIncrement", true),
        Discriminator = table.Column<string>(nullable: false),
        PName = table.Column<string>(nullable: true),
        Email = table.Column<string>(nullable: true),
        FirstName = table.Column<string>(nullable: true),
        LastName = table.Column<string>(nullable: true),
        Password = table.Column<string>(nullable: true),
        Role = table.Column<string>(nullable: true)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Loaner", x => x.LoanerId);
    });

Discriminator = table.Column<string>(nullable: false) was added automatically with the SQLite provider, but with the MySQL provider it wasn't.

ts95 avatar Mar 05 '17 13:03 ts95

Ok, that helped a lot in finding where this happens. I believe this behavior to be part of the RelationalConventionSetBuilder, which SqlServer, Sqlite, and Npgsql are all using, but we are not. It seems like something we should use, but I'm a little worried about it causing uenxpected behavior with current systems. I'll try wiring it up, and will bump this library by a minor version, rather than a revision.

SapientGuardian avatar Mar 05 '17 13:03 SapientGuardian

@ts95 Can you give this version a try and see if it behaves as you expected? https://ci.appveyor.com/api/buildjobs/nycxtw9od923rciu/artifacts/artifacts%2FSapientGuardian.EntityFrameworkCore.MySql%2FSapientGuardian.EntityFrameworkCore.MySql.7.2.0.nupkg

SapientGuardian avatar Mar 05 '17 14:03 SapientGuardian

Restore output

log  : Restoring packages for dokflyt-utstyr/project.json...
log  : Installing SapientGuardian.EntityFrameworkCore.MySql 7.2.0.
log  : Restoring packages for tool 'Microsoft.AspNetCore.Razor.Tools' in dokflyt-utstyr/project.json...
log  : Restoring packages for tool 'Microsoft.AspNetCore.Server.IISIntegration.Tools' in dokflyt-utstyr/project.json...
log  : Restoring packages for tool 'Microsoft.DotNet.Watcher.Tools' in dokflyt-utstyr/project.json...
log  : Restoring packages for tool 'Microsoft.EntityFrameworkCore.Tools.DotNet' in dokflyt-utstyr/project.json...
log  : Writing lock file to disk. Path: dokflyt-utstyr/project.lock.json
log  : dokflyt-utstyr/project.json
log  : Restore completed in 8502ms.
Done: 0.

I still get the same error as before.

System.InvalidOperationException: The entity type 'Loan' is part of a hierarchy, but does not have a discriminator prope
rty configured.
   at Microsoft.EntityFrameworkCore.Internal.ModelValidator.ShowError(String message)
   at Microsoft.EntityFrameworkCore.Internal.RelationalModelValidator.ValidateDiscriminator(IEntityType entityType)
   at Microsoft.EntityFrameworkCore.Internal.RelationalModelValidator.ValidateDiscriminatorValues(IEntityType rootEntity
Type) Closing connection to database 'DokflytUtstyr' on server '127.0.0.1'.
   at Microsoft.EntityFrameworkCore.Internal.RelationalModelValidator.ValidateInheritanceMapping(IModel model)
 ...

ts95 avatar Mar 05 '17 14:03 ts95

You could reproduce this by creating a model that inherits from another one.

Base class

class Animal
{
    [Key]
    public int AnimalId { get; set; }

    public int Weight { get; set; }
}

Subclass Dog

class Dog : Animal
{
    public string Breed { get; set; }
}

Subclass Cat

class Cat : Animal
{
    public bool IsHairless { get; set; }
}

If you create a migration for this it should generate just one table (called Animal) with a Discriminator column. The value of this column can either be Animal, Dog or Cat, which would correspond with the type of the model.

Instead of generating it it simply throws the exception I posted above aka System.InvalidOperationException

ts95 avatar Mar 05 '17 15:03 ts95

@ts95 try abstract class Animal, like: https://docs.microsoft.com/en-us/aspnet/core/data/ef-mvc/inheritance

kleberksms avatar Apr 04 '17 17:04 kleberksms