I have an application that uses an existing database, currently using NHibernate as O/R-Mapper.
Now I need to migrate to Entity Framework 6.1.1 using Code First and Fluent API Configuration.
But now I have a problem with a part of the data model because it uses different types of inheritance strategies (TPT and TPH)
Note: Posting the complete data model here seemed a bit too enormous to me so I reproduced the problem I face in a small POC program.
CLASS | TABLE | TYPE
-----------------------+--------------------+------
BaseEntity (abstract) | BaseTable |
Inherited_TPH | BaseTable | 1
Inherited_TPT | Inherited_TPT | 2
The column used as discrimator in the table is called Type
Based on this answer I added an abstract class Intermediate_TPH
as intermediate layer:
Some sample data: Entry with ID=3
is of type Inherited_TPT
These are my entity classes and my context class:
class MyContext : DbContext
{
public MyContext ( string connectionString )
: base ( connectionString )
{
}
public DbSet<Inherited_TPH> TPH_Set { get; set; }
public DbSet<Inherited_TPT> TPT_Set { get; set; }
public DbSet<SomethingElse> Another_Set { get; set; }
protected override void OnModelCreating ( DbModelBuilder modelBuilder )
{
modelBuilder
.Entity<BaseEntity> ()
.ToTable ( "BaseTable" );
modelBuilder
.Entity<Inherited_TPH> ()
.Map ( t => t.Requires ( "Type" ).HasValue ( 1 ) );
modelBuilder
.Entity<Intermediate_TPT> ()
.Map ( t => t.Requires ( "Type" ).HasValue ( 2 ) );
modelBuilder
.Entity<Intermediate_TPT> ()
.Map<Inherited_TPT> ( t => t.ToTable ( "Inherited_TPT" ) );
modelBuilder
.Entity<SomethingElse> ()
.ToTable ( "SomethingElse" )
.HasKey ( t => t.Id );
}
}
public abstract class BaseEntity
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
}
public class Inherited_TPH : BaseEntity
{
}
public abstract class Intermediate_TPT : BaseEntity
{
}
public class Inherited_TPT : Intermediate_TPT
{
public virtual string Comment { get; set; }
}
public class SomethingElse
{
public virtual string Description { get; set; }
public virtual int Id { get; set; }
}
Running the following code will give me an error.
static void Main ( string[] args )
{
Database.SetInitializer<MyContext> ( null );
var ctx = new MyContext ( @"Data Source=(local);Initial Catalog=nh_ef;Integrated Security=true" );
try
{
// Accessing Inherited_TPH works just fine
foreach ( var item in ctx.TPH_Set ) Console.WriteLine ( "{0}: {1}", item.Id, item.Title );
// Accessing Inherited_TPT works just fine
foreach ( var item in ctx.TPT_Set ) Console.WriteLine ( "{0}: {1} ({2})", item.Id, item.Title, item.Comment );
// The rror occurs when accessing ANOTHER entity:
foreach ( var item in ctx.Another_Set ) Console.WriteLine ( "{0}: {1}", item.Id, item.Description );
}
catch ( Exception ex )
{
Console.WriteLine ( ex.Message );
if( ex.InnerException != null ) { Console.WriteLine ( ex.InnerException.Message ); }
}
}
The program produces the following output:
1: Simpson
2: Johnson
3: Smith (More details about SMITH)
4: Miller (More details about MILLER)
An error occurred while preparing the command definition. See the inner exception for details.(26,10) : error 3032: Problem in mapping fragments starting at lines 14, 26:EntityTypes PoC.Inherited_TPH, PoC.Inherited_TPT are being mapped to the same rows in table BaseEntity. Mapping conditions can be used to distinguish the rows that these types are mapped to.
As you can see, the mapping seems to work because I can load all data from Inherited_TPT
and Inherited_TPH
. But when accessing another entity, I get an exception.
How do I need to configure the mapping to get rid of this error and be able to access the existing database structure?
I finally found the solution myself. I'll post it here in case someone else will need the same behavior...
So all we need to do is to change the definition of the mapping:
protected override void OnModelCreating ( DbModelBuilder modelBuilder )
{
// Not changed
modelBuilder
.Entity<BaseEntity> ()
.ToTable ( "BaseTable" );
// --- CHANGED ---
modelBuilder.Entity<BaseEntity> ()
// TPH => Discriminator
.Map<Inherited_TPH> ( m => m.Requires ( "Type" ).HasValue ( 1 ).IsOptional () )
// TPT => Mapping to table
.Map<Inherited_TPT> ( m => m.ToTable ( "Inherited_TPT" ) );
// Not changed
modelBuilder
.Entity<SomethingElse> ()
.ToTable ( "SomethingElse" )
.HasKey ( t => t.Id );
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With