Efcore: Why does ModelBuilder.Entity<BaseType>().HasData(new DerivedType()) throw?

Created on 30 Jul 2018  路  13Comments  路  Source: dotnet/efcore

Why does ModelBuilder.Entity().HasData(new DerivedType()) throw?
Can HasData be enhanced to accept a derived type?

{System.InvalidOperationException: The seed entity for entity type 'Blog' cannot be added because the value provided is of a derived type 'RssBlog'. Add the derived seed entities to the corresponding entity type.
   at Microsoft.EntityFrameworkCore.Metadata.Internal.EntityType.AddData(Object[] data)
   at Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder.HasData(Object[] data)
   at Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder`1.HasData(TEntity[] data)
   at Context.<>c.<OnModelCreating>b__5_0(EntityTypeBuilder`1 builder) in 
   at Microsoft.EntityFrameworkCore.ModelBuilder.Entity[TEntity](Action`1 buildAction)
   at Context.OnModelCreating(ModelBuilder modelBuilder) in 
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)}

Steps to reproduce

using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;

class Program
{
    static void Main(string[] args)
    {
        var connection = new SqliteConnection("datasource=:memory:");
        connection.Open();

        var options = new DbContextOptionsBuilder<Context>()
            .UseSqlite(connection)
            .Options;

        using (var ctx = new Context(options))
        {
            ctx.Database.EnsureCreated();
        }

        connection.Close();
    }
}
public class Context : DbContext
{
    public Context(DbContextOptions options) : base(options) { }

    public DbSet<Blog> Blogs { get; private set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<RssBlog>().HasBaseType<Blog>();
        modelBuilder.Entity<Blog>(builder =>
        {
            builder.HasData(new RssBlog());
        });
    }
}
public abstract class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}
public class RssBlog : Blog
{
    public string RssUrl { get; set; }
}

Further technical details

EF Core version: 2.1.1
Database Provider: Microsoft.EntityFrameworkCore.Sqlite
Operating system: win7
IDE: Visual Studio 2017 15.4

closed-by-design customer-reported type-enhancement

Most helpful comment

Putting this on the backlog for now. We would be interesting in hearing from any others who hit this issue.

All 13 comments

@gojanpaolo Do you expect a RssBlog or a Blog row to be inserted?

@ajcvickers I expect a Blog row to be inserted.
Db will only have Blog table with Discriminator column.

@gojanpaolo Let me ask that differently. Do you expect a row with the discriminator set to RssBlog and the RssUrl column populated with whatever value it has?

@ajcvickers Yes. :)

Putting this on the backlog for now. We would be interesting in hearing from any others who hit this issue.

Thank you for considering. :)

Shouldn't the DbSet be RssBlog in the code sample above?

    public DbSet<RssBlog> Blogs { get; private set; }

rather than

    public DbSet<Blog> Blogs { get; private set; }

@GZidar We can expose only the base type DbSet<Blog> by explicitly configuring the model builder modelBuilder.Entity<RssBlog>().HasBaseType<Blog>();

See also: https://docs.microsoft.com/en-us/ef/core/modeling/inheritance#conventions

If you don't want to expose a DbSet for one or more entities in the hierarchy, you can use the Fluent API to ensure they are included in the model. And if you don't rely on conventions you can specify the base type explicitly using HasBaseType.

@gojanpaolo Thanks for the clarification. :-)

I hit the issue when trying to apply Enumeration pattern and seed base type with derived types (derived types add no new properties into class signature, just logical separation).

See CardType implementation in eShopOnContainers repository for example.

So, trying to seed like this will fail (because instead of CardType items it returns specific derived type for each item):

builder.Entity<CardType>().HasData(Enumeration.GetAll<CardType>());

It there any recommended workaround for situation, when derived class used in seeding is not presented in model?

@dmitryshunkov Here's a similar workaround I did on my project.

```c#
public MyContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new CardTypeConfiguration()).HasAllCardTypes();
}
}

internal sealed class CardTypeConfiguration : IEntityTypeConfiguration
{
public void Configure(EntityTypeBuilder builder)
{
var discriminatorBuilder = builder.HasDiscriminator(c => c.Name);

    foreach (var cardType in Enumeration.GetAll<CardType>())
        discriminatorBuilder.HasValue(cardType.GetType(), cardType.Name);
}

}

internal static class ModelBuilderCardTypeConfigurationExtensions
{
///


/// Configures HasData for all CardType.
///

///
/// Workaround until ef core support
/// ModelBuilder.Entity().HasData(new DerivedType())
/// https://github.com/aspnet/EntityFrameworkCore/issues/12841
///

public static void HasAllCardTypes(this ModelBuilder modelBuilder)
{
foreach (var cardType in Enumeration.GetAll())
modelBuilder.Entity(cardType.GetType())
.HasData(cardType);
}
}
```

@gojanpaolo I guess, your situation is a bit different, as your derived type (RssBlog) is presented in the model (with discriminator). And what I'm trying to achieve is to have CardType table with "Id", "Name" columns, but seed it with derived types instances (no additional properties, so I don't need TPH for CardType hierarchy).
For now, as a workaround, I just manually construct anonymous type:

builder.Entity<CardType>().HasData(Enumeration.GetAll<CardType>().Select(p => new { p.Id, p.Name })

I simplified the example, in reality there are some other columns.

Discussed again in triage and decided that for now at least we will not change the behavior here.

Was this page helpful?
0 / 5 - 0 ratings