EF Core Many-To-Many Relationship Sets Both Keys to Parent - entity-framework

I working on building out a model that would represent a typical product that could be created in an e-commerce platform written using EF Core 2.0. See the model structure below
public class GSProduct : BaseEntity
{
[Required]
public string Name { get; set; }
public ICollection<GSProduct> BaseProducts { get; set; }
public ICollection<GSRelatedProduct> ParentProducts { get; set; }
public ICollection<GSRelatedProduct> ChildProducts { get; set; }
public ICollection<GSVendorProductInfo> VendorProductInfo { get; } = new List<GSVendorProductInfo>();
}
public class GSRelatedProduct
{
public virtual GSProduct ParentProduct { get; set; }
public Guid ParentProductId { get; set; }
public virtual GSProduct ChildProduct { get; set; }
public Guid ChildProductId { get; set; }
}
public class GSVendorProductInfo : BaseEntity
{
public GSContact Vendor { get; set; }
public Guid VendorId { get; set; }
public GSProduct Product { get; set; }
public Guid ProductId { get; set; }
public string VendorPartNumber { get; set; }
public int BaseUnits { get; set; }
public double Cost { get; set; }
public int MinOrderQty { get; set; }
public int OrderedInMultiples { get; set; }
}
This is what I have set up for the Fluent API.
modelBuilder.Entity<GSVendorProductInfo>()
.HasOne(pt => pt.Product)
.WithMany(p => p.VendorProductInfo)
.HasForeignKey(pt => pt.ProductId);
modelBuilder.Entity<GSVendorProductInfo>()
.HasOne(pt => pt.Vendor)
.WithMany(t => t.VendorProductInfo)
.HasForeignKey(pt => pt.VendorId);
modelBuilder.Entity<GSRelatedProduct>().HasKey(x => new { x.ParentProductId, x.ChildProductId });
modelBuilder.Entity<GSRelatedProduct>()
.HasOne(pt => pt.ParentProduct)
.WithMany(t => t.ParentProducts)
.HasForeignKey(pt => pt.ParentProductId);
modelBuilder.Entity<GSRelatedProduct>()
.HasOne(pt => pt.ChildProduct)
.WithMany(t => t.ChildProducts)
.HasForeignKey(pt => pt.ChildProductId);
Also including the migration
migrationBuilder.CreateTable(
name: "GSRelatedProducts",
columns: table => new
{
ParentProductId = table.Column<Guid>(nullable: false),
ChildProductId = table.Column<Guid>(nullable: false),
Optional = table.Column<bool>(nullable: false),
Quantity = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GSRelatedProducts", x => new { x.ParentProductId, x.ChildProductId });
table.ForeignKey(
name: "FK_GSRelatedProducts_GSProducts_ChildProductId",
column: x => x.ChildProductId,
principalTable: "GSProducts",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
table.ForeignKey(
name: "FK_GSRelatedProducts_GSProducts_ParentProductId",
column: x => x.ParentProductId,
principalTable: "GSProducts",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
});
The scaffolding / migration is working fine and I can actually create products without a problem that include all of the relationships. The issue arises when I try to add a 'RelatedProduct' to the Product model.
I set the ParentProductId and the ChildProductId accordingly and when I create or update the entity it sets both the ParentProductId and the ChildProductId value to the ParentProductId.
I've followed the code through my debugger and it is correct up until the point where I call _context.Update(entity). After that both of the Ids in the RelatedProduct model are set to the same value.
I've got no idea why this is happening any suggestions would be very helpful.

I think there is a problem with the way your migration is generated. From the above code I am not sure it is clear what is the purpose of the child property in GSRelatedProduct. If you look at the migration you will see that the same constraint is set for both parent and child:
table.ForeignKey(
name: "FK_GSRelatedProducts_GSProducts_ChildProductId",
column: x => x.ChildProductId,
principalTable: "GSProducts",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
table.ForeignKey(
name: "FK_GSRelatedProducts_GSProducts_ParentProductId",
column: x => x.ParentProductId,
principalTable: "GSProducts",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
Both Parent and Child have the same principal table and principal column. They will both get the same value from GSProducts table. What your logic or business process is I cannot figure out, but you are actually getting the desired effect. Is the child supposed to point to something else? In your code you are probably assigning different values to product and child, but this seems to be somehow overwritten. Basically, your code first is thinking that both and parent should have the same value. In other words, Child seems to be redundant.
Is your GSProduct table a self-referencing table where you keep products and subproducts together? If so, then you need an additional column for this purpose like ParentId that points to the Id in order to get the desired relationship.

Related

how to use a key from a table for two different columns in another table using ef code first

I have tow tables with one-to-many relationship.
Flights Table
Destination Table
I have to use AirportCode from a Destination table for two columns in Flights table.
DepartureAirportCode - column_1
ArrivalAirportCode - c0lumn_2
How to do that by using EF code First?
I have tried with below codes. But That is not correct. I can add foreign key for only one column.
public class Flight
{
public int Id { get; set; }
[Required]
public string DepartureAirportCode { get; set; } // sholud be foreign key of Destinations_AirportCode
[Required]
public string ArrivalAirportCode { get; set; } // sholud be foreign key of Destinations_AirportCode
public Destination Airport { get; set; }
}
public class Destinations
{
public int Id { get; private set; }
public string AirportCode { get; private set; }
public IEnumerable<Flight> Flights { get; set; }
}
DbContext
builder.Entity<Flight>()
.HasOne(x => x.Airport)
.WithMany(x => x.Flights)
.HasForeignKey(x => x.ArrivalAirportCode)
.HasPrincipalKey(x => x.AirportCode);
builder.Entity<Flight>()
.HasOne(x => x.Airport)
.WithMany(x => x.Flights)
.HasForeignKey(x => x.DepartureAirportCode)
.HasPrincipalKey(x => x.AirportCode);
Edit: Added Migration file
I already have did migration and DB update for add ArrivalAirportCode as a foreignKey.
When I try to add a migration for DepartureAirportCode as a foreign key, I got this
migrationBuilder.DropForeignKey(
name: "FK_Flight_Destinations_ArrivalAirportCode",
table: "Flight");
// here dropped FK_Flight_Destinations_ArrivalAirportCode . But not added later.
migrationBuilder.DropForeignKey(
name: "FK_Flight_Departures_DepartureAirportCode",
table: "Flight");
// please ignore above line. Because already I used FK_Flight_Departures_DepartureAirportCode - foreign key from a different table named as Departures. Now I want to use FK_Flight_Destinations_DepartureAirportCode and FK_Flight_Destinations_ArrivalAirportCode from Destinations table
migrationBuilder.AddForeignKey(
name: "FK_Flight_Destinations_DepartureAirportCode",
table: "Flight",
column: "DepartureAirportCode",
principalTable: "Destinations",
principalColumn: "AirportCode",
onDelete: ReferentialAction.Cascade);
// added only FK_Flight_Destinations_DepartureAirportCode. I want two column with foreign key FK_Flight_Destinations_DepartureAirportCode and FK_Flight_Destinations_ArrivalAirportCode
Your problem is that you are trying to use the same properties for two different relationships between Flight and Destination. Both the Airport property on Flight and the Flights property on Destination are used for the "Arrival" relationship as well as for the "Destination" relationshio - and this is not possible with EF.
So in your Flight class you are missing a property for the second relationship to Destination as you cannot use the same property for Arrival and Depature (because they are different airports):
public class Flight
{
public int Id { get; set; }
[Required]
public string DepartureAirportCode { get; set; } // sholud be foreign key of Destinations_AirportCode
[Required]
public string ArrivalAirportCode { get; set; } // sholud be foreign key of Destinations_AirportCode
public Destination DepartureAirport { get; set; }
public Destination ArrivalAirport { get; set; }
}
And same for Destination:
public class Destination
{
public int Id { get; private set; }
public string AirportCode { get; private set; }
public IEnumerable<Flight> ArrivalFlights { get; set; }
public IEnumerable<Flight> DepartureFlights { get; set; }
}
once you add it and change your configuration to:
builder
.Entity<Flight>()
.HasOne(x => x.ArrivalAirport)
.WithMany(x => x.ArrivalFlights)
.HasForeignKey(x => x.ArrivalAirportCode)
.HasPrincipalKey(x => x.AirportCode);
builder.Entity<Flight>()
.HasOne(x => x.DepartureAirport)
.WithMany(x => x.DepartureFlights)
.HasForeignKey(x => x.DepartureAirportCode)
.HasPrincipalKey(x => x.AirportCode);
it should work.

EF core issue in inserting

I have some doubt on entity framwork. I have two classes User and UserClaim. When I run migration there are 3 tables created, User, UserClaim and UserUserClaim.
Is the new table UserUserClaim created by EF (columns UserId, ClaimId) bcos to maintain the relationship I guess. Am I correct?
When inserting User with UserClaims the values will be automatically added to the table UserUserClaim but when I add UserClaims only the values are not added, why it is so?
Is it possible not to create table UserUserClaim ?
public class User{
[Key]
public Guid UserId { get; set; }
[MaxLength(200)]
public string Username { get; set; }
[Required]
public Guid Subject { get; set; }
public ICollection<UserClaim> Claims { get; set; } = new List<UserClaim>();
}
public class UserClaim
{
[Key]
public Guid UserClaimId { get; set; }
[MaxLength(250)]
[Required]
public string Type{ get; set; }
[Required]
public Guid Subject { get; set; }
[MaxLength(250)]
[Required]
public string Value { get; set; }
public ICollection<User> Users { get; set; }
}
migration code
migrationBuilder.CreateTable(
name: "UserUserClaim",
columns: table => new
{
ClaimsUserClaimId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UsersUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserUserClaim", x => new { x.ClaimsUserClaimId, x.UsersUserId });
table.ForeignKey(
name: "FK_UserUserClaim_UserClaims_ClaimsUserClaimId",
column: x => x.ClaimsUserClaimId,
principalTable: "UserClaims",
principalColumn: "UserClaimId",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_UserUserClaim_Users_UsersUserId",
column: x => x.UsersUserId,
principalTable: "Users",
principalColumn: "UserId",
onDelete: ReferentialAction.Cascade);
});
1 - Yes, EF will automatically create said table for many-to-many relationships - users can have many claims, claim can be assigned to many users.
2 - You are adding a user and claims that are in relationship with said user - therefore this is reflected in the UserUserClaim table. Adding new claim does not create new relationship. If You'll add this new claim later to a user, entry will be made.
3 - No - said table always will be created when relation is many-to-many. You can only reconfigure its naming / keys etc. like this (more or less, example from my app):
builder.HasMany(x => x.Seasons)
.WithMany(x => x.DeparturePlans)
.UsingEntity<Dictionary<string, object>>(
"DeparturePlanSeason",
x => x.HasOne<Season>().WithMany(),
x => x.HasOne<DeparturePlan>().WithMany(),
x => x.ToTable("DeparturePlanSeason", "hub"));
This configures this third table for many-to-many relationship for entities called Season and DeparturePlan to be named "DeparturePlanSeason" in custom schema "hub".

Foreign key constraint may cause cycles or multiple cascade paths but only in one project

I've gone through the other questions on SO and none of them have helped in my instance, so please don't mark it as duplicate. I downloaded and ran the method from the documentation to get a many-to-many linked table. The sample project has
public class Book {
[Key]
public int BookId { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public ICollection<BookCategory> BookCategories { get; set; }
}
public class Category {
[Key]
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public ICollection<BookCategory> BookCategories { get; set; }
}
public class BookCategory {
public int BookId { get; set; }
public Book Book { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
with
modelBuilder.Entity<BookCategory>()
.HasKey(bc => new { bc.BookId, bc.CategoryId });
modelBuilder.Entity<BookCategory>()
.HasOne(bc => bc.Book)
.WithMany(b => b.BookCategories)
.HasForeignKey(bc => bc.BookId);
modelBuilder.Entity<BookCategory>()
.HasOne(bc => bc.Category)
.WithMany(c => c.BookCategories)
.HasForeignKey(bc => bc.CategoryId);
in OnModelCreating. This works fine and creates the correct migration file which works fine and looks like this
table.ForeignKey(
table.ForeignKey(
name: "FK_BookCategory_Book_BookID",
column: x => x.BookID,
principalTable: "Book",
principalColumn: "BookID",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_BookCategory_Category_CategoryID",
column: x => x.CategoryID,
principalTable: "Category",
principalColumn: "CategoryID",
onDelete: ReferentialAction.Restrict);
Copying the code into my project and adding a migration creates the same migration file. But, when I try to update the database I get the error
Introducing FOREIGN KEY constraint 'FK_BookCategory_Category_CategoryID' on table 'BookCategory' 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. See previous errors.
Swapping the two table.ForeignKey( statements around means that the error refers to FK_BookCategory_Book_BookID and only having one of them the database is updated successfully.
Any ideas why the migration works in one project but not another?
In protected override void OnModelCreating(ModelBuilder modelBuilder) { I removed
.HasForeignKey(bc => bc.BookId);
and replaced it with
.OnDelete(DeleteBehavior.Restrict);
so it looked like this.
modelBuilder.Entity<BookCategory>()
.HasOne(bc => bc.Book)
.WithMany(b => b.BookCategories)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<BookCategory>()
.HasOne(bc => bc.Category)
.WithMany(c => c.BookCategories)
.OnDelete(DeleteBehavior.Restrict);
This removes the cascade part so everything works. I've tested deleting records from each of the tables in SQL Management Studio and in code and none of the code cascades any deletes which means I'm happy.

Introducing FOREIGN KEY constraint - Basic Migration

I tried to make some project in C# with entity framework core 2.1. However, there is a problem that I can't solve, since I don't see anything wrong.
I'm trying to just do a simple migrate in my database.
There is no problem until I write 'Update-Database' to Package manager console. After trying to update database, here is the error message:
Introducing FOREIGN KEY constraint 'FK_Users_Baskets_BasketId' on table 'Users' 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. See previous errors.
Basket.cs
public class Basket {
[Key]
public int BasketId { get; set; }
public List<ProductByBasket> ProductByBaskets { get; set; }
public string BasketName { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
}
Product.cs
public class Product {
[Key]
public int ProductId { get; set; }
public List<ProductByBasket> ProductByBaskets { get; set; }
public string ProductName { get; set; }
}
ProductByBasket.cs
[Key]
public int ProductByBasketId { get; set; }
public int BasketId { get; set; }
[ForeignKey("BasketId")]
public Basket Basket { get; set; }
public int ProductId { get; set; }
[ForeignKey("ProductId")]
public Product Product { get; set; }
}
Migration File
migrationBuilder.CreateTable(
name: "ProductByBaskets",
columns: table => new
{
BasketId = table.Column<int>(nullable: false),
ProductId = table.Column<int>(nullable: false),
ProductByBasketId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProductByBaskets", x => new { x.ProductId, x.BasketId });
table.UniqueConstraint("AK_ProductByBaskets_ProductByBasketId", x => x.ProductByBasketId);
table.ForeignKey(
name: "FK_ProductByBaskets_Products_ProductId",
column: x => x.ProductId,
principalTable: "Products",
principalColumn: "ProductId",
onDelete: ReferentialAction.Cascade);
});
ApplicationDbContext.cs
public class ApplicationDbContext : DbContext {
public ApplicationDbContext() { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
string connectionString = #"Data Source=...\SQLEXPRESS; Initial Catalog = db; Integrated Security=true;";
optionsBuilder.UseSqlServer(connectionString);
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<ProductByBasket>()
.HasOne(u => u.Basket).WithMany(u => u.ProductByBaskets).IsRequired().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<ProductByBasket>()
.HasKey(x => new { x.ProductId, x.BasketId });
modelBuilder.Entity<ProductByBasket>()
.HasOne(pt => pt.Basket)
.WithMany(p => p.ProductByBaskets)
.HasForeignKey(pt => pt.BasketId);
modelBuilder.Entity<ProductByBasket>()
.HasOne(pt => pt.Product)
.WithMany(t => t.ProductByBaskets)
.HasForeignKey(pt => pt.ProductId);
}
public DbSet<Product> Products { get; set; }
public DbSet<Basket> Baskets { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<ProductByBasket> ProductByBaskets { get; set; }
}
I tried to configure migration file to write. Then, it looks like:
migrationBuilder.CreateTable(
name: "ProductByBaskets",
columns: table => new
{
BasketId = table.Column<int>(nullable: false),
ProductId = table.Column<int>(nullable: false),
ProductByBasketId = table.Column<int>(nullable: false).Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn)
},
constraints: table =>
{
table.PrimaryKey("PK_ProductByBaskets", x => x.ProductByBasketId);
table.ForeignKey(
name: "FK_ProductByBaskets_Baskets_BasketId",
column: x => x.BasketId,
principalTable: "Baskets",
principalColumn: "BasketId",
onDelete: ReferentialAction.Cascade);
table.PrimaryKey("PK_ProductByBaskets", x => new { x.ProductId, x.BasketId });
table.UniqueConstraint("AK_ProductByBaskets_ProductByBasketId", x => x.ProductByBasketId);
table.ForeignKey(
name: "FK_ProductByBaskets_Products_ProductId",
column: x => x.ProductId,
principalTable: "Products",
principalColumn: "ProductId",
onDelete: ReferentialAction.Cascade);
});
Then I get this error:
Foreign key references invalid table.
What am I doing wrong?

EF Core OnDelete restrict adds additional column

I have models in a many-to-many relationship:
User
public class User
{
public int Id { get; set; }
public int CompanyId { get; set; }
[Required]
[StringLength(100)]
public string Username { get; set; }
[Required]
public string PasswordHash { get; set; }
[ForeignKey("CompanyId")]
public Company Company { get; set; }
public ICollection<UserRole> UserRoles { get; set; }
}
Role
public class Role
{
public int Id { get; set; }
public int CompanyId { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[StringLength(500)]
public string Description { get; set; }
[ForeignKey("CompanyId")]
public Company Company { get; set; }
public ICollection<RolePrivilege> RolePrivileges { get; set; }
}
UserRole
public class UserRole
{
public int Id { get; set; }
public int UserId { get; set; }
public int RoleId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
[ForeignKey("RoleId")]
public Role Role { get; set; }
}
When I created migrations and then tried update-database, it threw an error of multiple cascade paths. The solution to this was to make On Delete, No Action so I added this in OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserRole>()
.HasIndex(e => new { e.UserId, e.RoleId })
.IsUnique();
modelBuilder.Entity<UserRole>()
.HasOne(e => e.User)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<UserRole>().ToTable("UserRoles");
}
Now the tables are created but one thing that I wasn't expecting is its making an extra column. The migration code looks like this after generating:
migrationBuilder.CreateTable(
name: "UserRoles",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
RoleId = table.Column<int>(type: "int", nullable: false),
UserId = table.Column<int>(type: "int", nullable: false),
UserId1 = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_UserRoles", x => x.Id);
table.ForeignKey(
name: "FK_UserRoles_Roles_RoleId",
column: x => x.RoleId,
principalTable: "Roles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_UserRoles_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_UserRoles_Users_UserId1",
column: x => x.UserId1,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
As you can see, it added an extra column, UserId1.
What am I doing wrong or how do I prevent this from happening?
This is a result of a typical relationship fluent configuration mistake - using a parameterless overload of Has / With (effectively telling EF that there is no corresponding navigation property) while actually a navigation property exists. In that case EF will map the missing navigation property to another relationship with no navigation property at the other end and default by convention FK property/column name.
To fix the issue, make sure to use the correct overloads which represent the presence/absence of a navigation property (and update them according in case you add/remove navigation property). In your case, replace
.WithMany()
with
.WithMany(e => e.UserRoles)