inheritanceentity-framework-coreentity-framework-migrationstable-per-type

EF Core: How to include a common column in all generated migrations?


Note: an answer in either VB.NET or C# will be fine. I have no preference.

I'm using table-per-type architecture.

I want all of my entities to have a second key column (of type Guid), so I've created a base class like this:

Public MustInherit Class Entity
    Public Property Id As Integer
    Public Property Key As Guid
End Class

Public Class City
    Inherits Entity

    Public Property Name As Integer
    Public Property Code As String
End Class

I want each of these columns to self-populate at the database level (SQL Server) when a new row is added, so I've created a configuration like this:

Public Class EntityConfiguration
  Implements IEntityTypeConfiguration(Of Entity)

  Public Sub Configure(Builder As EntityTypeBuilder(Of Entity)) Implements IEntityTypeConfiguration(Of Entity).Configure
    Builder.Property(Function(Entity) Entity.Key).IsRequired.HasDefaultValueSql("NEWID()")
  End Sub
End Class

This works—the column is added correctly—but the migrations generator builds code for one table only, named Entities.

I tried this in my CityConfiguration class:

Builder.ToTable("Cities")

...but the generator still includes an Entity table. This is undesirable for obvious reasons. Entity is a base class only. And the Key column is omitted from the Cities table.

Is there a way to have all of my entities contain this common Key column without having to include an explicit call to HasDefaultValueSql("NEWID()") in each entity's configuration? That's going to be tedious and error-prone.


Solution

  • Well, shucks... that didn't take long.

    A quick-and-dirty extension method gets us there just nicely:

    Public Module Extensions
      <Extension>
      Public Sub AddKey(Of TEntity As Entity)(Builder As EntityTypeBuilder(Of TEntity))
        Builder.Property(Function(Entity) Entity.Key).IsRequired.HasDefaultValueSql("NEWID()")
        Builder.HasIndex(Function(Entity) Entity.Key).IsUnique()
      End Sub
    End Module
    

    And then we just call it like this in each entity's configuration:

    Builder.AddKey
    

    I'll leave this Q&A up in case anyone wants to use the idea.

    --EDIT--

    Possibly even cleaner would be to use a base class:

    Public MustInherit Class BaseConfiguration(Of T As Entity)
      Public Overridable Sub Configure(Builder As EntityTypeBuilder(Of T))
        Builder.Property(Function(Entity) Entity.Key).IsRequired.HasDefaultValueSql("NEWID()")
        Builder.HasIndex(Function(Entity) Entity.Key).IsUnique()
      End Sub
    End Class
    

    Just make sure it doesn't implement IEntityTypeConfiguration(Of Entity), or an unwanted Entity table will be generated.

    Use it like this:

    Public Class CityConfiguration
      Inherits BaseConfiguration(Of City)
      Implements IEntityTypeConfiguration(Of City)
    
      Public Overrides Sub Configure(Builder As EntityTypeBuilder(Of City)) Implements IEntityTypeConfiguration(Of City).Configure
        Builder.Property(Function(City) City.Code).IsRequired.HasDefaultValue(String.Empty).HasMaxLength(7)
        Builder.Property(Function(City) City.Name).IsRequired.HasDefaultValue(String.Empty).HasMaxLength(25)
    
        Builder.HasIndex(Function(City) City.Code).IsUnique()
        Builder.HasIndex(Function(City) City.Name)
    
        MyBase.Configure(Builder)
      End Sub
    End Class