Search code examples
c#oracle-databaseentity-framework-coretable-per-hierarchy

How to use a custom table prefix in combination with TPH in Entity Framework Core?


Disclaimer: I'm relatively inexperienced in C# / WPF / EFC

I'm working on a WPF application written in C# which needs to store some data in a database. To store this data, I want to use Entity Framework Core. The application will uses a shared database instance which is also used by other applications. Each application has its own table prefix so my application needs to have an own table prefix as well.

Update

What I want to achieve is that all objects which are inherited from Parent are stored in the same table. When I "load" data from the table I want an instantiated instance of concerning Child class. It is/should not possible to have instances of Parent themself.

Add table prefix

I added the table prefixes in the OnModelCreating function of my DbContext class (this works great so far):

foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
    modelBuilder.Entity(entity.ClrType).ToTable(tablePrefix + entity.GetTableName());
}

Implement "Table per Hierarchy"

After implementing the table prefix, I implemented the TPH according to this description https://www.learnentityframeworkcore.com/inheritance/table-per-hierarchy

This is the implementation:

public abstract class Parent
{
    // ...
}

public class ChildA : Parent
{
    // ...
}

public class ChildB : Parent
{
    // ...
}

internal class EcroDbContext : DbContext
{
    private string tablePrefix = "APP_PREFIX_";

    public DbSet<Schedule> Schedules { get; set; } = null;

    public DbSet<ChildA> ChildA { get; set; } = null;
    public DbSet<ChildBIpp> ChildB { get; set; } = null;

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        string user = "xxx";
        string password = "xxx";
        string dbName = "xxx";
        string conString = "User Id=" + user + ";Password=" + password + ";Data Source=" + dbName;

        optionsBuilder.UseOracle(conString, x => x.MigrationsHistoryTable(tablePrefix + "ef_migration_history", null));
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Parent>().ToTable(tablePrefix + "Jobs");

        // Add prefix to each table.
        // When I remove this foreach, the TPH works fine
        foreach (var entity in modelBuilder.Model.GetEntityTypes())
        {
            modelBuilder.Entity(entity.ClrType).ToTable(tablePrefix + entity.GetTableName());
        }
    }
}

When I run Add-Migration InitialMigration, I got this error:

Unable to create a 'DbContext' of type ''. The exception 'Both 'ChildB' and 'ChildA' are mapped to the table 'APP_PREFIX_APP_PREFIX_APP_PREFIX_Jobs'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different tables.

The error says that "all entity types in a non-TPH must map a different table", but I thought I had implemented TPH for the mentioned classes. First I thought that I implemented it wrong, but when I remove the table prefix part from the OnModelCreating part, it seems to work fine.

This means that the table prefix implementation causes issues for the TPH implementation. How can I solve this error?

[SOLVED]

I notice that the prefix is added 3 times for the Jobs table. How can I solve this?


Solution

  • I solved the naming issue by checking if the name already contains the prefix (like @ Svyatoslav Danyliv suggested).

                foreach (var entity in (modelBuilder.Model.GetEntityTypes()))
                {
                    if (!entity.GetTableName().Contains(tablePrefix))
                    {
                        modelBuilder.Entity(entity.ClrType).ToTable(tablePrefix + entity.GetTableName());
                    }
                }
    

    This also solved the TPH issue for some reason. It works like a charm now.