Multiple level of inheritance in EF Code First Configuration - entity-framework

I have an abstract base class for a few entities I'm defining. One of those derived entities is actually a non-abstract base class to another entity.
Following this code:
public abstract class BaseReportEntry {
public int ReportEntryId { get; set;}
public int ReportBundleId { get; set; } //FK
public virtual ReportBundle ReportBunde { get; set; }
}
//A few different simple pocos like this one
public PerformanceReportEntry : BaseReportEntry {
public int PerformanceAbsolute { get; set; }
public double PerformanceRelative { get; set; }
}
//And one with a second level of inheritance
public ByPeriodPerformanceReportEntry : PerformanceReportEntry {
public string Period { get; set; }
}
I'm using a base EntityTypeConfiguration:
public class BaseReportEntryMap<TReportEntry> : EntityTypeConfiguration<TReportEntry>
where TReportEntry : BaseReportEntry
{
public BaseReportEntryMap()
{
this.HasKey(e => e.ReportEntryId);
this.HasRequired(e => e.ReportsBundle)
.WithMany()
.HasForeignKey(e => e.ReportsBundleId);
}
}
Presumably this works fine for the one-level of inheritance but throw the following error for that one case where it has a second level:
The foreign key component 'ReportsBundleId' is not a declared property on type 'ByPeriodPerformanceReportEntry'
public class ByPeriodPerformanceReportEntryMap : BaseReportEntryMap<ByPeriodPerformanceReportEntry>
{
public ByPeriodPerformanceReportEntryMap ()
: base()
{
this.Property(e => e.Period).IsRequired();
this.Map(m =>
{
m.MapInheritedProperties();
m.ToTable("ByPeriodPerformanceReportEntries");
});
}
}
Here's ReportBundle class if needed
public class ReportsBundle
{
public int ReportsBundleId { get; set; }
public virtual ICollection<PerformanceReportEntry> PerformanceReportEntries{ get; set; }
public virtual ICollection<ByPeriodPerformanceReportEntry> ByPeriodPerformanceReportEntries{ get; set; }
}

The problem is not so much the second level of inheritance but that PerformanceReportEntry (the base of ByPeriodPerformanceReportEntry) is an entity while BaseReportEntry (the base of PerformanceReportEntry) is not.
Your mapping would work if PerformanceReportEntry would not be an entity - i.e. its mapping is not added to the model builder configuration and you have no DbSet for this type and it would not occur in a navigation collection in ReportsBundle.
Deriving the configuration from BaseReportEntryMap<ByPeriodPerformanceReportEntry> is not possible in this case - and it is not necessary because the mapping for the base properties already happened by the BaseReportEntryMap<PerformanceReportEntry>. Therefore you can use
public class ByPeriodPerformanceReportEntryMap
: EntityTypeConfiguration<ByPeriodPerformanceReportEntry>
But I have doubt that the resulting model is as you would expect it. I don't know what the PerformanceReportEntries and ByPeriodPerformanceReportEntries collections in ReportsBundle are supposed to express. Do you expect that ByPeriodPerformanceReportEntries is a collection filtered by the subtype? Do you expect that PerformanceReportEntries contains only the ReportsEntries that are PerformanceReportEntrys but not ByPeriodPerformanceReportEntrys? Do you expect that PerformanceReportEntries contains all entries including the ByPeriodPerformanceReportEntries?
Anyway, BaseReportEntry.ReportBundle is a navigation property mapped in PerformanceReportEntry (not in ByPeriodPerformanceReportEntry). That means that the inverse navigation property in class ReportsBundle must refer to PerformanceReportEntry which is the PerformanceReportEntries navigation collection. ByPeriodPerformanceReportEntries will introduce a second one-to-many relationship between ReportsBundle and ByPeriodPerformanceReportEntry (without a navigation property in ByPeriodPerformanceReportEntry). The inverse navigation property of ByPeriodPerformanceReportEntries will NOT be BaseReportEntry.ReportBundle.
My feeling is that you should not have the ReportsBundle.ByPeriodPerformanceReportEntries collection, but I am not sure what you want to achieve exactly.
Edit
Refering to your comment that you only have these two Report types your mapping is way too complicated in my opinion. I would do the following:
Remove the BaseReportEntry class and move its properties into PerformanceReportEntry. It makes no sense to have a base class that only one single other class derives from.
Remove the ByPeriodPerformanceReportEntries from ReportsBundle, so that ReportsBundle will be:
public class ReportsBundle
{
public int ReportsBundleId { get; set; }
public virtual ICollection<PerformanceReportEntry>
PerformanceReportEntries { get; set; }
}
Remove the BaseReportEntryMap and move the mapping into PerformanceReportEntryMap. Derive this map from EntityTypeConfiguration<PerformanceReportEntry>.
Correct the mapping. Currently it is wrong because you don't specify the inverse navigation property in WithMany. PerformanceReportEntryMap should look like this:
public class PerformanceReportEntryMap
: EntityTypeConfiguration<PerformanceReportEntry>
{
public PerformanceReportEntryMap()
{
this.HasKey(e => e.ReportEntryId);
this.HasRequired(e => e.ReportsBundle)
.WithMany(b => b.PerformanceReportEntries)
.HasForeignKey(e => e.ReportsBundleId);
}
}
Derive ByPeriodPerformanceReportEntryMap from EntityTypeConfiguration<ByPeriodPerformanceReportEntry> and specify only mappings for properties that are declared in ByPeriodPerformanceReportEntry, not again for the base properties. That already happened in PerformanceReportEntryMap. You don't need and can't specify it again because it will cause exactly the exception you had.
Use Table-Per-Hierarchy (TPH) inheritance instead of Table-Per-Concrete-Type (TPC), especially if you only have a few properties declared in ByPeriodPerformanceReportEntry. TPC is more difficult to use because it has problems with database-generated identities and with polymorphic associations (which you have in your relationship between PerformanceReportEntry and ReportsBundle). The problems are explained in more details here. TPH instead offers the best performance. ByPeriodPerformanceReportEntryMap would then look like this:
public class ByPeriodPerformanceReportEntryMap
: EntityTypeConfiguration<ByPeriodPerformanceReportEntry>
{
public ByPeriodPerformanceReportEntryMap()
{
this.Property(e => e.Period).IsRequired();
}
}
No explicit configuration for TPH is necessary because it is the default inheritance mapping.

