Fluent API Producing Duplicate Columns - c#

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

Related

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

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

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