If I attempt to configure primary key behaviour against base classes using the fluent API only those classes are created against the database. This behaviour is contrary to my understanding that abstract classes do not trigger table behaviour.
Given the following base classes
``` c#
public abstract class Entity : IEntity
{
public Guid Id { get; set; }
}
public abstract class RangeEntity : IRangeEntity
{
public Guid Id { get; set; }
public int RangeId { get; set; }
}
And configuration code to normalize behaviour across my concrete types
```c#
const string SequentialGuid = "newsequentialid()";
// Configure the base entities to use sequential guid's as primary keys
builder.Entity<Entity>(b =>
{
b.HasKey(p => p.Id);
b.Property(p => p.Id).HasDefaultValueSql(SequentialGuid);
});
// Configure sequential guid primary key and additional incremental key
builder.Entity<RangeEntity>(b =>
{
b.HasKey(p => p.Id);
b.Property(p => p.Id).HasDefaultValueSql(SequentialGuid);
b.HasAlternateKey(p => p.RangeId);
});
The library will create two tables in my database Entity and RangeEntity. These tables contain some of the properties of inheriting classes and foreign keys for inheriting classes also. No other tables are generated. I have configured DbSet roperties for all my concrete types.
I would expect EF Core to create tables and implement those rules for all inheriting concrete types and not create tables for the base types.
EF Core version:
@JimBobSquarePants Using the Entity call tells EF to map the entity type, which in turn causes it to become the base type of a TPH mapping. If you want the type instead to be an un-mapped base type, then don't explicitly configure it as as an entity type with a call to Entity or a by having DbSet for it on the context.
Something like this can be used to bulk-configure all entity types that inherit from a type:
C#
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(Entity).IsAssignableFrom(e.ClrType)))
{
modelBuilder
.Entity(entityType.ClrType)
.Property(nameof(Entity.Id))
.HasDefaultValueSql(SequentialGuid);
}
Issues #9117 and #6787 are about making this somewhat easier.
Also note that Id will be mapped as the key by convention, so it doesn't need to be mapped explicitly. HasAlternateKey is unlikely to be useful--see discussion here: #8645
Finally, I'm curious where your "understanding that abstract classes do not trigger table behaviour" comes from? I'm asking in case we need to update docs anywhere.
Edit : After applying a migration, your code works partially. However, it creates static default values in the
database. What I wanted was being able to provide default values for those fields on the run. I realized what I want is called TPC, and it is not available in EF Core yet. Please correct me if I am wrong.
I have a similar problem regarding this issue. I have a base type that will not be mapped seperately to the database, but which contains some fields that I want to be present in all of my entities. And I want EF to automatically populate those fields.
Here is the base class that I have :
```C#
public abstract class EntityBase
{
public int Id { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedAt { get; set; }
public int CreatedBy { get; set; }
}
Here is one of my entities that will be mapped to database :
```C#
public class Bill : EntityBase
{
public DateTime DateTime { get; set; }
public double Total { get; set; }
}
I have tried the following to populate fields, but it fails stating EntityBase is not present in database.
```C#
modelBuilder.Entity
.Property(x => x.CreatedBy)
.ValueGeneratedOnAdd()
.HasDefaultValue(UserId);
From your answer, I have tried the following. It works without an error, but the field is not populated in the database (It still has the default int value 0).
```C#
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(EntityBase).IsAssignableFrom(e.ClrType)))
{
modelBuilder
.Entity(entityType.ClrType)
.Property(nameof(EntityBase.CreatedBy))
.ValueGeneratedOnAdd()
.HasDefaultValue(5);
}
I am using :
.Net Core 2.0
EF Core 2.0
Npgsql.EntityFrameworkCore.PostgreSQL 2.0
Windows 10
@akyildizfirat I ran your code and it works fine for me. Can you please post a new issue with a runnable project/solution or code listing that demonstrates what you are seeing?
Here's my little test listing using your code for reference:
```C#
public abstract class EntityBase
{
public int Id { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedAt { get; set; }
public int CreatedBy { get; set; }
}
public class Bill : EntityBase
{
public DateTime DateTime { get; set; }
public double Total { get; set; }
}
public class TestDbContext : DbContext
{
public DbSet
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(EntityBase).IsAssignableFrom(e.ClrType)))
{
modelBuilder
.Entity(entityType.ClrType)
.Property(nameof(EntityBase.CreatedBy))
.ValueGeneratedOnAdd()
.HasDefaultValue(5);
}
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
public class Program
{
public static void Main()
{
using (var context = new TestDbContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
context.Add(new Bill());
context.SaveChanges();
}
using (var context = new TestDbContext())
{
Console.WriteLine(context.Bills.Single().CreatedBy);
}
}
}
```
I am sorry for the late reply and the confusion. I should have made a new post without editing my previous one. Let me try to be more specific.
I have an abstract base class and many models inheriting from it. In the database, I want to have tables for concrete types. In addition, I am trying to make EF automatically populate the fields of the base class for each model for any query that I execute, if that is possible.
public abstract class EntityBase
{
public int Id { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedAt { get; set; }
public int CreatedBy { get; set; }
}
public class Bill : EntityBase
{
public DateTime DateTime { get; set; }
public double Total { get; set; }
}
public class TestDbContext : DbContext
{
public DbSet<Bill> Bills { get; set; }
public int UserId { get; set; }
public TestDbContext()
{
UserId = GetUserIdFromSomewhere();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(EntityBase).IsAssignableFrom(e.ClrType)))
{
modelBuilder
.Entity(entityType.ClrType)
.Property(nameof(EntityBase.CreatedBy))
.ValueGeneratedOnAdd()
.HasDefaultValue(UserId);
}
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
To be clear, I want a different UserId value inserted on each query at the runtime, depending on the value I get at the DbContext's constructor.
Is there a possible way to achieve that without writing configuration code for every model ? I am trying to write one piece of code for EntityBase, so that each model inheriting from it gets its fields populated by EF dynamically at runtime.
Thank you.
@akyildizfirat There isn't anything built-in to do that. I suspect you will be able to do with state-changing events, which is being tracked by issue #626
Thank you very much for your answers.
Most helpful comment
@JimBobSquarePants Using the
Entitycall tells EF to map the entity type, which in turn causes it to become the base type of a TPH mapping. If you want the type instead to be an un-mapped base type, then don't explicitly configure it as as an entity type with a call toEntityor a by having DbSet for it on the context.Something like this can be used to bulk-configure all entity types that inherit from a type:
C# foreach (var entityType in modelBuilder.Model.GetEntityTypes() .Where(e => typeof(Entity).IsAssignableFrom(e.ClrType))) { modelBuilder .Entity(entityType.ClrType) .Property(nameof(Entity.Id)) .HasDefaultValueSql(SequentialGuid); }Issues #9117 and #6787 are about making this somewhat easier.
Also note that Id will be mapped as the key by convention, so it doesn't need to be mapped explicitly. HasAlternateKey is unlikely to be useful--see discussion here: #8645
Finally, I'm curious where your "understanding that abstract classes do not trigger table behaviour" comes from? I'm asking in case we need to update docs anywhere.