Related

How do you implement a base and derived class where primary key is defined in the base class

Given the following code example, how do you implement this in entity framework core where you do not want a table created for the base class but you do for the derived class and the primary key is defined in the base class?
public class JobBase
{
public JobBase() { }
public Guid JobId { get; set; } = Guid.NewGuid();
public string Title { get; set; }
}
public class Job : JobBase
{
public Job() { }
public String AdditionalInformation { get; set; }
}
And here is what I have in my DBContext class:
public DbSet<Job> Jobs { get; set; }
var job = mb.Entity<Job>();
job.HasKey(aa => aa.JobId);
job.Property(aa => aa.JobId).HasColumnName("JobId");
I currently get the following error when trying to add-migration:
A key cannot be configured on 'Job' because it is a derived type. The key must be configured on the root type 'JobBase'. If you did not intend for 'JobBase' to be included in the model, ensure that it is not referenced by a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation on a type that is included in the model.
The only part of the error message I am doing is JobBase is referenced from a navigation on a type that is included in the model but doing that is the whole reason i implemented this relationship in the first place so i can't just remove that navigation.
modelBuilder.Entity<Job>().HasKey(x => x.JobId).ToTable("Job");
EF Inheritance and Primary Keys

Entity Framework 6.1 Unidirectional Navigation Property 0 to Many with Fluent API

