EF Core creates multiple foreign key columns - c#

I'm using EF Core with .NET Core 3.1
I have simple example of Client-Event relationship:
public class BaseEntity
{
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
}
public class Client : BaseEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
public class Event : BaseEntity
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public Client Client { get; set; }
}
In my context, I'm using Fluent API to specify relationships:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Event>()
.HasOne<Client>()
.WithMany()
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
}
When I create migration, the client table looks fine, but the event table looks like this:
migrationBuilder.CreateTable(
name: "Events",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
CreatedOn = table.Column<DateTime>(nullable: false),
ModifiedOn = table.Column<DateTime>(nullable: true),
Start = table.Column<DateTime>(nullable: false),
End = table.Column<DateTime>(nullable: false),
ClientId = table.Column<int>(nullable: false),
ClientId1 = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Events", x => x.Id);
table.ForeignKey(
name: "FK_Events_Clients_ClientId",
column: x => x.ClientId,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Events_Clients_ClientId1",
column: x => x.ClientId1,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
Finally I end up having two columns: ClientId and ClientId1. Why is Entity Framework creating two columns for my relationship?
I wasn't using Fluent API so far and it worked perfectly with just shadow property ClientId auto-generated, but I needed to configure cascade delete form this entities, and since there is no other way to do it, I specified the relationship as pictured above. Since then, there is additional foreign key column for it.
I tried specifying a foreign key column:
modelBuilder.Entity<Event>()
.HasOne<Client>()
.WithMany()
.IsRequired()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
No effect so far. Is there any way to tell EF im using auto generated shadow properties?
Edit #1:
I also tried specyfing Foreign keys properties on my own like this:
public class Event : BaseEntity
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public int ClientId { get; set; }
public Client Client { get; set; }
}
and then
modelBuilder.Entity<Event>()
.HasOne<Client>()
.WithMany()
.IsRequired()
.HasForeignKey(e => e.ClientId)
.OnDelete(DeleteBehavior.Cascade);
but there is still no effect.

It turns out, relationships can be empty - leaving decision to the framework, but it doesn't really serve Your interest. I modified my code, so there is explicit pointing at the navigation property, and EF recognized the relationship and stopped creating shadow properties for the columns:
modelBuilder.Entity<Event>()
.HasOne<Client>(e => e.Client)
.WithMany()
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);

You can try this:
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public int? ClientId { get; set; }
[ForeignKey("ClientId ")]
public virtual Client Client { get; set; }
}

Related

Entity framework core code first : multiple foreign key on itself

We got this exception message when we try to create mutliple foreign key to the owner table (with only one foreign key, there is no problem) :
The dependent side could not be determined for the one-to-one relationship between 'Person.Alternative1Person' and 'Person.Alternative2Person'. To identify the dependent side of the relationship, configure the foreign key property. If these navigations should not be part of the same relationship, configure them independently via separate method chains in 'OnModelCreating'. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details.
We founded that hading the [ForeignKey] solve the problem. But we don't understand why without this data-annotion it doesn't work, because finally the migration generated code is the same if these foreign key were on another table ?
internal class DatabaseContext : DbContext
{
public DatabaseContext() { }
public DbSet<Country> Country { get; set; }
public DbSet<Resident> Resident { get; set; }
public DbSet<Person> Person { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
string connectionStringSqlServer = #"Server=myserver;Database=TestEf;User Id=x;Password=x;";
optionsBuilder.UseSqlServer(connectionStringSqlServer);
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.EnableDetailedErrors();
}
}
}
public class Resident
{
public int Id { get; set; }
public string Name { get; set; }
// Data-annotation ForeignKey not required
public int? HomeCountryId { get; set; }
public virtual Country HomeCountry { get; set; }
// Data-annotation ForeignKey not required
public int? BirthCountryId { get; set; }
public virtual Country BirthCountry { get; set; }
}
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Alternative1Person")] // Error without this data-annotation if more than one foreign key on itseft
public int? Alternative1PersonId { get; set; }
public virtual Person Alternative1Person { get; set; } constraint)
[ForeignKey("Alternative2Person")] // Error without this data-annotation if more than one foreign key on itseft
public int? Alternative2PersonId { get; set; }
public virtual Person Alternative2Person { get; set; } constraint)
}
Migration generated code are identical, so why the data-annotion is required to avoid the error ?
constraints: table =>
{
table.PrimaryKey("PK_Resident", x => x.Id);
table.ForeignKey(
name: "FK_Resident_Country_BirthCountryId",
column: x => x.BirthCountryId,
principalTable: "Country",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Resident_Country_HomeCountryId",
column: x => x.HomeCountryId,
principalTable: "Country",
principalColumn: "Id");
});
constraints: table =>
{
table.PrimaryKey("PK_Person", x => x.Id);
table.ForeignKey(
name: "FK_Person_Person_Alternative1PersonId",
column: x => x.Alternative1PersonId,
principalTable: "Person",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Person_Person_Alternative2PersonId",
column: x => x.Alternative2PersonId,
principalTable: "Person",
principalColumn: "Id");
});

