EF inheritance with table splitting - c#

I'm trying to map two different EF models to the same table SharedTable, let us call them EntityA and EntityB. I made them both extend a base Entity called BaseEntity.
EntityA is defined only with SharedTable fields, EntityB has fields in SharedTable and EntityBTable.
modelBuilder.Entity<BaseEntity>()
.Map<EntityA>(m => m.Requires("IsEntityA").HasValue<bool>(true))
.Map<EntityB>(m => m.Requires("IsEntityA").HasValue<false>(true));
modelBuilder.Configurations.Add(new EntityBMap());
modelBuilder.Configurations.Add(new EntityAMap());
modelBuilder.Configurations.Add(new BaseEntityMap());
The models look like this
public class BaseEntity
{
[Required]
public int Id { get; set; }
public int SharedTableField1 { get; set; }
}
public class EntityA : BaseEntity
{
public int SharedTableField2 { get; set; }
}
public class EntityB : BaseEntity
{
public int EntityBTableField1 { get; set; }
}
The mappings are
public class BaseEntityMap : EntityTypeConfiguration<BaseEntity>
{
public BaseEntityMap()
{
// Primary Key
this.HasKey(t => t.Id);
this.ToTable("SharedTable");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.SharedTableField1).HasColumnName("SharedTableField1");
}
}
public class EntityAMap : EntityTypeConfiguration<EntityA>
{
public EntityAMap()
{
this.HasKey(t => t.Id);
this.Property(t => t.Id).HasColumnName("Id");
this.ToTable("SharedTable");
this.Property(t => t.SharedTableField2).HasColumnName("SharedTableField2");
}
}
public class EntityBMap : EntityTypeConfiguration<EntityB>
{
public EntityBMap()
{
Map(c =>
{
HasKey(t => t.Id);
Property(t => t.Id).HasColumnName("Id");
c.Properties(t => new
{
t.SharedTableField2
});
c.ToTable("SharedTable");
});
Map(c =>
{
c.Properties(t => new
{
t.EntityBTableField1
});
c.ToTable("EntityBTable");
});
}
}
The error I get says:
A first chance exception of type 'System.NotSupportedException' occurred in EntityFramework.dll
Additional information: The type 'EntityB' cannot be mapped as defined because it maps inherited properties from types that use entity splitting or another form of inheritance. Either choose a different inheritance mapping strategy so as to not map inherited properties, or change all types in the hierarchy to map inherited properties and to not use splitting.
Any way around this?

