Maintain original FK column when adding new navigation property - c#

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; }
}

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);

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.

3 way many to many with fluent api

I'd like to create database tables with a 3 way relationship using code first and the fluent api.
In my contrived example, a Team should have a unique combination of a Cat, a Dog, and a Pig. Another team could contain the same Cat and Pig, but not the same Dog and so on.
Firstly, I'd like to be able to get the teams containing a specific animal. myCat.Teams() And if possible I'd like to enforce uniqueness too.
public class Cat
{
public int Id { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
public class Dog
{
public int Id { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
public class Pig
{
public Guid { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
public class Team
{
public int Id { get; set; }
public int CatId { get; set; }
public int DogId { get; set; }
public Guid PigId { get; set; }
public virtual Cat Cat {get; set;}
public virtual Dog Dog {get; set;}
public virtual Pig Pig {get; set;}
}
In OnModelCreating(), EntityTypeConfigurations are added for these objects (CatMap, DogMap, PigMap, TeamMap).
I've tried setting up HasMany relationships from the TeamMap class, and alternatively from the other direction. For example, in DogMap:
HasMany(t => t.Teams)
.WithRequired(t => t.Dog)
.HasForeignKey(t => t.DogId);
but whenever I try to Add-Migration, I get errors like:
tSystem.Data.Entity.Edm.EdmAssociationConstraint: : The number of properties in the Dependent and Principal Roles in a relationship constraint must be identical.
How can I set up these associations correctly to achieve the two goals above? Thanks!!
The Team class shouldn't have its own Id, since the primary key is a combination of Cat, Dog, Pig. So, it should be something like:
public class Team
{
public int CatId { get; set; }
public int DogId { get; set; }
public Guid PigId { get; set; }
public virtual Cat Cat { get; set; }
public virtual Dog Dog { get; set; }
public virtual Pig Pig { get; set; }
}
Mapping:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//primary key, composed by a combination
modelBuilder.Entity<Team>()
.HasKey(i => new { i.CatId, i.DogId, i.PigId });
modelBuilder.Entity<Team>()
.HasRequired(i => i.Cat)
.WithMany(i => i.Teams)
.HasForeignKey(i => i.CatId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Team>()
.HasRequired(i => i.Dog)
.WithMany(i => i.Teams)
.HasForeignKey(i => i.DogId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Team>()
.HasRequired(i => i.Pig)
.WithMany(i => i.Teams)
.HasForeignKey(i => i.PigId)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
Generated migration:
CreateTable(
"dbo.Teams",
c => new
{
CatId = c.Int(nullable: false),
DogId = c.Int(nullable: false),
PigId = c.Guid(nullable: false),
})
.PrimaryKey(t => new { t.CatId, t.DogId, t.PigId })
.ForeignKey("dbo.Cats", t => t.CatId, cascadeDelete: true)
.ForeignKey("dbo.Dogs", t => t.DogId, cascadeDelete: true)
.ForeignKey("dbo.Pigs", t => t.PigId, cascadeDelete: true)
.Index(t => t.CatId)
.Index(t => t.DogId)
.Index(t => t.PigId);
CreateTable(
"dbo.Cats",
c => new
{
Id = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.Dogs",
c => new
{
Id = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.Pigs",
c => new
{
PigId = c.Guid(nullable: false),
})
.PrimaryKey(t => t.PigId);
If for some reason Team must have its own Id; change the model as follow:
public class Team
{
public int TeamId { get; set; }
//....
}
Mapping:
modelBuilder.Entity<Team>()
.HasKey(i => i.TeamId);
//if you want to make the teamId an auto-generated column
modelBuilder.Entity<Team>()
.Property(i => i.TeamId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
//if you want to make the cat, dog, and pig combination unique
modelBuilder.Entity<Team>()
.Property(i => i.CatId)
.HasColumnAnnotation(IndexAnnotation.AnnotationName,
new IndexAnnotation(
new IndexAttribute("IX_TeamComp", 1) { IsUnique = true }));
modelBuilder.Entity<Team>()
.Property(i => i.DogId)
.HasColumnAnnotation(IndexAnnotation.AnnotationName,
new IndexAnnotation(
new IndexAttribute("IX_TeamComp",2) { IsUnique = true }));
modelBuilder.Entity<Team>()
.Property(i => i.PigId)
.HasColumnAnnotation(IndexAnnotation.AnnotationName,
new IndexAnnotation(
new IndexAttribute("IX_TeamComp", 3) { IsUnique = true }));

Composite Key in EF

I cant seem to understand how EF deals with composite keys. When I try to 'add-migration Initial' the below code returns "The property 'QuestionID ' cannot be used as a key property on the entity QuestionQuestionTypesModel' because the property type is not a valid key type. Only scalar types, string and byte[] are supported key types."
I also tried to set annotations instead of overriding OnModelCreating. I used [Key, Column(Order = 0)]
Can anyone give me any clues on what Im doing wrong? Or explain whats happening to better understand the problem at hand?
public class QuestionModel
{
[Key]
[HiddenInput(DisplayValue = false)]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
[Required]
[StringLength(250)]
public string Question { get; set; }
}
public class QuestionTypeModel
{
[Key]
[HiddenInput(DisplayValue = false)]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
[Required]
[StringLength(250)]
public string TypeName { get; set; }
}
public class QuestionQuestionTypesModel
{
public virtual QuestionModel QuestionID {get;set;}
public virtual QuestionTypeModel QuestionTypeID { get; set; }
}
public class InnuendoContext : DbContext
{
public IContext() : base("DefaultConnection")
{
}
public DbSet<QuestionTypeModel> QuestionTypes { get; set; }
public DbSet<QuestionModel> Questions { get; set; }
public DbSet<QuestionQuestionTypesModel> QuestionQuestionTypes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<QuestionQuestionTypesModel>().HasKey(a => new { a.QuestionID, a.QuestionTypeID });
}
}
You have to create the properties required for the table which are also the the foreign keys of the system. By setting this structure:
public class QuestionQuestionTypesModel
{
[Key, Column(Order = 1), ForeignKey("Question")]
public Guid QuestionID { get; set; }
[Key, Column(Order = 2), ForeignKey("QuestionType")]
public Guid QuestionTypeID { get; set; }
public virtual QuestionModel Question { get; set; }
public virtual QuestionTypeModel QuestionType { get; set; }
}
You get this migration:
public override void Up()
{
CreateTable(
"dbo.QuestionModel",
c => new
{
ID = c.Guid(nullable: false, identity: true),
Question = c.String(nullable: false, maxLength: 250),
})
.PrimaryKey(t => t.ID);
CreateTable(
"dbo.QuestionTypeModel",
c => new
{
ID = c.Guid(nullable: false, identity: true),
TypeName = c.String(nullable: false, maxLength: 250),
})
.PrimaryKey(t => t.ID);
CreateTable(
"dbo.QuestionQuestionTypesModel",
c => new
{
QuestionID = c.Guid(nullable: false),
QuestionTypeID = c.Guid(nullable: false),
})
.PrimaryKey(t => new { t.QuestionID, t.QuestionTypeID })
.ForeignKey("dbo.QuestionModel", t => t.QuestionID, cascadeDelete: true)
.ForeignKey("dbo.QuestionTypeModel", t => t.QuestionTypeID, cascadeDelete: true)
.Index(t => t.QuestionID)
.Index(t => t.QuestionTypeID);
}
Update
Just saw your comment. If you have just a many-to-many relationship and you don't need any other attribute, you can do this:
public class QuestionModel
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
[Required]
[StringLength(250)]
public string Question { get; set; }
//One question has many QuestionTypes
public virtual ICollection<QuestionTypeModel> QuestionTypes { get; set; }
}
public class QuestionTypeModel
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
[Required]
[StringLength(250)]
public string TypeName { get; set; }
//One QuestionType has many Questions
public virtual ICollection<QuestionModel> Questions { get; set; }
}
This will produce the same migration but makes your data layer clear.
modelBuilder.Entity<QuestionQuestionTypesModel>().HasKey(a => new { a.QuestionID, a.QuestionTypeID });
QuestionID and QuestionTypeID are both navigation properties and therefore can't be used as primary keys. It's as the Error message suggests: only these Datatypes are supported as primary keys (can be converted to key columns in the supported databases), unfortunately, QuestionModel and QuestionTypeModel are none of these.
Add Guid key values to match the key columns of QuestionModel and QuestionTypeModel.

Mapping subclass values in Entity Framework TPH

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();
}
}

Categories

Resources