Should I map both sides of bidirectional relations in EF code first? - c#

Assume I have the following entity classes:
public class Customer {
public int Id { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
public class Order {
public int Id { get; set; }
public virtual Customer Customer { get; set; }
}
How should those be mapped in Entity Framework 6 fluent code-first mapping? I want to be explicit about the mapping and not rely on automatic mapping conventions.
Option 1
Just map the local properties of both classes. That's how I would do it in Fluent NHibernate.
public class CustomerMap : EntityTypeConfiguration<Customer> {
public CustomerMap() {
HasMany(x => x.Orders);
}
}
public class OrderMap : EntityTypeConfiguration<Order> {
public OrderMap() {
HasRequired(x => x.Customer);
}
}
Option 2
Map both sides of the relationship in both classes.
public class CustomerMap : EntityTypeConfiguration<Customer> {
public CustomerMap() {
HasMany(x => x.Orders).WithRequired(x => x.Customer);
}
}
public class OrderMap : EntityTypeConfiguration<Order> {
public OrderMap() {
HasRequired(x => x.Customer).WithMany(x => x.Orders);
}
}
Option 3
Map both sides of the relation, but only in one of the classes. The code would be similar to option 2, just one of the two constructors would be empty.
Is there any difference between those options? If yes, please also explain why I should or shouldn't use a specific option.

I would go for option 3.
In option 1 you can forget to map the inverse end of an association. In this simple example it's clear that Order.Customer and Customer.Orders are two ends of the same association. When things get more complex, this isn't always obvious. Also, it is redundant code.
In option 2 you could have conflicting mappings. For instance when you have...
HasOptional(x => x.Customer).WithMany(x => x.Orders);
...in OrderMap, you will get a runtime exception telling you that both mappings don't match. And again, it is redundant code.
So option 3 is DRY and safe. The only issue is that it's a bit arbitrary where to configure the mappings. I tend to adhere to mapping children in their parent's mapping.
One more comment. You may want to add a primitive property CustomerId in Order. The mapping would look like:
public class CustomerMap : EntityTypeConfiguration<Customer>
{
public CustomerMap()
{
HasMany(x => x.Orders).WithRequired(x => x.Customer)
.HasForeignKey(o => o.CustomerId);
}
}
Now you have full control over both ends of the association and the foreign key name to be used. Besides that, there are some advantages of these foreign key associations as opposed to independent associations (without a primitive foreign key property). For instance, the ability to establish an association without having to fetch the parent object from the database. You can just by set an Id value.

Related

Can I use an Interface with a Foreign Key in EF Core and set it as a foreign key using Fluent API?

I am trying to restrict a couple of generic methods to only be allowed Entities that inherit from the IParentOf<TChildEntity> interface, as well as accessing an Entity's Foreign Key (ParentId) Generically.
To demonstrate;
public void AdoptAll<TParentEntity, TChildEntity>(TParentEntity parent,
TParentEntity adoptee)
where TParentEntity : DataEntity, IParentOf<TChildEntity>
where TChildEntity : DataEntity, IChildOf<TParentEntity>
{
foreach (TChildEntity child in (IParentOf<TChildEntity>)parent.Children)
{
(IChildOf<TParentEntity)child.ParentId = adoptee.Id;
}
}
A child entity class model would look like this,
public class Account : DataEntity, IChildOf<AccountType>, IChildOf<AccountData>
{
public string Name { get; set; }
public string Balance { get; set; }
// Foreign Key and Navigation Property for AccountType
int IChildOf<AccountType>.ParentId{ get; set; }
public virtual AccountType AccountType { get; set; }
// Foreign Key and Navigation Property for AccountData
int IChildOf<AccountData>.ParentId{ get; set; }
public virtual AccountData AccountData { get; set; }
}
First of all, is this possible to do? Or will it breakdown in EF?
Secondly, since the Foreign Keys do not follow convention (and there are multiple) how do I set them via Fluent Api? I can see how to do this in Data Annotations.
I hope this is clear, I have been considering it for a while and trying to work round it, so I can follow my argument, but it may not be clearly conveyed, so please ask for clarification if needed. My reason for wanting to do this is to make the code safe as well as automating a lot of the manual changing of classes necessary to add new associations and entities.
Thanks.
Edit
I decided to create some basic classes to implement this idea and test it, my code is as follows.
public abstract class ChildEntity : DataEntity
{
public T GetParent<T>() where T : ParentEntity
{
foreach (var item in GetType().GetProperties())
{
if (item.GetValue(this) is T entity)
return entity;
}
return null;
}
}
public abstract class ParentEntity : DataEntity
{
public ICollection<T> GetChildren<T>() where T : ChildEntity
{
foreach (var item in GetType().GetProperties())
{
if (item.GetValue(this) is ICollection<T> collection)
return collection;
}
return null;
}
}
public interface IParent<TEntity> where TEntity : ChildEntity
{
ICollection<T> GetChildren<T>() where T : ChildEntity;
}
public interface IChild<TEntity> where TEntity : ParentEntity
{
int ForeignKey { get; set; }
T GetParent<T>() where T : ParentEntity;
}
public class ParentOne : ParentEntity, IParent<ChildOne>
{
public string Name { get; set; }
public decimal Amount { get; set; }
public virtual ICollection<ChildOne> ChildOnes { get; set; }
}
public class ParentTwo : ParentEntity, IParent<ChildOne>
{
public string Name { get; set; }
public decimal Value { get; set; }
public virtual ICollection<ChildOne> ChildOnes { get; set; }
}
public class ChildOne : ChildEntity, IChild<ParentOne>, IChild<ParentTwo>
{
public string Name { get; set; }
public decimal Balance { get; set; }
int IChild<ParentOne>.ForeignKey { get; set; }
public virtual ParentOne ParentOne { get; set; }
int IChild<ParentTwo>.ForeignKey { get; set; }
public virtual ParentTwo ParentTwo { get; set; }
}
Data Entity simply gives each entity an Id property.
I have standard Generic Repositories set up with a Unit of Work class for mediating. The AdoptAll method looks like this in my program.
public void AdoptAll<TParentEntity, TChildEntity>(TParentEntity parent,
TParentEntity adoptee, UoW uoW)
where TParentEntity : DataEntity, IParent<TChildEntity>
where TChildEntity : DataEntity, IChild<TParentEntity>
{
var currentParent = uoW.GetRepository<TParentEntity>().Get(parent.Id);
foreach (TChildEntity child in currentParent.GetChildren<TChildEntity>())
{
child.ForeignKey = adoptee.Id;
}
}
This seems to work correctly and without faults (minimal testing) are there any major flaws in doing this?
Thanks.
Edit Two
This is the OnModelCreating Method in the DbContext, which sets up the foreign key for each entity. Is this problematic?
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentOne)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentOne>)fk).ForeignKey);
modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentTwo)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentTwo>)fk).ForeignKey);
}
According to the updated example, you want to hide the explicit FK from the entity class public interface, and still let it be visible to EF Core and mapped to the FK column in the database.
The first problem is that the explicitly implemented interface member is not directly discoverable by EF. Also it has no good name, so the default conventions don't apply.
For instance, w/o fluent configuration EF Core will correctly create one to many associations between Parent and Child entities, but since it won't discover the int IChild<Parent>.ForeignKey { get; set; } properties, it would maintain the FK property values through ParentOneId / ParentTwoId shadow properties and not through interface explicit properties. In other words, these properties will not be populated by EF Core and also not considered by the change tracker.
To let EF Core use them you need to map both FK property and database column name using respectively HasForeignKey and HasColumnName fluent API method overloads accepting string property name. Note that the string property name must be fully qualified with the namespace. While Type.FullName provides that string for non-generic types, there is no such property/method for generic types like IChild<ParentOne> (the result has to be "Namespace.IChild<Namespace.ParentOne>"), so let first create some helpers for that:
static string ChildForeignKeyPropertyName<TParent>() where TParent : ParentEntity
=> $"{typeof(IChild<>).Namespace}.IChild<{typeof(TParent).FullName}>.{nameof(IChild<TParent>.ForeignKey)}";
static string ChildForeignKeyColumnName<TParent>() where TParent : ParentEntity
=> $"{typeof(TParent).Name}Id";
The next would be creating a helper method for performing the necessary configuration:
static void ConfigureRelationship<TChild, TParent>(ModelBuilder modelBuilder)
where TChild : ChildEntity, IChild<TParent>
where TParent : ParentEntity, IParent<TChild>
{
var childEntity = modelBuilder.Entity<TChild>();
var foreignKeyPropertyName = ChildForeignKeyPropertyName<TParent>();
var foreignKeyColumnName = ChildForeignKeyColumnName<TParent>();
var foreignKey = childEntity.Metadata.GetForeignKeys()
.Single(fk => fk.PrincipalEntityType.ClrType == typeof(TParent));
// Configure FK column name
childEntity
.Property<int>(foreignKeyPropertyName)
.HasColumnName(foreignKeyColumnName);
// Configure FK property
childEntity
.HasOne<TParent>(foreignKey.DependentToPrincipal.Name)
.WithMany(foreignKey.PrincipalToDependent.Name)
.HasForeignKey(foreignKeyPropertyName);
}
As you can see, I'm using EF Core provided metadata services to find the names of the corresponding navigation properties.
But this generic method actually shows the limitation of this design. The generic constrains allow us to use
childEntity.Property(c => c.ForeignKey)
which compiles fine, but doesn't work at runtime. It's not only for fluent API methods, but basically any generic method involving expression trees (like LINQ to Entities query). There is no such problem when the interface property is implemented implicitly with public property.
We'll return to this limitation later. To complete the mapping, add the following to your OnModelCreating override:
ConfigureRelationship<ChildOne, ParentOne>(modelBuilder);
ConfigureRelationship<ChildOne, ParentTwo>(modelBuilder);
And now EF Core will correctly load / take into account your explicitly implemented FK properties.
Now back to limitations. There is no problem to use generic object services like your AdoptAll method or LINQ to Objects. But you can't access these properties generically in expressions used to access EF Core metadata or inside LINQ to Entities queries. In the later case you should access it through navigation property, or in both scenarios you should access in by the name returned from the ChildForeignKeyPropertyName<TParent>() method. Actually queries will work, but will be evaluated locally thus causing performance issues or unexpected behaviors.
E.g.
static IEnumerable<TChild> GetChildrenOf<TChild, TParent>(DbContext db, int parentId)
where TChild : ChildEntity, IChild<TParent>
where TParent : ParentEntity, IParent<TChild>
{
// Works, but causes client side filter evalution
return db.Set<TChild>().Where(c => c.ForeignKey == parentId);
// This correctly translates to SQL, hence server side evaluation
return db.Set<TChild>().Where(c => EF.Property<int>(c, ChildForeignKeyPropertyName<TParent>()) == parentId);
}
To recap shortly, it's possible, but use with care and make sure it's worth for the limited generic service scenarios it allows. Alternative approaches would not use interfaces, but (combination of) EF Core metadata, reflection or Func<...> / Expression<Func<..>> generic method arguments similar to Queryable extension methods.
Edit: Regarding the second question edit, fluent configuration
modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentOne)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentOne>)fk).ForeignKey);
modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentTwo)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentTwo>)fk).ForeignKey);
produces the following migration for ChildOne
migrationBuilder.CreateTable(
name: "ChildOne",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
ForeignKey = table.Column<int>(nullable: false),
Name = table.Column<string>(nullable: true),
Balance = table.Column<decimal>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChildOne", x => x.Id);
table.ForeignKey(
name: "FK_ChildOne_ParentOne_ForeignKey",
column: x => x.ForeignKey,
principalTable: "ParentOne",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ChildOne_ParentTwo_ForeignKey",
column: x => x.ForeignKey,
principalTable: "ParentTwo",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
Note the single ForeignKey column and the attempt to use it as foreign key to both ParentOne and ParentTwo. It suffers the same problems as using a constrained interface property directly, so I would assume it not working.

Tracking changes in Entity Framework for many-to-many relationships with behavior

I'm currently attempting to use Entity Framework's ChangeTracker for auditing purposes. I'm overriding the SaveChanges() method in my DbContext and creating logs for entities that have been added, modified, or deleted. Here is the code for that FWIW:
public override int SaveChanges()
{
var validStates = new EntityState[] { EntityState.Added, EntityState.Modified, EntityState.Deleted };
var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseEntity && validStates.Contains(x.State));
var entriesToAudit = new Dictionary<object, EntityState>();
foreach (var entity in entities)
{
entriesToAudit.Add(entity.Entity, entity.State);
}
//Save entries first so the IDs of new records will be populated
var result = base.SaveChanges();
createAuditLogs(entriesToAudit, entityRelationshipsToAudit, changeUserId);
return result;
}
This works great for "normal" entities. For simple many-to-many relationships, however, I had to extend this implementation to include "Independent Associations" as described in this fantastic SO answer which accesses changes via the ObjectContext like so:
private static IEnumerable<EntityRelationship> GetRelationships(this DbContext context, EntityState relationshipState, Func<ObjectStateEntry, int, object> getValue)
{
context.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
return objectContext
.ObjectStateManager
.GetObjectStateEntries(relationshipState)
.Where(e => e.IsRelationship)
.Select(
e => new EntityRelationship(
e.EntitySet.Name,
objectContext.GetObjectByKey((EntityKey)getValue(e, 0)),
objectContext.GetObjectByKey((EntityKey)getValue(e, 1))));
}
Once implemented, this also worked great, but only for many-to-many relationships that use a junction table. By this, I'm referring to a situation where the relationship is not represented by a class/entity, but only a database table with two columns - one for each foreign key.
There are certain many-to-many relationships in my data model, however, where the relationship has "behavior" (properties). In this example, ProgramGroup is the many-to-many relationship which has a Pin property:
public class Program
{
public int ProgramId { get; set; }
public List<ProgramGroup> ProgramGroups { get; set; }
}
public class Group
{
public int GroupId { get; set; }
public IList<ProgramGroup> ProgramGroups { get; set; }
}
public class ProgramGroup
{
public int ProgramGroupId { get; set; }
public int ProgramId { get; set; }
public int GroupId { get; set; }
public string Pin { get; set; }
}
In this situation, I'm not seeing a change to a ProgramGroup (eg. if the Pin is changed) in either the "normal" DbContext ChangeTracker, nor the ObjectContext relationship method. As I step through the code, though, I can see that the change is in the ObjectContext's StateEntries, but it's entry has IsRelationship=false which, of course, fails the .Where(e => e.IsRelationship) condition.
My question is why is a many-to-many relationship with behavior not appearing in the normal DbContext ChangeTracker since it's represented by an actual class/entity and why is it not marked as a relationship in the ObjectContext StateEntries? Also, what is the best practice for accessing these type of changes?
Thanks in advance.
EDIT:
In response to #FrancescCastells's comment that perhaps not explicitly defining a configuration for the ProgramGroup is cause of the problem, I added the following configuration:
public class ProgramGroupConfiguration : EntityTypeConfiguration<ProgramGroup>
{
public ProgramGroupConfiguration()
{
ToTable("ProgramGroups");
HasKey(p => p.ProgramGroupId);
Property(p => p.ProgramGroupId).IsRequired();
Property(p => p.ProgramId).IsRequired();
Property(p => p.GroupId).IsRequired();
Property(p => p.Pin).HasMaxLength(50).IsRequired();
}
And here are my other configurations:
public class ProgramConfiguration : EntityTypeConfiguration<Program>
{
public ProgramConfiguration()
{
ToTable("Programs");
HasKey(p => p.ProgramId);
Property(p => p.ProgramId).IsRequired();
HasMany(p => p.ProgramGroups).WithRequired(p => p.Program).HasForeignKey(p => p.ProgramId);
}
}
public class GroupConfiguration : EntityTypeConfiguration<Group>
{
public GroupConfiguration()
{
ToTable("Groups");
HasKey(p => p.GroupId);
Property(p => p.GroupId).IsRequired();
HasMany(p => p.ProgramGroups).WithRequired(p => p.Group).HasForeignKey(p => p.GroupId);
}
When these are implemented, EF still does not show the modified ProgramGroup in the ChangeTracker.
While the concept of "relationship with attributes" is mentioned in the theory of entity-relationship modelling, as far as Entity Framework is concerned, your ProgramGroup class is an entity. You're probably unwittingly filtering it out with the x.Entity is BaseEntity check in the first code snippet.
I believe the problem lies in the definition of your Program and Group class and overridden SaveChanges method. With the current definition of the classes the EF is unable to use change tracking proxies, that catch changes as they are being made. Instead of that the EF relies on the snapshot change detection, that is done as part of SaveChanges method. Since you call base.SaveChanges() at the end of the overridden method, the changes are not detected yet when you request them from ChangeTracker.
You have two options - you can either call ChangeTracker.DetectChanges(); at the beginning of the SaveChanges method or change definition of your classes to support change tracking proxies.
public class Program {
public int ProgramId { get; set; }
public virtual ICollection<ProgramGroup> ProgramGroups { get; set; }
}
public class Group {
public int GroupId { get; set; }
public virtual ICollection<ProgramGroup> ProgramGroups { get; set; }
}
The basic requirements for creating change tracking proxies are:
A class must be declared as public
A class must not be sealed
A class must not be abstract
A class must have a public or protected constructor that does not have parameters.
A navigation property that represents the "many" end of a relationship must have public virtual get and set accessors
A navigation property that represents the "many" end of a relationship must be defined as ICollection<T>
Entity Framework represents many-to-many relationships by not having entityset for the joining table in CSDL, instead it manages this through mapping.
Note: Entity framework supports many-to-many relationship only when the joining table does NOT include any columns other than PKs of both the tables
you should have to define navigation property yourself to coupe with this proplem.
this link can be of your help.

Entity Framework Self Referencing Using Non-Primary Key Column

I have an employee table that self references to determine organization structure. I'm having some trouble trying to set this up using Code-First (POCO) fluently.
An employee record has both a "Position" field and a "ReportsTo" field and neither of the columns are the primary key (employee.id).
An employee with a "ReportsTo" value of "08294" , is an employee of a direct report of an employee with "Position" value of "08294".
Can anyone offer up some info on how to set this up using EF code first, fluently...is it possible?
I tried the code below and am getting error:
Employee_Employees_Source_Employee_Employees_Target: : The types of
all properties in the Dependent Role of a referential constraint must
be the same as the corresponding property types in the Principal Role.
The type of property 'ReportsTo' on entity 'Employee' does not match
the type of property 'Id' on entity 'Employee' in the referential
constraint 'Employee_Employees'.
Employee.cs
public class Employee
{
public int Id { get; set; } //pk
public string Position { get; set; } // i.e. 06895
public string ReportsTo{ get; set; } // i.e. 08294
public virtual Employee Supervisor { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
DbContext
modelBuilder.Entity<Employee>()
.HasMany(e => e.Employees)
.WithOptional(e => e.Supervisor)
.HasForeignKey(e => e.ReportsTo);
I think more than anything, I would like to keep the POCO free of EF "stuff" and be able to do something like:
employee.IsSupervisor(); // based on child employee count.
The issue is in the relationship configuration. If you want to configure your one to many relation without using a FK, you could do this:
modelBuilder.Entity<Employee>()
.HasMany(e => e.Employees)
.WithOptional(e => e.Supervisor);
Now if you want to use a FK property, then add this property to your model class:
public class Employee
{
//...
public int SupervisorId { get; set; }
}
And map your relationship this way:
modelBuilder.Entity<Employee>()
.HasMany(e => e.Employees)
.WithOptional(e => e.Supervisor)
.HasForeignKey(e => e.SupervisorId);
To resolve your issue related with ReportTo and Position properties,I think you should handle that logic in your code. If you want to know if an Employee is a supervisor based on the count of Employees property, you could use a NotMapped property:
public class Employee
{
[NotMapped]
public bool IsSupervisor
{
get
{
return Employess.Count>0
}
}
}
You can do the same using Fluent Api:
modelBuilder.Entity<Employee>().Ignore(e => e.IsSupervisor);
PS: Remember initialize Employees in your class'constructor.
The error you get is because it is trying to map a PK of int type to a FK of string type. User int for all of your key fields.
Then, you need to declare your OnModelBuilding like this:
modelBuilder.Entity<Employee>()
.HasOptional(e => e.Supervisor)
.WithMany()
.HasForeignKey(s => s.ReportsTo);
To get something like IsSupervisor() you can take advantage of partial classes. Create another class file which is a public partial class Employee (and modify your original one to be partial), then in your new file you will add a property that does whatever you want, and decorate it with [NotMapped] attribute. Yours will probably look something like public bool IsSupervisor {get { return (Employees == null) ? false : true; } set {} } The new partial class is where you can do whatever you want for the POCO without changing the EF class (make sure you use [NotMapped] though).

Conditional Mapping on Relationships

Using Entity Framework (Code First), I'm trying to map a conditional/filtered relationship between the following 2 entities:
Building
BuildingId
BuildingName
Area
AreaId
ParentId
AreaName
IsSubArea
A Building can have many Areas
An Area can have many (Sub)Areas
I would like to create the relationship between Building and Area where the areas marked with 'IsSubArea' are filtered out of the relationship. In this context, ParentId would relate to a Building, otherwise, ParentId would be another Area. This would allow me to create a building with many areas, and each area could have many sub-areas, creating a tree style structure.
The closest to a solution I have found relates to 'soft delete' functionality (source):
modelBuilder.Entity<Foo>().Map(m => m.Requires("IsDeleted").HasValue(false));
Converted to fit my example:
modelBuilder.Entity<Area>().Map(m => m.Requires("IsSubArea").HasValue(false));
But as far as I can tell, this has no bearing on the relationship to the Building.
Another solution would be to create a property on the Building which specifies the query definition to use to return related areas (source):
public class Building
{
public int BuildingId {get; set;}
public string BuildingName {get; set;}
public IQueryable<Area> BuildingAreas
{
get
{
return from area in areas
where area.IsSubArea == false
and area.ParentId == BuildingId
select area;
//Assume I have a reference to relevant DbSets
}
}
}
This solution would work but doesn't feel as elegant as a conditional mapping.
Another solution would be to inherit from Area and create the 2 sub-classes:
BuildingArea
AreaId
BuildingId
AreaName
SubArea
AreaId
ParentAreaId
AreaName
Each inherits from Area and sets the 'IsSubArea' field as appropriate.
This solution feels tidier but I do not know how to implement this in Entity Framework.
Is there a way to specify conditional mapping on relationships?
Is there a better way to implement this structure?
Update 1:Found this & this guide on inheritance which seems to match my requirements. However, neither of these tutorials define relationships between derived types. I'll update the question tonight with what I have tried with regards to the Table per Hierarchy (TPH) method.
Update 2:
I'm going to try an describe the Table per Hierarchy (TPH) method I tried to implement based on the tutorial links above. Forgive me if this gets a little complicated (maybe I'm over thinking it).
Models
The building class remains the same as the OP.
I have created an abstract base class defining the Area properties common to each derived type (BuildingArea and SubArea):
public abstract class Area
{
protected Area(bool isSubArea)
{
IsSubArea = isSubArea;
SubAreas = new List<SubArea>();
}
public int AreaId { get; set; }
public int ParentId { get; set; }
public string AreaName { get; set; }
public bool IsSubArea { get; private set; } //note the private set
public virtual ICollection<SubArea> SubAreas { get; set; }
}
I then have 2 derived types which inherit from Area:
public class BuildingArea : Area
{
public BuildingArea () : base(false)
{}
public virtual Building ParentBuilding { get; set; }
}
public class SubArea : Area
{
public SubArea(): base(true)
{}
// This is of type `Area` because parent could be either `BuildingArea` or `SubArea`
public virtual Area Parent { get; set; }
}
I then have the following 2 EntityTypeConfigurations:
public class BuildingAreaMap : EntityTypeConfiguration<BuildingArea>
{
public BuildingAreaMap ()
{
// Primary Key
HasKey(t => t.AreaId);
// Properties
Property(t => t.AreaName)
.IsRequired()
.HasMaxLength(256);
// Table & Column Mappings
ToTable("Areas");
Property(t => t.AreaId).HasColumnName("AreaId");
Property(t => t.ParentId).HasColumnName("ParentId");
Property(t => t.AreaName).HasColumnName("AreaName");
Property(t => t.IsSubArea).HasColumnName("IsSubArea");
// This is the discriminator column
Map(m => m.Requires("IsSubArea").HasValue(false));
HasRequired(a => a.Site).WithMany(s => s.SiteAreas).HasForeignKey(k => k.ParentId);
}
public class SubAreaMap : EntityTypeConfiguration<SubArea>
{
public SubAreaMap()
{
// Primary Key
HasKey(t => t.AreaId);
// Properties
Property(t => t.AreaName)
.IsRequired()
.HasMaxLength(256);
// Table & Column Mappings
ToTable("AssetHealthAreas");
Property(t => t.AreaId).HasColumnName("AreaId");
Property(t => t.ParentId).HasColumnName("ParentId");
Property(t => t.AreaName).HasColumnName("AreaName");
Property(t => t.IsSubArea).HasColumnName("IsSubArea");
// This is the discriminator column
Map(m => m.Requires("IsSubArea").HasValue(true));
HasRequired(a => a.Parent).WithMany(s => s.SubAreas).HasForeignKey(k => k.ParentId);
}
}
This code builds successfully, but I do get the following runtime error:
Map was called more than once for type 'SiteArea' and at least one of the calls didn't specify the target table name.
But I am specifying the target table name (once in each EntityTypeConfiguration class).
So I removed the EntityTypeConfiguration for SubArea but I get the same error.
One of the tutorials pulls the mapping out of the EntityTypeConfiguration class and puts it in the OnModelCreating handler as follows:
modelBuilder.Entity<Area>()
.Map<BuildingArea>(m => m.Requires("IsSubArea").HasValue(false))
.Map<SubArea>(m => m.Requires("IsSubArea").HasValue(true));
This also gives me the same error.
If I remove relationships from the equation, I get a different error regarding the ParentId property:
The foreign key component 'ParentId' is not a declared property on type 'SiteArea'. Verify that it has not been explicitly excluded from the model and that it is a valid primitive property.
Update 3
An image of the model I'm trying to create...
Update 4
I'm going to try and simplify my model to match the following.
If the solution below works, I will need to have a little more business logic to navigate the tree but it should be manageable.
For the errors when creating the TPH classes:
I think this is because you are not supposed to have the discriminator column as a property in your classes.
Remove the property IsSubArea from your base class.
When you create new instance, EF should automatically detect the type and fill the IsSubArea accordingly.

How to map key properties to differing column types on inherited entities in EF CodeFirst?

I'm trying to implement a TPC inheritance model in EF 4.3 CodeFirst for an existing Oracle database (over which I have no control). I have several sub-types that each map to its own table. Unfortunately, some of the key columns are of datatype number(18,0) instead of integer. EF seems to hate me now.
Here's my base class:
public abstract class Vehicle
{
public virtual int Id { get; set;}
public virtual string Color { get; set; }
//more properties
}
Here are some example sub-types:
public class Car : Vehicle
{
//more properties
}
public class Truck : Vehicle
{
//more properties
}
public class Motorcycle : Vehicle
{
//more properties
}
And here's my DbContet:
public class VehicleDataContext : DbContext
{
public DbSet<Vehicle> Vehicles { get; set; }
public DbSet<Car> Cars { get; set; }
public DbSet<Truck> Trucks { get; set; }
public DbSet<Motorcycle> Motorcycles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Vehicle>().HasKey(x => x.Id);
modelBuilder.Entity<Car>().Map(m => m.MapInheritedProperties());
modelBuilder.Entity<Car>().Property(x => x.Id).HasColumnType("decimal");
modelBuilder.Entity<Truck>().Map(m => m.MapInheritedProperties());
modelBuilder.Entity<Truck>().Property(x => x.Id).HasColumnType("int");
modelBuilder.Entity<Motorcycle>().Map(m => m.MapInheritedProperties());
modelBuilder.Entity<Motorcycle>().Property(x => x.Id).HasColumnType("decimal");
base.OnModelCreating(modelBuilder);
}
}
So, I already know to MapInheritedProperties so that all the properties of the base and sub-type are mapped to one table. I'm assuming that I have to tell the base that it HasKey so that EF doesn't complain that my DbSet<Vehicle> doesn't have a key mapped. I'd like to be able to assume that I can "tell" each entity how to map its own key's column type like I've done above. But I think that's not quite it.
Here's a test that fails:
[TestFixture]
public class when_retrieving_all_vehicles
{
[Test]
public void it_should_return_a_list_of_vehicles_regardless_of_type()
{
var dc = new VehicleDataContext();
var vehicles = dc.Vehicles.ToList(); //throws exception here
Assert.Greater(vehicles.Count, 0);
}
}
The exception thrown is:
The conceptual side property 'Id' has already been mapped to a storage
property with type 'decimal'. If the conceptual side property is
mapped to multiple properties in the storage model, make sure that all
the properties in the storage model have the same type.
As mentioned above, I have no control over the database and it's types. It's silly that the key types are mixed, but "it is what it is".
How can I get around this?
You cannot achieve it through mapping. This is limitation of EF code first. You can map each property (including the key) in inheritance structure only once. Because of that you can have it either integer or decimal in all entities in the inheritance tree but you cannot mix it.
Btw. what happens if you try to use int or decimal for the whole inheritance tree? Does it fail for loading or persisting entity? If not you can simply use the one (probably decimal if it can use whole its range) for all entities.

Categories

Resources