4

I have some code first entities similar to these:

public abstract class Animal {
    public int ID { get; set; }
    public int NumberOfLegs { get; set; }
}

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

public class Bird : Animal {
    public string OtherBirdRelatedStuff { get; set; }
}

public class MyContext : DbContext {

    public IDbSet<Animal> Animals { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder) {

        var pluraliser = PluralizationService.CreateService(new System.Globalization.CultureInfo("en-GB"));

        modelBuilder.HasDefaultSchema("vs");
        modelBuilder.Types().Configure(t => t.ToTable(pluraliser.Pluralize(t.ClrType.Name)));

        // This next line doesn't give ID column IDENTITY(1,1)
        modelBuilder.Entity<Animal>().Property(_ => _.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        // This line puts IDENTITY(1,1) on Animal.ID, but causes errors when I try to add/update data.
        //modelBuilder.Entity<Dog>().Property(_ => _.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        base.OnModelCreating(modelBuilder);
    }        
}

And I want table per type... so the tables look something like this:

CREATE TABLE Animals (
    ID INT NOT NULL IDENTITY(1,1),
    NumberOfLegs INT NOT NULL,
    CONSTRAINT pkAnimals PRIMARY KEY (ID)
)

CREATE TABLE Dogs (
    ID INT NOT NULL,
    OtherDogRelatedStuff VARCHAR(200),
    CONSTRAINT pkDogs PRIMARY KEY (ID),
    CONSTRAINT fkAnimal_Dog FOREIGN KEY (ID) REFERENCES Animals(ID)
)

CREATE TABLE Birds (
    ID INT NOT NULL,
    OtherBirdRelatedStuff VARCHAR(200),
    CONSTRAINT pkBirds PRIMARY KEY (ID),
    CONSTRAINT fkAnimal_Bird FOREIGN KEY (ID) REFERENCES Animals(ID)
)

With data looking like this:

---Animals----------
ID      NumberOfLegs
1       4
2       4
3       2

---Dogs---------------------
ID      OtherDogRelatedStuff
1       Woof1
2       Woof2

---Birds---------------------
ID      OtherCatRelatedStuff
3       Sqwark1

But I can't get the auto increment ID working, or configured correctly.

I've tried this:

modelBuilder.Entity<Animal>().Property(_ => _.ID)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

But because Animal is abstract this doesn't seem to set the IDENTITY(1,1) property on the table.

I've also tried doing the same on the Dog entity, which correctly adds the identity property to the ID column in the Animals table, but I get an UpdateException when I try to add new entities to the database on the SaveChanges() method:

A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'ID'.

How can I correctly set the ID column on the abstract type to auto increment, and make it work when I add data?

BG100
  • 4,481
  • 2
  • 37
  • 64
  • Link the relevant parts of your dbcontext class please – Alexander Derck Jan 28 '16 at 17:49
  • @AlexanderDerck: Done. – BG100 Jan 29 '16 at 09:41
  • Noticed you have `ID` on both animal and dog while `Dog` inherits it from `Animal` – Alexander Derck Jan 29 '16 at 09:45
  • Ah yes, thats a mistake when I wrote the question. My real class doesn't have ID in the concrete classes. I'll fix it. – BG100 Jan 29 '16 at 09:47
  • I'll also add a second concrete class to further clarify what my real stucture looks like. – BG100 Jan 29 '16 at 09:50
  • I think `modelBuilder.Types().Configure(t => t.ToTable(pluraliser.Pluralize(t.ClrType.Name)));` also maps `Animal` to a table which shouldn't happen. You can only map the derived classes to a table – Alexander Derck Jan 29 '16 at 10:02
  • Doesn't work either. I changed it to this: `modelBuilder.Types().Where(t => t != typeof(Animal)).Configure(t => t.ToTable(pluraliser.Pluralize(t.ClrType.Name)));` but the ID column in the Animals table doesn't get IDENTITY(1,1) – BG100 Jan 29 '16 at 10:11
  • Then I don't know what's causing your problem I'm afraid, for me this approach always works :/ However I always use `DbSet` instead of `IDbSet` and instead of mapping all types to the table, I do it for every Entity, so like `modelBuilder.Entity().ToTable("Dogs");`. I don't think that causes the problem, but hey who knows... – Alexander Derck Jan 29 '16 at 10:20

3 Answers3

2

I would say use the interface or separate abstract class to put common functionalities like this:

public interface Common{
  int ID { get; set; }
}
public class Animal : Common {
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]    
public int ID { get; set; }
public int NumberOfLegs { get; set; }
}

