TPC Inheritance Error - c#

I've got an strange problem with TPC inheritance using C# Entity Framework Codefirst and Fluent Api.
I have 3 Classes named Person, Invoice and PeriodicInvoice as you can see below.
Here is a summary of my code:
Invoice class and its configuration class:
public class Invoice : InvoiceBase
{
public Person User { get; set; }
}
public class InvoiceConfig : EntityTypeConfiguration<Invoice>
{
public InvoiceConfig()
{
this.Map(m => { m.MapInheritedProperties(); m.ToTable("Invoices"); });
}
}
PeriodicInvoice class and its configuration:
public class PeriodicInvoice : InvoiceBase
{
// Some extra properties.
}
public class PeriodicInvoiceConfig : EntityTypeConfiguration<PeriodicInvoice>
{
public PeriodicInvoiceConfig()
{
this.Property(x => x.SuspendOnExpire).IsRequired();
this.Map(m => { m.MapInheritedProperties(); m.toTable("PeriodicInvoices"); });
}
}
When I run the code, this error appears:
The association 'Invoice_User' between entity types 'Invoice' and 'Person' is invalid. In a TPC hierarchy independent associations are only allowed on the most derived types.
I know it means that I should include the property User to class PeriodicInvoice and don't use it in class Invoice.
But, Isn't there any other way to solve this problem?
Thanks.

In TPC inheritance you can't have a field in parent class that points to another table because you are trying to point two tables to another table and one table that tries to point to one of these two tables using only one foreign key (and that's impossible!).
I suggest you to use TPT. This link can help you.

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.

Inheritance of EntityTypeConfiguration across different Projects in Entity Framework

I have a 'master' Visual Studio project which contains Entity Framework mappings similar to:
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
// Table Mapping
ToTable("Users");
this.Property(x => x.Username).HasColumnName("Username");
// Other mapping goes here
}
}
The User entity being mapped is just a simple POCO:
public class User {
public string Username { get; set; }
// Other properties omitted for brevity
}
I have a second 'child' VS project/application (which references the master project) which has the following ExtendedUser Entity/POCO which is also mapped using EF:
public class ExtendedUser : User {
// Extra navigation properties
public ICollection<Order> Orders { get; set; }
}
This entity doesn't have extra fields, but does have various relationship collections which are specific to that application and only that application.
My problem is that I would like to inherit the mapping defined for the User in the first VS project. The master User class is going to be used in several other projects so I need to avoid any duplication of mappings.
If I define the mapping as:
public class ExtendedUserMap : UserMap
{
public UserMap()
{
}
}
Then I can't reference any of the ExtendedUser properties as the mapping is of type EntityTypeConfiguration<User> not EntityTypeConfiguration<ExtendedUser>.
Obviously I can't inherit from two classes, so I am unsure of a suitable way to achieve what I want to do.
How can I define ExtendedUserMap such that I can use the User mappings and also include the navigation properties for ExtendedUser?
You can define your base mapping class slightly different:
public abstract class UserMapBase<TUser> : EntityTypeConfiguration<TUser>
where TUser : User
{
protected UserMapBase()
{
// Table Mapping
ToTable("Users");
this.Property(x => x.Username).HasColumnName("Username");
// Other mapping goes here
}
}
Now you can have a subclasses like so:
public class UserMap : UserMapBase<User>
{ }
public class ExtendedUserMap : UserMapBase<ExtendedUser>
{
public ExtendedUserMap()
{
// map ExtendedUser properties here.
}
}

An Entity with identical table data

Before I elaborate the problem, I'm well aware the database isn't designed conventionally. Sadly, I can't change this particular database due to how it is integrated, so I've got a potential solution but that won't be implemented for several months. In the mean time I need to work around the following:
The problem is I need to build an Entity, this would represent our Accounts. But the problem, our database implements the following structure:
Invoiced Table
Non-Invoiced Table
My Entity, represents the exact same data on those tables, same column names, duplicate under all conditions, except one is invoiced while the other represents non-invoiced customers. But since it isn't one table, with a Flag to indicate invoiced versus non-invoiced, how can my Entity link to both of those tables?
Since both tables represent separate names, I can't use the [Table("...")] or the auto mapping capabilities. I hate asking such a question, but I can't find any documentation on how to handle such an issue.
You could use table-per-concrete class inheritance then define the table names on the derived types:
public abstract class Account
{
// common entity code here
...
}
public class InvoicedAccount : Account {}
public class NonInvoicedAccount: Account {}
public YourContext : DbContext
{
public DbSet<InvoicedAccount> InvoicedAccounts { get; set; }
public DbSet<NonInvoicedAccount> NonInvoicedAccounts { get; set; }
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Entity<InvoicedAccounts>().Map( m =>
{
m.MapInheritedProperties();
m.ToTable( "InvoicedAccountTable" );
} );
modelBuilder.Entity<NonInvoicedAccounts>().Map( m =>
{
m.MapInheritedProperties();
m.ToTable( "NonInvoicedAccountTable" );
} );
}
}

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

Base Types with EntityFramework CodeOnly

I am having a lot of trouble with 'base types' in the Code Only model of the Entity Framework. I am having a lot of trouble with 'base types' in the Code Only model of the Entity Framework.
When I try to run this code using a DbContext with a DbSet<Template>, I get the following error.
A The navigation property 'Flags' is mapped to two different join tables 'page.flags' and 'template.flags'. Only one mapping of the navigation property may exist
What this says to me is that I cannot map inherited properties. This is quite breaking to a lot of object oriented code design. Is there a known remedy? I realize I can make Layout non-abstract, and have a backing for it, but it's very obvious this is not the intention of the domain model. The abstract class is a foundational base, not the stored model.
I would like to add, if I put the IList<Flag> in the Template class, this code runs. The Id field still works, even through inheritance. I do not understand why this is happening. Can someone enlighten me?
public abstract class Layout
{
public virtual int Id
{
get;
set;
}
public virtual IList<Flag> Flags
{
get;
set;
}
}
public class Template : Layout
{
public virtual string Name
{
get;
set;
}
}
public class Page: Layout
{
}
public class LayoutConfiguration : EntityConfiguration<Layout>
{
public LayoutConfiguration()
{
HasKey(u => u.Id);
Property(u => u.Id).IsIdentity();
MapHierarchy().Case<Page>(c => new
{
c.Id
}).ToTable("Pages");
MapHierarchy().Case<Template>(c => new
{
c.Id,
c.Name
}).ToTable("Templates");
}
}
public class TemplateConfiguration : EntityConfiguration<Template>
{
public TemplateConfiguration()
{
Property(o => o.Name).HasMaxLength(64).IsUnicode();
HasMany(u => u.Flags).WithOptional()
.Map("template.flags",
(template, flag) => new {
Template = template.Id,
Flag = flag.Id
});
MapSingleType(c => new {
c.Id,
c.Name
}).ToTable("templates");
}
}
public class PageConfiguration : EntityConfiguration<Page>
{
public PageConfiguration()
{
HasMany(c => c.Flags).WithOptional()
.Map("page.flags",
(page, flag) => new
{
Page = page.Id,
Flag = flag.Id
});
}
}
When you use base type for your Template entity, you also have to model this inheritance in mapping. It means that you have to write configuration for Layout which will map Id and Flags and configuration for Template which will map Name. There is several approaches of mapping inheritance in EF. You should probably check Table per Hiearchy.
Edit: Based on your comment you are looking for Table per Class + examples for CTP4.
Edit2: Ok. I tested your scenario with navigation property defined in abstract parent class and it really doesn't work if you are trying to map it to multiple tables.

Categories