I'm new to EF and am having issues trying to create a unidirectional navigation association (0 to many) using the Fluent API. Here are simplified versions of the classes:
public partial class Company
{
public int Id { get; set; }
// "Company" is NOT REQUIRED to have any BillingInfo records/objects
public virtual IList<BillingInfo> BillingInfos { get; set; }
}
public partial class BillingInfo
{
public int Id { get; set; }
// A "BillingInfo" requires ONE "Company"
public int Company_Id { get; set; }
}
I'm using EF 6.1 Code First with migrations enabled along with SQL Server 2012.
In my derived EntityTypeConfiguration classes for "Company" and "BillingInfo", I've tried every which way I can think of to achieve:
A Company DOES NOT REQUIRE any BillingInfo records, but MAY HAVE MANY.
A BillingInfo DOES REQUIRE only ONE Company.
Maintain a unidirectional navigation between Company and BillingInfo. (don't want to have a Company navigation property on BillingInfo)
EF Migration creates the NON-NULLABLE Company_Id field in database, WITH a defined ForeignKey constraint.
All the methods I've tried, only gets me partially there. The closest I've come is this (but the only thing missing is the foreignkey constraint isn't created):
class CompanyConfig : EntityTypeConfiguration<Company>
{
public CompanyConfig()
{
this.HasOptional(company => company.BillingInfos)
.WithMany()
.Map(m => m.MapKey("Company_Id"));
}
}
Any ideas???
I think you should use the following code:
private class CompanyMapping : EntityTypeConfiguration<Company>
{
public CompanyMapping()
{
this.HasMany(o => o.BillingInfos).WithOptional().HasForeignKey(fk => fk.Company_Id);
}
}
private class BillingInfoMapping : EntityTypeConfiguration<BillingInfo>
{
public BillingInfoMapping()
{
this.HasOptional(o => o.Company).WithMany(c=>c.BillingInfos).HasForeignKey(fk => fk.Company_Id);
}
}

In Entity Framework, is it possible to auto-map a column to an entity property? ID => [tablename]ID

I want to use .Id in my entity classes for the unique id, but our dba wants [tablename]Id in the database tables. Is there a way that Entity Framework can make this mapping automatically without having to create a new map file for every entity?
As long as I understand you correctly, you have something like:
public class Foo
{
public Int32 ID { get; set; }
// ...
}
public class Bar
{
public Int32 ID { get; set; }
// ...
}
And, without too much effort (or creating multiple entityTypeConfiguration<T> models) you'd like something along the lines of the following outcome:
Current Mapping Desired Mapping
[Foo] [Foo]
ID FooID
... ...
[Bar] [Bar]
ID BarID
... ...
For this, a few methods exist (and depend on which version of EF you're using). With that said, some approachable tactics:
ColumnAttribute
You can visit each entity model and decorate the ID property with the ColumnAttribute. This tells EF that, despite what we named the column, we want something else to be the name within the database. e.g.
public class Foo
{
[Column("FooID")]
public Int32 ID { get; set; }
// ...
}
public class Foo
{
[Column("BarID")]
public Int32 ID { get; set; }
// ...
}
The only problem here is that you're now going to every model and adding the attribute.
OnModelCreating & Fluent Mapping
Another method is to do the mapping but keep it all in one place. The OnModelCreating event is great for this kind of thing.
public class MyDbContext : DbContext
{
public Dbset<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
protected override void OnmodelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Foo>()
.Property(x => x.ID).HasColumnName("FooID");
modelBuilder.Entity<Bar>()
.Property(x => x.ID).HasColumnName("BarID");
}
}
Again, the problem here is that you're creating a configuration for each entity.
Custom Conventions
As of EF6, you can use Custom Conventions which make things easier (Including developing your own convention that would make ID=TableNameID). Unfortunately I don't have the time to write an example, but the docs are pretty enlightening.
According to MSDN , both way should work.
Primary key detection is case insensitive. Recognized naming patterns
are, in order of precedence: 'Id' [type name]Id

Table-per-Hierarchy and Many to Many relationship

this may seem very easy to solve, but the complication I'm having is that I have a table per hierarchy to store all of the entities and I'm not able to create the relationships over the same table. Here is what I have in the DB and classes:
I have just one table named BaseObject with an ID, name and type. I will create two classes for those entities stored in there. Master and Component. The type column is the discriminator. I have another table to store the relationships between both: A master can have many components and a component also can have many other components.
This is the code I have for the classes:
public partial class BaseObject
{
public BaseObject()
{
}
public System.Guid ID { get; set; }
public string Name { get; set; }
public Nullable<int> Type { get; set; }
}
public class MasterObject : BaseObject
{
public virtual ICollection<ComponentObject> Components { get; set; }
public MasterObject()
{
this.Components = new List<ComponentObject>();
}
}
public class ComponentObject : BaseObject
{
public virtual ICollection<MasterObject> MasterObjects { get; set; }
public ComponentObject()
{
this.MasterObjects = new List<MasterObject>();
}
}
And these are the mappings:
public class BaseObjectMap : EntityTypeConfiguration<BaseObject>
{
public BaseObjectMap()
{
// Primary Key
this.HasKey(t => t.ID);
// Table & Column Mappings
this.ToTable("BaseObject");
this.Property(t => t.ID).HasColumnName("ID");
this.Property(t => t.Name).HasColumnName("Name");
//configure the inheritance in here
this.Map<MasterObject>(m => m.Requires("Type").HasValue(1));
this.Map<ComponentObject>(m => m.Requires("Type").HasValue(2));
}
}
public class MasterObjectMap : EntityTypeConfiguration<MasterObject>
{
public MasterObjectMap()
{
this.HasMany<ComponentObject>(t => t.Components)
.WithMany(t => t.MasterObjects)
.Map(c =>
{
c.ToTable("ObjectComponents");
c.MapLeftKey("ComponentObjectID");
c.MapRightKey("BaseObjectID");
});
}
}
public class ComponentObjectMap : EntityTypeConfiguration<ComponentObject>
{
public ComponentObjectMap()
{
this.HasMany<MasterObject>(t => t.MasterObjects)
.WithMany(t => t.Components)
.Map(m =>
{
m.ToTable("ObjectComponents");
m.MapLeftKey("BaseObjectID");
m.MapRightKey("ComponentObjectID");
});
}
}
The thing is that when quering the DB, I can get a Master by accessing the DBSet Masters, but the Component collection always gives a non-sense exception saying that "Problem in mapping fragments starting at line 6:Condition member 'BaseObject.Type' with a condition other than 'IsNull=False' is mapped. Either remove the condition on BaseObject.Type or remove it from the mapping."
I don't understand what's happening. Of course if the classes where pointing each to a table, this would be very easy, but I suspect that is the root of my problem.
Also, I'm just starting with EF. I wanted to create the classes based on my existing DB that I wouldn't like to modify at all. Unless it's really needed. Please guide me if what I'm trying to do is right or wrong, or what should I do first to fully implement EF on this project that is currently using NHibernate.
Any help here? Thanks
Finally I found the issue thanks to this answer: EF4.1 Exception creating Database with table-per-hierarchy inheritance
The problem is in the mapping of the Base class. When using a base class and a discriminator to get the children classes, the discriminator MUST NOT BE a property in neither instance. Just removed the discriminator as property and it worked fine. In my example, column 'Type' is the discriminator.
Hope this helps to someone else.

ADO EF Code First Generic Intermediate Class Inheritance mapping

I've got the following requirement that works well in the OO space but I can't seem to get it to map back to the DB using ADO EF code first.
I have numrous products each will have different aspects (attributes but not in the sense of code attributes). For instance ring would have aspects such as mineral type = gold etc whilst a diamond would have an aspec of clarity = VVSI1.
As you can see the products very greatly in thier composition and I want a dynamic way of growing my system.
As such I've created a product class:
public class Product
{
public int id { get; set; }
public string Name { get; set; }
private List<ProductAspect> aspects = new List<ProductAspect>();
public List<ProductAspect> Aspects { get { return aspects; } set { aspects = value; } }
}
It has a list of ProductAspect which is the base class for all aspects moving forward:
public class ProductAspect
{
public int id { get; set; }
public string AspectName { get; set; }
}
I then inherit from the ProductAspect using a generic which alows me to be specific (strongly typed) about my Aspect Value:
public abstract class ProductAspect<T> : ProductAspect
{
public T AspectValue { get; set; }
}
I then create some Aspects that will allow me to decorate my product:
public class StringAspect : ProductAspect<string> { };
public class DecimalAspect : ProductAspect<decimal> { };
public class ImageAspect : ProductAspect<byte[]> { };
I then give the DbContext a try and have tried both TPH and TPC inheritance mappings.
Neither seem to work. The DB model that get's generated doesn't create a foriegn key to the StringAspect or DecimalAspect tables from the Aspect Table.
public class IxamDataContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<ProductAspect> Aspects { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
AspectMapping(modelBuilder);
}
private void AspectMapping(DbModelBuilder mb)
{
//TPH
//mb.Entity<ProductAspect>()
// .Map<StringAspect>(m => m.Requires("type").HasValue("sa"))
// .Map<DecimalAspect>(m => m.Requires("type").HasValue("da"));
//TPC
//mb.Entity<StringAspect>().ToTable("StringAspect");
//mb.Entity<DecimalAspect>().ToTable("DecimalAspect");
}
}
Resulting in the following exception for this Seeding code:
Product p = new Product();
p.Name = "Diamond";
p.Aspects.Add(new StringAspect() { AspectName = "History", AspectValue = "Old and long" });
p.Aspects.Add(new DecimalAspect() { AspectName = "Weight", AspectValue= 96.5M });
context.Products.Add(p);
context.SaveChanges();
Excpetion:
EntityType 'StringAspect' does not
exist in the EntitySet
'IxamDataContext.Aspects'. Parameter
name: entity
Any ideas from the EF code first pros out there?
Entity framework doesn't support intermediate non mapped types in inheritance hierarchy. It means that you can't have this inheritance: A (mapped) -> B (not mapped) -> C (mapped). EF also doesn't support mapping generic types. It means that you must remove your generic intermediate class from the hierarchy and move AspectValue to derived types with correct type.
Maybe it's to late, but I would offer you using ComplexType attribute it will allows you to extend your types as you wish.