Double id field being generated in EF core code first when adding foreign key

I'm working on an application that keeps track of our client's cars. I'm using EF core code first. This is where I have a problem with two tables, vehicle and licensePlate.
A vehicle can have multiple licensePlate (but only one active at a time), and a licensePlate can belong to one vehicle. Everything has worked fine so far, but for a new feature I want to implement the history of a car's license plates. For this I have added an activeLicensePlate field to the vehicle class, which should contain a foreign key to the licensePlate (id field) table.
The code I have:
Class Vehicle (before adding the new code):
namespace Models
{
public class Vehicle : IIdentifiable
{
public virtual long Id { get; set; }
public virtual long ChassisNr { get; set; }
public virtual ICollection<LicensePlate> LicensePlates { get; set; }
public virtual FuelTypes FuelType { get; set; }
public virtual VehicleTypes VehicleType { get; set; }
public virtual int Mileage { get; set; }
public virtual ICollection<Maintenance> Maintenances { get; set; }
public virtual ICollection<Application> Applications { get; set; }
}
}
Class Vehicle (after trying to add a field to keep track of the active license plate):
namespace Models
{
public class Vehicle : IIdentifiable
{
public virtual long Id { get; set; }
public virtual long ChassisNr { get; set; }
public virtual ICollection<LicensePlate> LicensePlates { get; set; }
public virtual FuelTypes FuelType { get; set; }
public virtual VehicleTypes VehicleType { get; set; }
public virtual int Mileage { get; set; }
public virtual ICollection<Maintenance> Maintenances { get; set; }
public virtual ICollection<Application> Applications { get; set; }
//not working, migration creates 2 columns
public virtual LicensePlate ActiveLicensePlate { get; set; }
public virtual long ActiveLicensePlateId { get; set; }
}
}
Class LicensePlate:
namespace Models
{
public class LicensePlate : IIdentifiable
{
public virtual long Id { get; set; }
public virtual string LicensePlateCharacters { get; set; }
//rel vehicle
public virtual long VehicleId { get; set; }
public virtual Vehicle Vehicle { get; set; }
}
}
Model constraints:
//rel vehicle license plate one many
modelBuilder.Entity<LicensePlate>()
.HasOne<Vehicle>(l => l.Vehicle)
.WithMany(v => v.LicensePlates)
.HasForeignKey(l => l.VehicleId)
.IsRequired();
Vehicle model constraints:
using Microsoft.EntityFrameworkCore;
using Models;
namespace ReadRepositories.Mappings
{
public static class VehicleModelConstraints
{
public static void OnModelCreatingVehicle(this ModelBuilder modelBuilder)
{
modelBuilder.Entity<Vehicle>()
.HasKey(v => v.Id);
modelBuilder.Entity<Vehicle>()
.Property(v => v.Id)
.ValueGeneratedOnAdd();
modelBuilder.Entity<Vehicle>()
.Property(v => v.FuelType)
.IsRequired();
modelBuilder.Entity<Vehicle>()
.Property(v => v.VehicleType)
.IsRequired();
modelBuilder.Entity<Vehicle>()
.Property(v => v.Mileage)
.IsRequired();
modelBuilder.Entity<Vehicle>()
.Property(v => v.ChassisNr)
.IsRequired();
modelBuilder.Entity<Vehicle>()
.Property(v => v.ActiveLicensePlateId)
.IsRequired();
}
}
}
License plate model constraints:
using Microsoft.EntityFrameworkCore;
using Models;
namespace ReadRepositories.Mappings
{
public static class LicensePlateModelConstraints
{
public static void OnModelCreatingLicensePlate(this ModelBuilder modelBuilder)
{
modelBuilder.Entity<LicensePlate>()
.HasKey(l => l.Id);
modelBuilder.Entity<LicensePlate>()
.Property(l => l.Id)
.ValueGeneratedOnAdd();
modelBuilder.Entity<LicensePlate>()
.Property(l => l.LicensePlateCharacters)
.IsRequired();
}
}
}
Migration generated:
using Microsoft.EntityFrameworkCore.Migrations;
namespace Repositories.Migrations
{
public partial class ActiveLicensePlate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(
name: "ActiveLicensePlateId",
table: "Vehicle",
type: "bigint",
nullable: false,
defaultValue: 0L);
migrationBuilder.AddColumn<long>(
name: "ActiveLicensePlateId1",
table: "Vehicle",
type: "bigint",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Vehicle_ActiveLicensePlateId1",
table: "Vehicle",
column: "ActiveLicensePlateId1");
migrationBuilder.AddForeignKey(
name: "FK_Vehicle_LicensePlate_ActiveLicensePlateId1",
table: "Vehicle",
column: "ActiveLicensePlateId1",
principalTable: "LicensePlate",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Vehicle_LicensePlate_ActiveLicensePlateId1",
table: "Vehicle");
migrationBuilder.DropIndex(
name: "IX_Vehicle_ActiveLicensePlateId1",
table: "Vehicle");
migrationBuilder.DropColumn(
name: "ActiveLicensePlateId",
table: "Vehicle");
migrationBuilder.DropColumn(
name: "ActiveLicensePlateId1",
table: "Vehicle");
}
}
}
As you can see in the migration generated, a field named ActiveLicensePlateId is created, but as well is a field named ActiveLicensePlateId1 (which contains the foreign key that I want to be added).
My database tables before the migration:
vehicle table:
license plate table:
So, briefly put, the 'only' thing I want is for a field to be added in the vehicle table, a field named activeLicensePlate. This field should reference a license plates' id, with a foreign key to the license plate table.
Does anyone have an idea what is going wrong?
I am not sure, but I think your explicit property definition is making ef core ignoring your foreign key since you didn't mark it, so maybe just removing this should work since you already follow naming conventions
//remove this
modelBuilder.Entity<Vehicle>()
.Property(v => v.ActiveLicensePlateId)
.IsRequired();
or a better way to do it is using fluent api to define the relation
modelBuilder.Entity<Vehicle>()
.HasOne(p => p.ActiveLicensePlate)
.WithOne()
.HasForeignKey(p => p.ActiveLicensePlateId);

