Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
ts95 opened this issue Mar 5, 2017 · 10 comments

Comments

@ts95
Copy link

ts95 commented Mar 5, 2017

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
Copy link
Author

ts95 commented Mar 5, 2017

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));
    }
}

@SapientGuardian
Copy link
Owner

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

@ts95
Copy link
Author

ts95 commented Mar 5, 2017

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

@SapientGuardian
Copy link
Owner

SapientGuardian commented Mar 5, 2017

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?

@ts95
Copy link
Author

ts95 commented Mar 5, 2017

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.

@SapientGuardian
Copy link
Owner

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
Copy link
Owner

@ts95
Copy link
Author

ts95 commented Mar 5, 2017

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
Copy link
Author

ts95 commented Mar 5, 2017

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

@kleberksms
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants