Mapping subclass values in Entity Framework TPH - c#

I'm setting up a TPH inheritance in C# MVC for the first time, and I'm a bit confused on how to map subclass values to the table. For example, my parent entity's fluent map is declared using:
public class ProjectTaskMap : EntityTypeConfiguration<ProjectTask>
I have individual maps set up using:
Map<ProjectTL>(m => m.Requires("Type").HasValue("TL"));
As a further example: One of the subclasses needs to have a many-to-many mapping. I don't know how to configure that with TPH since I can't access the child class's properties to declare the mapping.
I can't access the subclass properties in this parent map, however (since it's calling the config of ProjectTask). I can't specify how to map a field to the table, I can't do anything with them.
What am I missing to be able to do this? This is an old system that was just upgraded to EF 6.1, so I don't have EF Designer or anything like that, only these fluent mappings.
Parent class:
public class ProjectTask : BaseEntity
{
public virtual int ProjectId { get; set; }
// various other properties
public virtual Project Project { get; set; }
public virtual ICollection<ProjectTaskFile> Files { get; set; }
}
Two child classes have none of their own properties (they're empty shells), but the third does. ECOs is part of a many-to-many relationship.
public class ProjectET : ProjectTask
{
public virtual int SalesOrderId { get; set; }
public virtual SalesOrder SalesOrder { get; set; }
public ICollection<EngChangeOrders> ECOs { get; set; }
}

When creating your model with Code First, TPH is the default strategy for the types that participate in the inheritance hierarchy. Take a look at the following structure:
public class Project
{
public int ProjectId { get; set; }
}
public class ProjectTask
{
public int ProjectTaskId { get; set; }
public virtual Project Project { get; set; }
public string SomeString { get; set; }
}
public class ProjectET : ProjectTask
{
public ICollection<Order> ECOs { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public string SomeString { get; set; }
}
Mapping:
public class Context : DbContext
{
public Context() : base("Model2")
{
}
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectET> ProjectETs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Project>()
.HasKey(i => i.ProjectId);
modelBuilder.Entity<ProjectTask>()
.HasKey(i => i.ProjectTaskId);
base.OnModelCreating(modelBuilder);
}
}
Migrations Generated
CreateTable(
"dbo.ProjectTasks",
c => new
{
ProjectTaskId = c.Int(nullable: false, identity: true),
SomeString = c.String(),
Discriminator = c.String(nullable: false, maxLength: 128),
Project_ProjectId = c.Int(),
})
.PrimaryKey(t => t.ProjectTaskId)
.ForeignKey("dbo.Projects", t => t.Project_ProjectId)
.Index(t => t.Project_ProjectId);
CreateTable(
"dbo.Orders",
c => new
{
OrderId = c.Int(nullable: false, identity: true),
SomeString = c.String(),
ProjectET_ProjectTaskId = c.Int(),
})
.PrimaryKey(t => t.OrderId)
.ForeignKey("dbo.ProjectTasks", t => t.ProjectET_ProjectTaskId)
.Index(t => t.ProjectET_ProjectTaskId);
CreateTable(
"dbo.Projects",
c => new
{
ProjectId = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.ProjectId);
Does that work for you?

Your fluent config can obviously only address a single entity type at a time, but there's nothing stopping you from adding fluent config for your subclass as well. Not sure what the complication is here.
public class ProjectETMap : EntityTypeConfiguration<ProjectET>
{
public ProjectETMap()
{
HasMany(m => m.ECOs).WithMany();
}
}

Related

Fluent API Producing Duplicate Columns

We are trying to reconfigure our EF project so that we use fluent API configuration instead of data annotations for cleaner data models. We have existing tables and I'm now attempting to validate my efforts.
Here are the old (partial) EF model declarations:
[Table("CustomerLocation")]
internal class CustomerLocationEF
{
[Column(Order = 0), Key]
public int CustomerID { get; set; }
[Column(Order = 1), Key]
[Required]
[MaxLength(ModelConstants.MaxLength128)]
[Index("IX_Customer_LocationReference", IsUnique = true)]
public string LocationReference { get; set; }
public virtual CustomerEF Customer { get; set; }
}
[Table("Customer")]
internal class CustomerEF
{
[Key]
public int CustomerID { get; set; }
public virtual ICollection<CustomerLocationEF> Locations { get; set; }
}
These work fine and produce the expected schema. However, here are the new models and configuration:
public class CustomerLocationModel
{
public int CustomerId { get; set; }
public string LocationReference { get; set; }
public virtual CustomerModel Customer { get; set; }
}
public class CustomerModel
{
public int Id { get; set; }
public virtual ICollection<CustomerLocationModel> Locations { get; set; }
}
internal sealed class CustomerLocationTypeConfiguration : EntityTypeConfiguration<CustomerLocationModel>
{
public CustomerLocationTypeConfiguration()
{
ToTable("CustomerLocation");
HasKey(x => new {x.CustomerId, x.LocationReference});
Property(x => x.CustomerId)
.HasColumnName("CustomerID");
HasRequired(x => x.Customer).WithMany().WillCascadeOnDelete(false);
}
}
However, this tries to generate a table like this:
CreateTable(
"dbo.CustomerLocation",
c => new
{
CustomerID = c.Int(nullable: false),
LocationReference = c.String(nullable: false, maxLength: 128),
CustomerModel_Id = c.Int(),
})
.PrimaryKey(t => new { t.CustomerID, t.LocationReference })
.ForeignKey("dbo.Customer", t => t.CustomerID)
.ForeignKey("dbo.Customer", t => t.CustomerModel_Id)
.Index(t => new { t.CustomerID, t.LocationReference }, unique: true, name: "IX_Customer_LocationReference")
.Index(t => t.CustomerModel_Id);
Notice the duplicate column CustomerModel_Id and associated foreign key. I've run into issues like this before with data annotations and resolved them with a [ForeignKey], but I'm new to Fluent API and unsure about what I am doing wrong here. How do I resolve this in Fluent so that it picks up my navigation property/foreign key properly?
It turns out the problem was in the mapping config for the CustomerModel, once I cleaned that up, my problem went away:
HasMany(x => x.Locations)
.WithRequired(x => x.Customer)
.HasForeignKey(x => x.CustomerId);

Code First Foreign Key error in Dependent and Principal Roles

Have been reading other SO posts on this for a while now and there's still something that isn't making sense. Making a MVC application that includes these two classes
public class ChecklistFilled
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int InternalChecklistFilledID { get; set; }
public DateTime ChecklistFilledDate { get; set; }
public int PersonnelID { get; set; }
[ForeignKey("PersonnelID")]
public virtual Personnel Personnel { get; set; }
public int EquipmentID { get; set; }
[ForeignKey("EquipmentID")]
public virtual ICollection<Equipment> Equipment { get; set; }
public virtual ItemsFilled ItemsFilled { get; set; }
}
public class ItemsFilled
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int InternalItemsFilledID { get; set; }
public bool? DeviceCondition { get; set; }
public int DeviceID { get; set; }
[ForeignKey("DeviceID")]
public virtual Device Device { get; set; }
public int ChecklistFilledID { get; set; }
[ForeignKey("ChecklistFilledID")]
public virtual ICollection<ChecklistFilled> ChecklistFilled { get; set; }
}
And the statement from my DBContext class
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ItemsFilled>()
.HasRequired(x => x.ChecklistFilled)
.WithMany()
.Map(x => x.MapKey("ChecklistFilledID"));
base.OnModelCreating(modelBuilder);
}
So far I've found a lot of posts that have solutions that are fairly specific to the programmer's problem, and nothing that is straight forward enough for me to apply here as a newer developer. From what I've seen with the DBContext setup, I have it mapping the foreign key as needed from the ChecklistFilled class into the ItemsFilled class, but when I run the enable-migrations command, I get the error
ItemsFilled_Device_Target_ItemsFilled_Device_Source: : The number of properties in the Dependent and Principal Roles in a relationship constraint must be identical
Which, taken at face value, is trying to tell me that I must have the same number of properties in each class - and that just sounds absurd to me. So I'm not sure what I need to do to solve this issue and place the ChecklistID into the ItemsFilled table as a FK
If I understood correctly, what you want is a relationship where a checklist is related to a list of items, and an item is related to one checklist.
To implement this one-to-many relationship you should redefine the properties in your entities like this:
public class ChecklistFilled
{
...
public virtual ICollection<ItemsFilled> ItemsFilled { get; set; }
}
public class ItemsFilled
{
...
public int CheckListFilledId { get; set; }
[ForeignKey("CheckListFilledId")]
public virtual ChecklistFilled ChecklistFilled { get; set; }
}
With this change you wouldn't even need an explicit mapping in OnModelInitialize. In fact, you should remove the mapping.
As a side note, in general it is better if the
base.OnModelCreating(modelBuilder); line is the first one in the method.
The tables generated by the migration look like this (I removed some non-relevant properties for clarity):
CreateTable(
"dbo.ChecklistFilleds",
c => new
{
InternalChecklistFilledID = c.Int(nullable: false, identity: true),
ChecklistFilledDate = c.DateTime(nullable: false),
})
.PrimaryKey(t => t.InternalChecklistFilledID);
CreateTable(
"dbo.ItemsFilleds",
c => new
{
InternalItemsFilledID = c.Int(nullable: false, identity: true),
DeviceCondition = c.Boolean(),
CheckListFilledId = c.Int(nullable: false),
})
.PrimaryKey(t => t.InternalItemsFilledID)
.ForeignKey("dbo.ChecklistFilleds", t => t.CheckListFilledId, cascadeDelete: true)
.Index(t => t.CheckListFilledId);
Perhaps you should consider renaming your entities, the names are a bit confusing, specially when pluralized to compose the tables names. I would suggest FilledCheckList and FilledItem.
Also, the error you get states that the problematic relationship is ItemsFilled_Device_Target_ItemsFilled_Device_Source. It is about Devices and ItemsFilled. It seems that your code includes some annotation or mapping in the Device entity that is causing a problem.

Entity Framework one-to-zero-or-one foreign key association

I am in the process of changing the back-end of an existing application to use Entity Framework Code First. I've used the built-in tool in Visual Studio 2015 to generate POCO classes based on my existing database. This worked perfectly for the most part, except for two classes, with a one-to-zero-or-one relationship. These are my (simplified) classes:
public class Login
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int TeamMemberId { get; set; }
public virtual TeamMember TeamMember { get; set; }
}
public class TeamMember
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual Login Login { get; set; }
}
With the following configuration:
public class LoginTypeConfiguration : EntityTypeConfiguration<Login>
{
public LoginTypeConfiguration()
{
this.HasRequired(e => e.TeamMember)
.WithOptional(e => e.Login);
this.Property(e => e.TeamMemberId)
.HasColumnName("TeamMember_Id");
}
}
This results in the following migration:
CreateTable(
"dbo.Logins",
c => new
{
Id = c.Int(nullable: false, identity: true),
TeamMember_Id = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.TeamMembers", t => t.Id)
.Index(t => t.Id);
For some reason EF creates a foreign key on [Logins].[Id] instead of [Logins].[TeamMember_Id]. I've already tried decorating my navigation property with a ForeignKey attribute, but this did not work. Is there a way to get it to create a foreign key on [Logins].[TeamMember_Id] instead?
I ended up creating one-to-many relationship, with a [NotMapped] property for Login.
My classes:
public class Login
{
public int TeamMemberId { get; set; }
public virtual TeamMember TeamMember { get; set; }
}
public class TeamMember
{
[NotMapped]
public virtual Login Login
{
get { return Logins.FirstOrDefault(); }
}
public virtual ICollection<Login> Logins { get; set; }
}
With the following configuration:
public class LoginTypeConfiguration : EntityTypeConfiguration<Login>
{
public LoginTypeConfiguration()
{
this.Property(e => e.TeamMemberId)
.HasColumnName("TeamMember_Id");
}
}
For that you can write your code first like this. Entity Framework will generate foreign key TeamMemberId automatically for you. For more information: Entity Framework Tutorial -
Code First Conventions
public class Login
{
public int Id { get; set; } //foreign key declaration
public int TeamMemberId { get; set; }
public TeamMember TeamMember { get; set; }
}
public class TeamMember
{
public int TeamMemberId { get; set; }
public Ilist<Login> Login { get; set; }
}

C# - Foreign key references invalid table - Basic Migration not working

Trying to make my first project in C#. However, it's very frustrating so far. This is a problem I can't solve, since I don't see anything wrong.
I'm trying to just do a simple migrate in my DB.
Users migration file
public override void Up()
{
CreateTable(
"dbo.Users",
c => new
{
user_id = c.Int(nullable: false, identity: true),
first_name = c.String(),
last_name = c.String(),
email = c.String(),
role = c.String(),
})
.PrimaryKey(t => t.user_id);
}
Locations migration file
public override void Up()
{
CreateTable(
"dbo.Locations",
c => new
{
loc_id = c.Int(nullable: false, identity: true),
loc_name = c.String(),
})
.PrimaryKey(t => t.loc_id);
CreateTable(
"dbo.UserLoc",
c => new
{
ul_id = c.Int(nullable: false, identity: true),
fk_user_id = c.Int(nullable: false),
fk_loc_id = c.Int(nullable: false),
})
.PrimaryKey(t => t.ul_id);
AddForeignKey("dbo.UserLoc", "fk_user_id", "dbo.Users", "user_id");
AddForeignKey("dbo.UserLoc", "fk_loc_id", "dbo.Locations", "loc_id");
}
Models:
User.cs
public class User
{
[Key]
public int user_id { get; set; }
public string firs_tname { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public string role { get; set; }
}
Location.cs
public class Locations
{
[Key]
public int loc_id { get; set; }
public int loc_name { get; set; }
}
UserLoc.cs
public class UserLoc
{
[Key]
public int ul_id { get; set; }
[ForeignKey("Users")]
public int fk_user_id { get; set; }
public virtual User user { get; set; }
[ForeignKey("Locations")]
public int fk_location_id { get; set; }
public virtual Locations location { get; set; }
}
Every time I want to migrate I get the same error
Foreign key 'FK_dbo.UserLoc_dbo.Users_fk_user_id' references invalid
table 'dbo.Users'. Could not create constraint or index. See previous
errors.
What am I doing wrong?
First I recommend you to change the FK name that you are using in the ForeignKey attributes for the navigation property names:
public class UserLoc
{
[Key]
public int ul_id { get; set; }
[ForeignKey("user")]
public int fk_user_id { get; set; }
public virtual User user { get; set; }
[ForeignKey("location")]
public int fk_location_id { get; set; }
public virtual Locations location { get; set; }
}
This way you are telling it which navigation property represents the relationship it is a foreign key for.
After that, remove the old migration and try to run again Add-Migration command to regenerate it. You should see now that CreateIndex method is called before AddForeignKey method:
CreateIndex("dbo.UserLoc", "fk_user_id");
AddForeignKey("dbo.UserLoc", "fk_user_id", "dbo.Users", "user_id");
CreateIndex("dbo.UserLoc", "fk_loc_id");
AddForeignKey("dbo.UserLoc", "fk_loc_id", "dbo.Locations", "loc_id");
I had this issue recently.
I deleted all migrations, ran remove-migration a few times to clean up some of the default ASP.Net identity migrations, then recreated the migrations from scratch.
Entity Framework Core was confusing the default scaffolding of the IdentityUser class with my custom implementation, hence it could not find the table ASPNetUsers when I went to create the table that relates to it, in this case, Referrals.
Be sure to clear out any default migrations and run remove-migration when using EF Core with identity built into the database.
Hope this helps anyone that is confused.

Maintain original FK column when adding new navigation property

I have the classes:
public class Company
{
public int CompanyId { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int EmployeeId { get; set; }
}
Code first migrations creates the following tables:
CreateTable(
"dbo.Companies",
c => new
{
CompanyId = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.CompanyId);
CreateTable(
"dbo.Employees",
c => new
{
EmployeeId = c.Int(nullable: false, identity: true),
Company_CompanyId = c.Int(),
})
.PrimaryKey(t => t.EmployeeId)
.ForeignKey("dbo.Companies", t => t.Company_CompanyId)
.Index(t => t.Company_CompanyId);
Now I want to add the Company property to the Employee class:
public class Employee
{
public int EmployeeId { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
}
What is the best way to bind the new property to the existing column without changing the DB schema?
First map the association:
modelBuilder.Entity<Employee>()
.HasRequired(e => e.Company)
.WithMany(c => c.Employees)
.HasForeignKey(e => e.CompanyId);
Then tell EF to map the property CompanyId to the column Company_CompanyId:
modelBuilder.Entity<Employee>().Property(e => e.CompanyId)
.HasColumnName("Company_CompanyId");
Agree with the solution proposed by #GertArnold. Following the same idea, you could also use Data annotations to resolve the same problem:
public class Employee
{
public int EmployeeId { get; set; }
[ForeignKey("Company"),Column("Company_CompanyId")]
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
}

Categories

Resources