EF Core - create relationship without primary/foreign keys

I'm trying to configure EF to include documents when retriving a user or product. The entity Document has a ReferenceId property which should store either UserId or ProductId. This way, when I save a document for a user or product, the UserId or ProductId is saved to Document.ReferenceId.
Entities:
public class User
{
public string Id { get; set; }
public ICollection<Document> Documents { get; set; }
}
public class Product
{
public string Id { get; set; }
public ICollection<Document> Documents { get; set; }
}
public class Document
{
public string Id { get; set; }
public string ReferenceId { get; set; }
}
Configuring:
builder.Entity<User>(e =>
{
e.HasKey(e => e.Id);
e.Property(p => p.Id).ValueGeneratedOnAdd();
e.HasMany(e => e.Documents)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<Product>(e =>
{
e.HasKey(e => e.Id);
e.Property(p => p.Id).ValueGeneratedOnAdd();
e.HasMany(e => e.Documents)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<Document>(e =>
{
e.HasKey(e => e.Id);
e.Property(p => p.Id).ValueGeneratedOnAdd();
e.ToTable("Documents");
});
Saving:
var user = new User { };
var userDocument = new Document { ReferenceId = user.Id };
var product = new Product { };
var productDocument = new Document { ReferenceId = product.Id };
_context.Users.Add(user);
_context.Products.Add(product);
_context.Add(userDocument);
_context.Add(productDocument);
_context.SaveChanges();
Migrations:
migrationBuilder.CreateTable(
name: "Documents",
columns: table => new
{
Id = table.Column<string>(nullable: false),
ReferenceId = table.Column<string>(nullable: true),
ProductId = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Documents", x => x.Id);
table.ForeignKey(
name: "FK_Documents_Products_ProductId",
column: x => x.ProductId,
principalTable: "Products",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Documents_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
I don't want 2 foreign keys (ProductId and UserId) to be created on Documents table. Is there a way to make EF automatically link UserId and ProductId to ReferenceId?
The proper way to solve it would be to have User and Product inherit a base class and move the Id and Documents properties to that class.
public class BaseObject
{
public string Id { get; set; }
public ICollection<Document> Documents { get; set; }
}
public class User : BaseObject
{
}
public class Product : BaseObject
{
}
public class Document
{
public string BaseObjectId { get; set; }
}
The only way I see, is to use TPH inheritance (See here for more information).
I have quoted and edited the answer by Erik H.
public enum DocumentType
{
User = 0,
Product = 1
}
public class BaseObject
{
public string Id { get; set; }
public ObjectType DocumentType{ get; set; }
public virtual ICollection<Document> Documents { get; set; }
}
public class User : BaseObject
{
}
public class Product : BaseObject
{
}
public class Document
{
public int Id { get; set; }
public string BaseObjectId { get; set; }
public virtual BaseObject DocumentObject { get; set; }
}
Via fluent-Api you can set a discriminator. This way ef core will only create one table for for both objects Product and User and distinguishes their type by the value of the discriminator column. But only as long as they have exactly the same properties which they share from the base class. As soon as you add properties to one of those subclasses a new table will be created (with all properties from the base- and subclass).
Here is the configuration for the discriminator:
modelBuilder.Entity<BaseObject>()
.HasDiscriminator<DocumentType>("DocumentType")
.HasValue<User>(DocumentType.User)
.HasValue<Product>(DocumentType.Product)
This may not be a clean approach (for me it seems like User and Product should not inherit from the same base class, because they do not share anything than the relations to documents). But it should work as you want it.
You can create a many to many table:
public class Product
{
public string Id { get; set; }
public ICollection<ProductDocument> ProductDocuments{ get; set; }
}
public class Document
{
public string ReferenceId { get; set; }
}
public class ProductDocument
{
public ICollection<Product> Products{ get; set; }
public ICollection<Document> Documents { get; set; }
}
You will have to create a separate table for your user table ie UserDocumentes using the same pattern.

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

Entity Framework: Migration falsely adds foreign key column twice

I'm using EntityFramework 6.1.3 with CodeFirst for an Asp.Net application. I have an existing table ("Users") on which I'm trying to add a foreign key "GroupId".
This is the class (Everything with //new are changes made after the last migration)
[Table("Users")]
public class User
{
[Key]
[Column("PK_USER")]
public int Id { get; set; }
[Column("Username")]
public string Username { get; set; }
[Column("Name")]
public string Name { get; set; }
[Column("Password")]
public string Password { get; set; }
[Column("Firstname")]
public string Firstname { get; set; }
[Column("Lastname")]
public string Lastname { get; set; }
[Column("LastLogin")]
public DateTime? LastLogin { get; set; }
[Column("Department")]
public string Department { get; set; }
[Column("EMail")]
public string EMail { get; set; }
[Column("IsWindowsUser")]
public bool? IsWindowsUser { get; set; }
[Column("Signature")]
public string Signature { get; set; }
[Column("FK_ROLE")]
public int? RoleId { get; set; }
[ForeignKey("RoleId")]
public virtual Role Role { get; set; }
// new
[Column("GroupId")]
public int? GroupId { get; set; }
//new
[ForeignKey("GroupId")]
public virtual Group Group { get; set; }
//new
public virtual ICollection<GroupResponsibility> Responsibilites { get; set; }
public virtual ICollection<UserField> UserFields { get; set; }
public override string ToString()
{
return Username;
}
}
After I run add-migration the following code is generated (omitted other changes)
AddColumn("dbo.Users", "GroupId", c => c.Int());
AddColumn("dbo.Users", "Group_Id", c => c.Int());
CreateIndex("dbo.Users", "GroupId");
CreateIndex("dbo.Users", "Group_Id");
AddForeignKey("dbo.Users", "Group_Id", "dbo.Groups", "Id");
AddForeignKey("dbo.Users", "GroupId", "dbo.Groups", "Id");
As you can see EntityFramework recognized the foreign key but still added a default one "Group_Id". If I go through with it, and update the database, the navigational property "Group" will releate to "Group_Id" instead of the desired "GroupId".
Any ideas what might cause this?
Update 1
I commented out the navigational property and got the same result. Looks like the ICollection Users on the other end of the relation is the culprit. Here is the class "Group"
[Table("Groups")]
public class Group
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int? GroupLeaderId { get; set; }
[ForeignKey("GroupLeaderId")]
public virtual User GroupLeader { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<GroupResponsibility> Responsibilites { get; set; }
public virtual ICollection<ApplicationForm> Applications { get; set; }
public override string ToString()
{
return Name;
}
}
If I comment out the ICollection Users, I get the following exception when adding the migration:
Unable to determine the principal end of an association between the types 'SparePartsDb.Entities.Group' and 'SparePartsDb.Entities.User'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
Update 2
After some googling and changing the Key Attribute on the Group class...
[Key, ForeignKey("GroupLeader")]
public int Id { get; set; }
...I'm able to comment out the ICollection and run the migration. However, GroupId is not not longer recognized as a foreign key even though the navigiational property is present in the class User.
AddColumn("dbo.Users", "GroupId", c => c.Int());
OK, so I've searched for "migration adding duplicate foreign key" in multiple permutations for hours, but I finally figured out what was causing it for me: don't assume Fluent API knows which field you're referring to with WithOne() or WithMany()-- specify the field. See below for details.
I had two entity models in a one-to-many relationship, Address and Invitation, with public virtual ICollection<Invitation> Invitations on Address and public virtual Address Address on Invitation. I chose Fluent API over convention/attributes, so my Invitation builder looked like this:
builder
.HasOne(x => x.Address)
.WithMany()
.HasForeignKey(x => x.AddressId);
Unfortunately, EF Core 2.2 doesn't like having that empty WithMany() in there. Running dotnet ef migrations add InitialCreate resulted in this nonsense, much like OP:
migrationBuilder.CreateTable(
name: "Invitation",
columns: table => new
{
AddressId = table.Column<Guid>(nullable: false),
AddressId1 = table.Column<Guid>(nullable: true),
...
},
constraints: table =>
{
table.PrimaryKey("PK_Invitation", x => x.Id);
table.ForeignKey(
name: "FK_Invitation_Address_AddressId",
column: x => x.AddressId,
principalTable: "Address",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Invitation_Address_AddressId1",
column: x => x.AddressId1,
principalTable: "Address",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
Switching my builder to read:
builder
.HasOne(x => x.Address)
.WithMany(x => x.Invitations)
.HasForeignKey(x => x.AddressId);
fixed the problem for me.
Running dotnet ef migrations remove followed by dotnet ef migrations add InitialCreate again gave me a much nicer migration:
migrationBuilder.CreateTable(
name: "Invitation",
columns: table => new
{
AddressId = table.Column<Guid>(nullable: false),
...
},
constraints: table =>
{
table.PrimaryKey("PK_Invitation", x => x.Id);
table.ForeignKey(
name: "FK_Invitation_Address_AddressId",
column: x => x.AddressId,
principalTable: "Address",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
Hope this helps some other poor soul searching this down.
You shouldn't need to put a
[Column("GroupId")]
on top of the
public int? GroupId { get; set; }
Entity Framework should be able to recognize your mapping by itself.
As stated in msdn:
When generating the database, code first sees the BlogId property in the Post class and recognizes it, by the convention that it matches a class name plus “Id”, as a foreign key to the Blog class. But there is no BlogId property in the blog class. The solution for this is to create a navigation property in the Post and use the Foreign DataAnnotation to help code first understand how to build the relationship between the two classes —using the Post.BlogId property — as well as how to specify constraints in the database.
And the code to this explanation would be:
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
[ForeignKey("BlogId")]
public Blog Blog { get; set; }
public ICollection<Comment> Comments { get; set; }
}
As you can see, only the complex object has the mapping defining the fk, EF should do the rest for you.
source

Categories

Resources