public class Dog : Common {
    public int ID { get; set; }
    public string OtherDogRelatedStuff { get; set; }
}
Tanveer Ali
  • 151
  • 7
  • Thanks for your answer, but this isn't what I want. I want an Animal table with an auto-inc primary key that maps to an abstract type, and 1 or more tables that share the same ID linked by foreign key that inherit Animal. – BG100 Jan 29 '16 at 09:41
  • Just take the attribute he specified and place it on your Animal Abstract class. – Shaun Sharples Jan 29 '16 at 10:04
  • I've already tried that... it doesn't make any difference if I specify by attribute on the class, or do it in OnModelCreating... same result. – BG100 Jan 29 '16 at 10:15
  • Actually, adding the `[DatabaseGenerated(...)]` attribute worked... Not sure why it didn't the first time I tried. I don't want the interface though. Thanks. – BG100 Jan 29 '16 at 15:54
2

With these models...

public abstract class Animal
{
    public int ID { get; set; }
    public int NumberOfLegs { get; set; }
}

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

public class Bird : Animal
{
    public string OtherBirdRelatedStuff { get; set; }
}

...and this in the context...

public IDbSet<Animal> Animals { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<Dog>().ToTable("Dogs");
    modelBuilder.Entity<Bird>().ToTable("Birds");
}

...then in the package manager execute Add-Migration "Animals" then Update-Database -Script

You should get:

CREATE TABLE [dbo].[Animals] (
    [ID] [int] NOT NULL IDENTITY,
    [NumberOfLegs] [int] NOT NULL,
    CONSTRAINT [PK_dbo.Animals] PRIMARY KEY ([ID])
)
CREATE TABLE [dbo].[Dogs] (
    [ID] [int] NOT NULL,
    [OtherDogRelatedStuff] [nvarchar](max),
    CONSTRAINT [PK_dbo.Dogs] PRIMARY KEY ([ID])
)
CREATE INDEX [IX_ID] ON [dbo].[Dogs]([ID])
CREATE TABLE [dbo].[Birds] (
    [ID] [int] NOT NULL,
    [OtherBirdRelatedStuff] [nvarchar](max),
    CONSTRAINT [PK_dbo.Birds] PRIMARY KEY ([ID])
)
CREATE INDEX [IX_ID] ON [dbo].[Birds]([ID])
ALTER TABLE [dbo].[Dogs] 
ADD CONSTRAINT [FK_dbo.Dogs_dbo.Animals_ID] FOREIGN KEY ([ID]) 
REFERENCES [dbo].[Animals] ([ID])
ALTER TABLE [dbo].[Birds] 
ADD CONSTRAINT [FK_dbo.Birds_dbo.Animals_ID] FOREIGN KEY ([ID]) 
REFERENCES [dbo].[Animals] ([ID])
INSERT [dbo].[__MigrationHistory]([MigrationId], [ContextKey], [Model], [ProductVersion])
VALUES (N'etc)

which is what you want, right?

Reference: Inheritance with EF Code First: Part 2 – Table per Type (TPT)

Be aware that sometimes you can run into issues with a field created with an identity if it has been initially created without an identity (and vice versa). You can fix that by dropping and recreating the table: https://stackoverflow.com/a/18917348/150342

Community
  • 1
  • 1
Colin
  • 22,328
  • 17
  • 103
  • 197
0

I faced the same problem and I fixed it using data annotation in the abstract class, like this:

public abstract class Animal {
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }
    public int NumberOfLegs { get; set; }
}
Desmond
  • 406
  • 5
  • 7