The inheritance strategy you've chosen is Table per Type (TPT).
You've got three types: one base type BaseEntity and two derived types EntityA and EntityB. You decided to put them into three separate tables. The BaseEntity properties of both EntityA and EntityB will be put in one table. EntityA and EntityB each have a foreign key to their base properties in the BaseEntity table.
Whether this inheritance strategy is the best for your problem depends on whether you will mostly query for "BaseEntities that ..." or for "EntityA that ..." and "EntityTyB that ..."
Could it be that using Table per concrete class (TPC) would be more suitable for your problem?
If you choose TPT inheritance strategy, then for every query "EntityA with some base class properties that ..." will need a join.
If you'd use TPC this join would not be needed. However TPC has the disadvantage that a Concat between the two tables is required whenever you ask for "BaseEntities that ...".
So it depends what kind of queries you will do most often which inheritance strategy is the best for your needs.
If you want to stick to strategy TPT, it seems that you don't build your model correctly.
You don't want anyone to store BaseEntity objects on its own. If you'd allow that, it wouldn't be inheritance, but one-to-zero-or-one relation: every EntityA belongs to one BaseEntity, and every BaseEntity has zero or one 'EntityA. This is not what you want: everyBaseEntityhas exactly either oneEntityAor one 'EntityB, and every 'EntityA / EntityB has exactly one 'BaseEntity`
As you don't want to storeBaseEntity objects without the derived class, 'BaseEntity` class ought to be declared abstract, as in the given link for TPT.
In the class definitions of EntityA en EntityB, don't mention the foreign key to the BaseEntity table. Again, see the given link to the TPT
I think the abstract base class and the lack of foreign keys are the
key information to let entity framework know that you chose
inheritance strategy TPT.
In your model building, only mention the table names and if needed the column names. Don't mention the foreign keys
When I built your model like this, entity framework created the three tables like TPT. any extra fluent API nor attributes were needed. Even though I didn't mention the foreign keys, Entity Framework knew they were needed as a polymorphic association. Again see the link to TPT
By the way, is the [Required] on your integers useful? Even if I wanted to, I couldn't give this integers a null value. Didn't you mean [Key]? As you are following the entity framework code first conventions even that is not necessary.

Related

Inheriting properties from model - Navigation properties can only participate in a single relationship

I'm trying to share common properties with multiple entities by using multiple levels of inheritance, but I'm running into an error.
Cannot create a relationship between 'User.SupersCreated' and 'Super.CreatedBy' because a relationship already exists between 'User.BasicsCreated' and 'Basic.CreatedBy'. Navigation properties can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation 'Super.CreatedBy' first in 'OnModelCreating'.
The structure of my models is as follows.
public class EntityBase
{
public Guid Id { get; set; }
public Guid CreatedById { get; set; }
public User CreatedBy { get; set; }
}
public class Basic: EntityBase
{
public string BasicProperty { get; set; }
}
public class Super : Basic
{
public string SuperProperty { get; set; }
}
public class User : IdentityUser<Guid>
{
public ICollection<Basic> BasicsCreated { get; set; }
public ICollection<Super> SupersCreated { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasMany(x => x.BasicsCreated)
.WithOne(x => x.CreatedBy);
modelBuilder.Entity<User>()
.HasMany(x => x.SupersCreated)
.WithOne(x => x.CreatedBy);
}
The problem seems to be a result of Super Inheriting from Basic, or at least, the problem goes away when I remove this level of inheritance and make Super inherit from EntityBase (however than I'll lose the properties that exist in Basic).
Can anyone please help me understand why I'm getting this error and what should be done to fix it?
Edit
After considering this some more, I think I'm trying to abuse inheritance to do what it's not intended to do.
The database structure I was hoping to end up with, is:
Even though my Basic and Super tables share the same properties, with Super having it's own additional properties, there's no relationship between Basic data and Super data.
From having a look at Microsoft's tutorial on implementing inheritance, there's two options:
Table per type
Table per hierarchy
Neither of these are what I'm trying to achieve.
Perhaps I should be using interfaces to define the common properties that exist between unrelated entities. It seems like I need to back and re-evaluate my design anyway.
If some of the base classes of the entity is identified as entity (as with your Super and Basic), by default EF Core will try to use one of the database inheritance strategies.
If you don't want that (want to treat is just like non entity base class), then you have to configure that explicitly at the very beginning of the OnModelCreating, e.g. for your sample
modelBuilder.Entity<Super>().HasBaseType((Type)null);
or more generally using a loop similar to this
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
entityType.BaseType = null;
and then define explicitly the entity hierarchy if and where needed.

Duplicate ForeignKey when using inheritance

I have created these classes in order to generate the database model via EntityFramework 6 code-first approach:
public class Vehicle
{
public long Id { get; set; }
public long ResponsiblePersonId { get; set; }
}
public class Car: Vehicle {
public int HorsePower { get; set; }
}
public class Bike: Vehicle {
public int FrameSize { get; set; }
}
public class Organisation
{
public Organisation()
{
Cars = new List<Car>();
Bikes = new List<Bikes>();
}
public long Id { get; set; }
public List<Car> Cars { get; set; }
public List<Bike> Bikes { get; set; }
}
So far this seemed right for me.
But unfortunately, the resulting table looks like this:
Id | ResponsiblePersonId | HorsePower | FrameSize | Discriminator | Organisation_Id | Organisation_Id1
Why is the Organisation ForeignKey being generated twice? I expected this table to only have one Organisation_Id column.
Thanks
There are several ways for EF to implement the physical tables for your inheritance hierarchy. The default one, the one you are using, is called Table Per Hierarchy (TPH). It uses only one table for all the derived entities, with one Discriminator column to specify the type of entity which is contained in the record. EF also adds to the table a column for each property that is included in any of the derived entities.
So as the relationship between the derived entities and Organisation is defined at child level (the lists of Car and Bike properties in Organisation entity) EF decides to create a separate column for each child entity type Organisation_Id, and you don't want that.
How to change this? There are several ways:
Don't use TPH. Use instead TPC (Table Per Concrete class). That is, EF creates a separate table for each one of your child entities. How to do this: remove the DbSet<Vehicle> property from your DbContext. If this doesn't make it, set an explicit configuration for the physical table name for each entity derived from Vehicle like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Entity<Car>().ToTable("Cars");
modelBuilder.Entity<Bike>().ToTable("Bikes");
}
If you need to continue using TPH, I don't know of any way to implementing this that will generate only one OrganisationId column in the database and only one Foreign Key between Vehicle and Organisation. Common sense would say that you might define the Organisation foreign key at the Vehicle base entity level. But then you get errors when generating the migration:
Organisation: FromRole: NavigationProperty 'Organisation' is not
valid. Type 'Car' of FromRole 'Organisation_Cars_Target' in
AssociationType 'Organisation_Cars' must exactly match with the type
'Vehicle' on which this NavigationProperty is declared on.
It seems that when the relationship is defined at base level then EF expects the lists in Organisation to be defined of type Vehicle and not Car or Bike. And this does not fit with your model.
And if you try to define OrganisationId or Organisation properties in your derived classes then you get a different error when generating the migration, because you are not allowed to use the same name for the properties in the different child entities. You can use different names, but then you get two columns again. There is no way to get one column this way either.
So if you stick with TPH, as far as I know, you have to put up with having two columns for your OrganisationId. At least you can name them in a more verbose way with some fluent configurations:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Entity<Organisation>()
.HasMany(o => o.Bikes)
.WithRequired()
.Map(x => x.MapKey("OrganisationIdBike"));
modelBuilder.Entity<Organisation>()
.HasMany(o => o.Cars)
.WithRequired()
.Map(x => x.MapKey("OrganisationIdCar"));
}
I would recommend you to change to TPC, as with your model the fluent mappings are a bit less complex to write.
For a better understanding of TPH, TPC and TPT (Table Per Type, yet another implementation of inheritance hierarchies) read this post.

Semi-recursive associations with Entity Framework Code First

I'm hoping you can help me with a small issue I'm having.
I have an entity framework Code First setup where I have the following setup:
public class UserDetails{
public int Id {get;set;}
//... some other properties .. //
//This represents the approval group the user is a member of.
public virtual ApprovalGroup {get;set;}
//This represents the approval groups that the user is resposible for approving
public virual ICollection<ApprovalGroup> ApprovalGroups {get;set;}
}
public class ApprovalGroup
{
public int Id {get;set;}
public string Name {get;set;}
public virtual UserDetails Approvee {get;set;}
public virtual ICollection<UserDetails> Members {get;set;}
}
In my db context I have the following:
modelBuilder.Entity<ApprovalGroup>().ToTable("ApprovalGroup")
.HasKey(t=>t.Id)
.HasRequired(t=>t.Approver);
modelBuilder.Entity<ApprovalGroup>().HasMany(t=>t.Members)
.WithOptional().WillCascadeOnDelete(false);
So an approval group MUST have an approvee set-up, however it could potentially have no members (particularly when first configured).
This code is running, however when I examine the database that it creates the approval group table has an extra column in it that is called "UserDetails_Id". It is set up as a foreign key, but it is always null.
The schema of the table it creates has the following columns:
Id, Name, UserDetails_Id, Approver_Id
I have no idea why it is creating the unnecessary table "UserDetails_Id" and I'd like it not to as there is no reason for it. I suspect I have something wrong with my configuration/mapping behaviour but I can't figure out what it is.
As of yet, google has failed to shed light on what I'm doing wrong so if anyone here can help it would be greatly appreciated.
Nik
The issue you are experiencing is because of the improper/incomplete relationships mappings, combined with EF default conventions.
I would suggest you always configuring the relationships separately, only once per relationship and use the overloads that match exactly the presence/absence of the navigation and explicit FK properties.
In this particular case you have two one-to-many bidirectional (i.e. with navigation properties on both ends) relationships with no explicit FK properties. So the correct configuration should be like this:
// Entities
modelBuilder.Entity<ApprovalGroup>()
.ToTable("ApprovalGroup")
.HasKey(t => t.Id);
modelBuilder.Entity<UserDetails>()
.ToTable("UserDetails")
.HasKey(t => t.Id);
// Relationships
modelBuilder.Entity<ApprovalGroup>()
.HasRequired(t => t.Approver)
.WithMany(t => t.ApprovalGroups);
modelBuilder.Entity<ApprovalGroup>()
.HasMany(t => t.Members)
.WithOptional(t => t.ApprovalGroup) // or whatever the name of the navigation property is (it's missing in the posted code)
.WillCascadeOnDelete(false);

Entity Framework 7: Identifying relationships

In previous versions of EF I can use following code to implement an identifying relationship:
public class Child
{
[Key, Column(Order = 1)]
public virtual int Id { get; set; }
[Key, Column(Order = 2)]
public virtual int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
It's needed to easily remove a child from collection like this:
var parent = _context.Parents.First();
var child = parent.Children.First();
parent.Children.Remove(child);
_context.SaveChanges();
This approach is described in http://www.kianryan.co.uk/2013/03/orphaned-child/ (the method #2).
But in EF7 this code throws exception when migration is creating:
An exception was thrown while executing a resolve operation. See the
InnerException for details. ---> Entity type 'Child' has composite
primary key defined with data annotations. To set composite primary
key, use fluent API.
I also tried to use FluentAPI as described in How to define nested Identifying Relationships Entity Framework code first in following code:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.HasMany(p => p.Children)
.WithOne(c => c.Parent);
modelBuilder.Entity<Child>()
.HasKey(c => new {c.Id, c.ParentId});
base.OnModelCreating(modelBuilder);
}
This approach allow generate a migration successfully, but when I tried to remove a child from Children collection I got following exception:
System.InvalidOperationException: The association between entity types
'Parent' and 'Child' has been severed but the foreign key for this
relationship cannot be set to null. If the dependent entity should be
deleted, then setup the relationship to use cascade deletes.
But I wouldn't like to use cascade deletes, I would like to use identifying relationship!
Please, help me understand what I do incorrect. Thank you!
Use cascade on delete instead as this is what it's used for:
modelBuilder.Entity<Parent>()
.HasMany(p => p.Children)
.WithOne(c => c.Parent);
.WillCascadeOnDelete(true);
https://msdn.microsoft.com/en-gb/data/jj591620.aspx
Just in case someone sees this error, let me tell you how I resolved mine:
When you do an update, on EF you need to first query the database and get the data model, then map the Domain layer model with your changes onto it (basically copying fields onto the data), and finally call the DBContext update method, then save changes.
My problem was that my model (not the data model, the domain model) also had the sub objects on it.
So here's the data layer model (for example):
public class Parent
{
public int ChildId {get; set; }
[ForeignKey("ChildId")]
public virtual Child Child { get; set; }
}
And here's how the domain layer model should be:
public class Parent
{
public int ChildId { get; set; }
//public Child Child { get; set; } // this caused the error, keep reading if you want to know more.
}
When I was seeing the error, I had been using Autofac's runtime mapper to map the domain layer model's properties onto the data layer model. However, the child in the domain layer model was null, so it would nullify the data layer, causing the error:
"The association between entity types 'Parent' and 'Child' has been severed but the foreign key for this relationship cannot be set to null. If the dependent entity should be deleted, then setup the relationship to use cascade deletes."
By the way, in the db context class, I have the following relationship defined:
modelBuilder.Entity<Parent>()
.HasOne(a => a.Child)
.WithMany()
.HasForeignKey(p => p.ChildId)
.OnDelete(DeleteBehavior.Restrict);
It's working.

Can I combine TPT and TPC inheritance?

I have a dependency to several external suppliers of Data. Each supplier have very different data model. I map this to an internal data structure.
For simplicty Lets call the data from Supplier for Item
So I have a Item table which is the base table for my TPT
Basicly it only Holds the Id and the Name
I then have Sub tables that "derive" from the Item table
This works nice with TPT, but for some of the suppliers one sub table is not enough. I want to use TPC for these sub tables otherwise there will be so many joins etc.
Configs looks like this, given name of example data supplier is Foo
public class ItemConfig : EntityTypeConfiguration<Item>
{
public ItemConfig()
{
HasKey(k => k.Id);
ToTable("Item");
}
}
public class FooItemConfig : EntityTypeConfiguration<FooItem>
{
public FooItemConfig()
{
Property(cst => cst.Code) //External Identifier for supplier Foo
.HasMaxLength(50)
.IsRequired();
//Because of TPC this config does not map to a table
}
}
public class ConcreteFooItemConfig : EntityTypeConfiguration<ConcreteFooItem>
{
public ConcreteFooItemConfig()
{
Map(m => m.MapInheritedProperties());
ToTable("ConcreteFooItem");
}
}
Looks pretty straight forward, but I get
Additional information: The type 'ConcreteFooItem' cannot be mapped as defined
because it maps inherited properties from types that use entity
splitting or another form of inheritance. Either choose a different
inheritance mapping strategy so as to not map inherited properties, or
change all types in the hierarchy to map inherited properties and to
not use splitting.
edit: I can change FooItem to an interface IFooItem and that will work,but its not really an option because I want to be able to do queries on FooItem level from domain logic that is specific for FooItem

Categories