3 way many to many with fluent api - c#

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

Related

Getting an ERROR updating an entity with a many 2 many relationship (JOIN TABLE)

I have 2 entities bounded together with a join table. Creating the article works fine. Updating the article entity throws this exception:
SqlException: The DELETE statement conflicted with the REFERENCE constraint "FK_ArticleGroups_Articles_ArticleID". The conflict occurred in database "myDB", table "dbo.ArticleGroups", column 'ArticleID'.
The statement has been terminated.
Below is my classes and setup
Article.cs
public class Article
{
public int ArticleId { get; set; }
public string Title { get; set; }
public virtual ICollection<ArticleGroup> ArticleGroups { get; set; } = new List<ArticleGroup>();
}
Group.cs
public class Group
{
public int GroupId { get; set; }
public string GroupName { get; set; }
public virtual ICollection<ArticleGroup> ArticleGroups { get; set; } = new List<ArticleGroup>();
public virtual ICollection<GroupUser> GroupUsers { get; set; } = new List<GroupUser>();
}
ArticleGroup.cs
public class ArticleGroup
{
public int ArticleId { get; set; }
public Article Article { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
}
AppDbContext.cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
.....
modelBuilder.Entity<ArticleGroup>()
.HasKey(e => new { e.ArticleID, e.GroupId });
modelBuilder.Entity<ArticleGroup>()
.HasOne(e => e.Article)
.WithMany(e => e.ArticleKbGroups)
.HasForeignKey(e => e.ArticleID)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<ArticleGroup>()
.HasOne(e => e.Group)
.WithMany(e => e.ArticleGroups)
.HasForeignKey(e => e.GroupId)
.OnDelete(DeleteBehavior.Restrict);
}
public DbSet<Article> Articles { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<ArticleGroup> ArticleGroups { get; set; }
ArticleRepository.cs
public bool UpdateArticle(Article article)
{
var articleGroups = _appDbContext.ArticleGroups.Where(a => a.ArticleID == article.ArticleID);
_appDbContext.ArticleGroups.RemoveRange(articleGroups);
_appDbContext.Articles.Update(article);
return (_appDbContext.SaveChanges() >= 0);
}
I've seen similar problems with such complicated solutions that don't compare to something as trivial as this. Where in EF am I setting up incorrectly? I'm thinking its sql statements not executing in the right order?
I would think the game plan would be remove all from join table with the same articleID and insert the new values.
EDIT
Added relationships
User.cs
public int UserId { get; set; }
public string FullName { get; set; }
public virtual ICollection<GroupUser> GroupUsers { get; set; } = new List<GroupUser>();
GroupUser.cs
public class GroupUser
{
public int UserId { get; set; }
public User User { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
}
This is the other relationship in this as well. Would this cause the error?
I need the Groups to not be deleted as well as they could belong to another article.
EDIT 2
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<GroupUser>()
.HasKey(e => new { e.UserId, e.GroupId });
modelBuilder.Entity<GroupUser>()
.HasOne(e => e.User)
.WithMany(e => e.GroupUsers)
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<GroupUser>()
.HasOne(e => e.Group)
.WithMany(e => e.GroupUsers)
.HasForeignKey(e => e.GroupId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<ArticleGroup>()
.HasKey(e => new { e.ArticleId, e.GroupId });
modelBuilder.Entity<ArticleGroup>()
.HasOne(e => e.Article)
.WithMany(e => e.ArticleGroups)
.HasForeignKey(e => e.ArticleId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<ArticleGroup>()
.HasOne(e => e.Group)
.WithMany(e => e.ArticleGroups)
.HasForeignKey(e => e.GroupId)
.OnDelete(DeleteBehavior.Restrict);
}

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

Entity framework core: Challenge Modeling Product Variants Database Design with Many to Many

I am trying to model a product variant database design with Entity Framework Core
Issue/blocker faced with design:
I am getting the following error on running dotnet ef migrations add InitialCreate command:
Introducing FOREIGN KEY constraint 'FK_ProductSKUValues_ProductSKUs_ProductId_SkuId' on table 'ProductSKUValues' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index.
Db Design:
Note: This design was modeled based on this link: Modeling Product Variants
ApplicationDbContext.cs with Fluent API (pay attention to ProductSKU & ProductSKUValue relationship):
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using TikkyBoxWebAPI.Models.Account;
using TikkyBoxWebAPI.Models;
using TikkyBoxWebAPI.Models.Core;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using System.Linq;
namespace TikkyBoxWebAPI.Data
{
public class TikkyBoxDbContext : DbContext
{
public TikkyBoxDbContext(DbContextOptions<TikkyBoxDbContext> options)
: base(options)
{
Database.Migrate();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<ProductSKU>()
.HasKey(p => new { p.ProductId, p.SkuId });
modelBuilder
.Entity<ProductSKU>()
.HasOne(p => p.Product)
.WithMany(ps => ps.ProductSKUs)
.HasForeignKey(x => x.ProductId);
modelBuilder
.Entity<ProductSKU>()
.HasIndex(p => p.Sku);
modelBuilder
.Entity<ProductSKU>()
.Property(p => p.SkuId).ValueGeneratedOnAdd();
modelBuilder
.Entity<ProductSKUValue>()
.HasOne<ProductSKU>()
.WithMany( p => p.ProductSKUValues)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder
.Entity<ProductSKUValue>()
.HasKey(p => new { p.ProductId, p.SkuId, p.OptionId});
modelBuilder
.Entity<ProductSKUValue>()
.HasOne(p => p.ProductOptionValue)
.WithMany(ps => ps.ProductSKUValues)
.HasForeignKey(x => new { x.ProductId, x.OptionId, x.ValueId })
.OnDelete(DeleteBehavior.Restrict);
modelBuilder
.Entity<ProductSKUValue>()
.HasOne(p => p.ProductOption)
.WithMany(ps => ps.ProductSKUValues)
.HasForeignKey(x => new { x.ProductId, x.OptionId })
.OnDelete(DeleteBehavior.Restrict);
modelBuilder
.Entity<ProductOptionValue>()
.HasKey(p => new { p.ProductId, p.OptionId, p.ValueId });
modelBuilder
.Entity<ProductOptionValue>()
.HasOne(p => p.ProductOption)
.WithMany(ps => ps.ProductOptionValues)
.HasForeignKey(x => new { x.ProductId, x.OptionId });
// .OnDelete(DeleteBehavior.Restrict);
modelBuilder
.Entity<ProductOptionValue>()
.Property(p => p.ValueId).ValueGeneratedOnAdd();
modelBuilder
.Entity<ProductOption>()
.HasKey(p => new { p.ProductId, p.OptionId });
modelBuilder
.Entity<ProductOption>()
.HasOne(p => p.Product)
.WithMany(po => po.ProductOptions)
.HasForeignKey(x => new { x.ProductId })
.OnDelete(DeleteBehavior.Restrict);
modelBuilder
.Entity<ProductOption>()
.Property(p => p.OptionId).ValueGeneratedOnAdd();
// base.OnModelCreating(modelBuilder);
}
public DbSet<Product> Products { get; set; }
public DbSet<ProductOption> ProductOptions { get; set; }
public DbSet<ProductOptionValue> ProductOptionValues { get; set; }
public DbSet<ProductSKU> ProductSKUs { get; set; }
public DbSet<ProductSKUValue> ProductSKUValues { get; set; }
}
}
Product.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace TikkyBoxWebAPI.Models.Core
{
public class Product
{
public int Id { get; set; }
[Required]
public String Name { get; set; }
// to be used for barcode : remember look at datatype
[MaxLength(32)]
public String UniversalProductCode { get; set; }
public Decimal Height { get; set; }
public Decimal Weight { get; set; }
public Decimal NetWeight { get; set; }
public Decimal Depth { get; set; }
[MaxLength(128)]
public String ShortDescription { get; set; }
[MaxLength(255)]
public String LongDescription { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
public virtual ICollection<ProductSKU> ProductSKUs { get; set; }
public virtual ICollection<ProductOption> ProductOptions { get; set; }
}
}
ProductSKU.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace TikkyBoxWebAPI.Models.Core
{
public class ProductSKU
{
public int ProductId { get; set; }
public int SkuId { get; set; }
[Required]
[MaxLength(64)]
public String Sku { get; set; }
public Product Product { get; set; }
public List<ProductSKUValue> ProductSKUValues { get; set; }
}
}
ProductSKUValue.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace TikkyBoxWebAPI.Models.Core
{
public class ProductSKUValue
{
public int ProductId { get; set; }
public int SkuId { get; set; }
public int OptionId { get; set; }
public int ValueId { get; set; }
public virtual ProductSKU ProductSKU { get; set; }
public virtual ProductOption ProductOption { get; set; }
public virtual ProductOptionValue ProductOptionValue { get; set; }
}
}
ProductOption.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System;
namespace TikkyBoxWebAPI.Models.Core
{
public class ProductOption
{
public int ProductId { get; set; }
public int OptionId { get; set; }
[Required]
[MaxLength(40)]
public String OptionName { get; set; }
public virtual Product Product { get; set; }
public virtual ICollection<ProductSKUValue> ProductSKUValues { get; set; }
public virtual ICollection<ProductOptionValue> ProductOptionValues { get; set; }
}
}
ProductOptionValue.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System;
namespace TikkyBoxWebAPI.Models.Core
{
public class ProductOptionValue
{
public int ProductId { get; set; }
public int ValueId { get; set; }
public int OptionId { get; set; }
[Required]
[MaxLength(32)]
public String ValueName { get; set; }
public virtual ProductOption ProductOption { get; set; }
public virtual ICollection<ProductSKUValue> ProductSKUValues { get; set; }
}
}
I have already unsuccessfully tried these answers on StackOverflow & the web:
Configuring Many to Many in Entity Framework Core
Docs: Entity Framework Core Relationships
EF One-To-Many - may cause cycles or multiple cascade paths
Ef 4 Solution with nullable primary key (which I have tried)
I am using
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Design
Microsoft.AspNetCore.Identity.EntityFrameworkCore versions 1.1.2
Any assistance would really be appreciated. I have been searching the web for a solution for 2 days
Everything is ok except the following fluent configuration
modelBuilder
.Entity<ProductSKUValue>()
.HasOne<ProductSKU>()
.WithMany(p => p.ProductSKUValues)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
which is causing several issues.
First, the parameterless .HasOne<ProductSKU>() leaves the ProductSKU navigation property of the ProductSKUValue class unmapped, so by convention EF tries to create another one-to-many relationship.
Second, .IsRequired(false) disallows the usage of the existing {ProductId, SkuId} fields as foreign key because they are required (do not allow null values), hence EF creates another two nullable fields for that.
Here is the resulting table from the above configuration:
migrationBuilder.CreateTable(
name: "ProductSKUValues",
columns: table => new
{
ProductId = table.Column<int>(nullable: false),
SkuId = table.Column<int>(nullable: false),
OptionId = table.Column<int>(nullable: false),
ProductSKUProductId = table.Column<int>(nullable: true),
ProductSKUProductId1 = table.Column<int>(nullable: true),
ProductSKUSkuId = table.Column<int>(nullable: true),
ProductSKUSkuId1 = table.Column<int>(nullable: true),
ValueId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProductSKUValues", x => new { x.ProductId, x.SkuId, x.OptionId });
table.ForeignKey(
name: "FK_ProductSKUValues_ProductOptions_ProductId_OptionId",
columns: x => new { x.ProductId, x.OptionId },
principalTable: "ProductOptions",
principalColumns: new[] { "ProductId", "OptionId" },
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_ProductSKUValues_ProductSKUs_ProductSKUProductId_ProductSKUSkuId",
columns: x => new { x.ProductSKUProductId, x.ProductSKUSkuId },
principalTable: "ProductSKUs",
principalColumns: new[] { "ProductId", "SkuId" },
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_ProductSKUValues_ProductSKUs_ProductSKUProductId1_ProductSKUSkuId1",
columns: x => new { x.ProductSKUProductId1, x.ProductSKUSkuId1 },
principalTable: "ProductSKUs",
principalColumns: new[] { "ProductId", "SkuId" },
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_ProductSKUValues_ProductOptionValues_ProductId_OptionId_ValueId",
columns: x => new { x.ProductId, x.OptionId, x.ValueId },
principalTable: "ProductOptionValues",
principalColumns: new[] { "ProductId", "OptionId", "ValueId" },
onDelete: ReferentialAction.Restrict);
});
Note the additional columns and the two FK constraints to ProductSKUs.
To fix the issue, just use the proper configuration (similar to what you did for other relationships):
modelBuilder
.Entity<ProductSKUValue>()
.HasOne(p => p.ProductSKU)
.WithMany(p => p.ProductSKUValues)
.HasForeignKey(x => new { x.ProductId, x.SkuId })
.OnDelete(DeleteBehavior.Restrict);

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

How to specify foreign key property for one to zero..one relation using fluent api in Entity Framework

I have a table User and another table Company. A User can have a zero or one company registered.
User (1)---> (0..1) Company
My user class:
public class User {
public string Id {get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get { return FirstName + " " + LastName; }
}
//Relations
public virtual Company Company { get; set; }
}
and my company class is:
public class Company {
public int Id { get; set; }
public string CompanyName { get; set; }
public string TaxNumber { get; set; }
public string TaxOffice { get; set; }
public string OfficeTel { get; set; }
public string FaxNumber { get; set; }
public string WebSite { get; set; }
public string Address { get; set; }
public string About { get; set; }
//keys
public int CityId { get; set; }
public int StateId { get; set; }
public string UserId { get; set; }
//relations
public City City { get; set; }
public State State { get; set; }
public User User { get; set; }
}
the fluent api used for company is like this:
public class CompanyConfiguration : EntityTypeConfiguration<Company>
{
public CompanyConfiguration()
{
this.HasRequired(x => x.User)
.WithOptional(x => x.Company);
this.HasRequired(x => x.City)
.WithMany(x => x.Companies).HasForeignKey(x => x.CityId).WillCascadeOnDelete(false);
this.HasRequired(x => x.State)
.WithMany(x => x.Companies).HasForeignKey(x => x.StateId).WillCascadeOnDelete(false);
this.Property(x => x.Address).HasMaxLength(400);
this.Property(x => x.CompanyName).HasMaxLength(100).IsRequired();
this.Property(x => x.FaxNumber).HasMaxLength(20);
this.Property(x => x.OfficeTel).HasMaxLength(20);
this.Property(x => x.TaxNumber).HasMaxLength(20).IsRequired();
this.Property(x => x.TaxOffice).HasMaxLength(50).IsRequired();
this.Property(x => x.WebSite).HasMaxLength(200);
}
}
After I run Add-Migration what I expect is that UserId used as foreign key for User in Company table, but what is generated by Entity framework migrations is:
CreateTable(
"dbo.Companies",
c => new
{
Id = c.Int(nullable: false, identity: true),
CompanyName = c.String(nullable: false, maxLength: 100),
TaxNumber = c.String(nullable: false, maxLength: 20),
TaxOffice = c.String(nullable: false, maxLength: 50),
OfficeTel = c.String(maxLength: 20),
FaxNumber = c.String(maxLength: 20),
WebSite = c.String(maxLength: 200),
Address = c.String(maxLength: 400),
About = c.String(),
CityId = c.Int(nullable: false),
StateId = c.Int(nullable: false),
UserId = c.String(),
User_Id = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Cities", t => t.CityId)
.ForeignKey("dbo.States", t => t.StateId)
.ForeignKey("dbo.AspNetUsers", t => t.User_Id)
.Index(t => t.CityId)
.Index(t => t.StateId)
.Index(t => t.User_Id);
Question is how can I force Entity Framework to use my specified property as foreign key for relation, and the reason for this is I need the userId value of a company frequently in my code, and I don't want to use Company.User.Id expression to get that.
Note: I use Entity Framework 6.1.2 and asp.net mvc 5
Here is how I would define the foreignkey in this relationship:
public class Company {
public int Id { get; set; }
[Required]
[Key, ForeignKey("User")]
public string UserId { get; set; }
public User User { get; set; }
}
A bit late to the party but just throwing my thoughts in in-case anyone else comes across this question.
Had the same problem myself, Entity framework (EF6) does not seem to have a baked in way of specifying the foreign key for a 1-2-0/1 relationship, however its important to remember it is designed to be overwritten if required. The only way I could configure this was to manually amend the auto-generated migration file.
Example
below is an abbreviated version of the auto-generated migration code (as posted above) showing the UserId that was intended to be the FK and the automatically added property User_Id that was inserted by EF to be the FK
CreateTable(
"dbo.Companies",
c => new
{
Id = c.Int(nullable: false, identity: true),
...
UserId = c.String(),
User_Id = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.Id)
...
.ForeignKey("dbo.AspNetUsers", t => t.User_Id)
...
.Index(t => t.User_Id);
To achieve the original intention of the UserId being the FK, simply delete the autogenerated property then amend the FK statement and the index statement
CreateTable(
"dbo.Companies",
c => new
{
Id = c.Int(nullable: false, identity: true),
...
UserId = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.Id)
....
.ForeignKey("dbo.AspNetUsers", t => t.UserId)
...
.Index(t => t.UserId);
NB
The Down method to revert the Migration is not shown in the question but any additions or amendments made to the Up method need to be reflected in the Down method as well.

Categories

Resources