Many to Many relationship Entity Framework Core 5 - entity-framework

I created a Blazor project and I have a many-to-many relationship between these classes:
public class ItemAttribute
{
[Key]
public int ItemAttributeId { get; set; }
public string Title { get; set; }
public ICollection<Item> Items { get; set; }
public ICollection<ItemAttributeCluster> itemAttributeClusters { get; set; }
}
and
public class ItemAttributeCluster
{
[Key]
public int ItemAttributeClusterId { get; set; }
public string Titel { get; set; }
public bool IsMultiChoice { get; set; }
public ICollection<ItemAttribute> itemAttributes { get; set; }
}
So far so good, EF generates the Join table ItemAttributeItemAttributeCluster, ok.
Then I try to add a new cluster of ItemAttributes for the first time with my controller:
// Create
[HttpPost]
public async Task<IActionResult> Post(ItemAttributeCluster itemAttributeCluster)
{
_context.ItemAttributeClusters.Add(itemAttributeCluster);
await _context.SaveChangesAsync();
return Ok(itemAttributeCluster);
}
and I get this error:
Cannot insert explicit value for identity column in table 'ItemAttributes' when IDENTITY_INSERT is set to OFF.
What am I doing wrong? Why is EF trying to write something into 'ItemAttributes'? When i´m trying to create a new Cluster on 'ItemAttributesCluster' and the Join Table?
Migration Builder:
Join Table
migrationBuilder.CreateTable(
name: "ItemAttributeItemAttributeCluster",
columns: table => new
{
itemAttributeClustersItemAttributeClusterId = table.Column<int>(type: "int", nullable: false),
itemAttributesItemAttributeId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ItemAttributeItemAttributeCluster", x => new { x.itemAttributeClustersItemAttributeClusterId, x.itemAttributesItemAttributeId });
table.ForeignKey(
name: "FK_ItemAttributeItemAttributeCluster_ItemAttributeClusters_itemAttributeClustersItemAttributeClusterId",
column: x => x.itemAttributeClustersItemAttributeClusterId,
principalTable: "ItemAttributeClusters",
principalColumn: "ItemAttributeClusterId",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ItemAttributeItemAttributeCluster_ItemAttributes_itemAttributesItemAttributeId",
column: x => x.itemAttributesItemAttributeId,
principalTable: "ItemAttributes",
principalColumn: "ItemAttributeId",
onDelete: ReferentialAction.Cascade);
});
ItemAttributes
migrationBuilder.CreateTable(
name: "ItemAttributes",
columns: table => new
{
ItemAttributeId = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ItemAttributes", x => x.ItemAttributeId);
});
ItemAttributeCluster
migrationBuilder.CreateTable(
name: "ItemAttributeClusters",
columns: table => new
{
ItemAttributeClusterId = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Titel = table.Column<string>(type: "nvarchar(max)", nullable: true),
IsMultiChoice = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ItemAttributeClusters", x => x.ItemAttributeClusterId);
});

If this was an existing schema for the ItemAttribute / Cluster tables and their PK were defined as identity columns, you will need to tell EF to expect them using the [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] attribute alongside the Key designation.
When using a naming convention that EF recognizes like "ItemAttributeId" or "Id" I believe EF will default to assuming these are Identity columns, but with a name like "ItemAttributeCode" I believe it would assume a database generated option of "None" as default.

try to add some navigation properties
public ItemAttributeCluster()
{
AttributeClusters = new HashSet<AttributeCluster>();
}
[Key]
public int Id { get; set; }
public string Titel { get; set; }
public bool IsMultiChoice { get; set; }
[InverseProperty(nameof(AttributeCluster.ItemAttributeClaster))]
public virtual ICollection<AttributeCluster> AttributeClusters { get; set; }
}
public partial class ItemAttribute
{
public ItemAttribute()
{
AttributeClusters = new HashSet<AttributeCluster>();
}
[Key]
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Item> Items { get; set; }
[InverseProperty(nameof(AttributeCluster.ItemAttribute))]
public virtual ICollection<AttributeCluster> AttributeClusters { get; set; }
}
public partial class AttributeCluster
{
[Key]
public int Id { get; set; }
public int ItemAttributeId { get; set; }
public int ItemAttributeClasterId { get; set; }
[ForeignKey(nameof(ItemAttributeId))]
[InverseProperty("AttributeClusters")]
public virtual ItemAttribute ItemAttribute { get; set; }
[ForeignKey(nameof(ItemAttributeClasterId))]
[InverseProperty(nameof(ItemAttributeCluster.AttributeClusters))]
public virtual ItemAttributeCluster ItemAttributeClaster { get; set;
}
dbcontext (no any fluent apis at all)
public virtual DbSet<AttributeCluster> AttributeClusters { get; set; }
public virtual DbSet<ItemAttribute> ItemAttributes { get; set; }
public virtual DbSet<ItemAttributeCluster> ItemAttributeClusters { get; set; }
Test
var itemAttributeClaster = new ItemAttributeCluster { Titel="titleClaster2", IsMultiChoice=false};
var itemAttribute = new ItemAttribute{Title="attrTitle" };
var attributeClaster = new AttributeCluster { ItemAttribute = itemAttribute, ItemAttributeClaster = itemAttributeClaster };
_context.AttributeClusters.Add(attributeClaster);
_context.SaveChanges();
it created 1 record in each of 3 tables

I give up on getting this to work with ef. I run several sql`s directly to achieve the same functionality and so far it works, not a satisfactory solution but it needs to be done.

Related

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)

Not sure how to setup entity for list

I have a user
public class UserEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
[Index(IsUnique=true)]
[Required, StringLength(50)]
public string Username { get; set; }
public List<UserTokenEntity> Tokens { get; set; }
}
that has a list of tokens and I'm not sure how to set it up so that the key to UserEntityId is not null when I generate the migration. This is my TokenEntity
public class UserTokenEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[StringLength(512)]
[Required]
public string TokenHash { get; set; }
[StringLength(128)]
[Required]
public string Device { get; set; }
}
when I add a migration for these entities a UserEntity_Id is created for the database on the token entity but it is a nullable int and I want nullable to be false
here is the migration generated
public override void Up()
{
CreateTable(
"dbo.UserTokenEntities",
c => new
{
Id = c.Int(nullable: false, identity: true),
TokenHash = c.String(nullable: false, maxLength: 512),
Device = c.String(nullable: false, maxLength: 128),
UserEntity_Id = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.UserEntities", t => t.UserEntity_Id)
.Index(t => t.UserEntity_Id);)
}
does anyone know how to set up the token entity to get the UserEntity_Id to be nullable: false without manually changing it?
You can explicitly define the relationship:
public class UserTokenEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[StringLength(512)]
[Required]
public string TokenHash { get; set; }
[StringLength(128)]
[Required]
public string Device { get; set; }
public int UserEntityId { get; set; }
[ForeignKey("UserEntityId")]
public UserEntity UserEntity { get; set; }
}
https://msdn.microsoft.com/en-us/data/jj591583.aspx#Relationships

EF code first - add inverse propterty with optional element

I have the following classes
public class Order {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public DateTime Date { get; set; }
[Required]
[MaxLength(100)]
public string From { get; set; }
public int? TreatGuestEntryID { get; set; }
[ForeignKey("TreatGuestEntryID")]
public TreatedGuestEntry TreatGuestEntry { get; set; }
...
public class TreatedGuestEntry {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[MaxLength(200)]
public string Company { get; set; }
public string TypeOfTreat { get; set; }
This works as expected - in my Orders table it creates the foreign key.
Now I want to add an inverse property in TreatedGuestEntry for the order.
The best (at least somehow working) result I get when I add
modelBuilder.Entity<TreatedGuestEntry>()
.HasOptional(a => a.Order)
.WithOptionalDependent(a => a.TreatGuestEntry)
.Map(a=>a.MapKey("TreatGuestEntryID"));
and further rename the key of TreatedGuestEntry to TreatGuestEntryID.
But I get no relation in the database and also TreatGuestEntryID in the table Order is no longer a key (FK).
My approach in simple words:
In my Order I want an optional TreatedGuestEntry (and I need access to the foreign key) - and further in the related TreatedGuestEntry I want to access the Order.
In your case, the FK TreatGuestEntryID is not a PK, it means that it is a 1:n relationship. So, you have to put a Collection of Order on the other side:
public class Order
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public DateTime Date { get; set; }
[Required]
[MaxLength(100)]
public string From { get; set; }
public int? TreatGuestEntryID { get; set; }
public TreatedGuestEntry TreatGuestEntry { get; set; }
}
public class TreatedGuestEntry
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[MaxLength(200)]
public string Company { get; set; }
public string TypeOfTreat { get; set; }
public ICollection<Order> Orders { get; set; }
}
Mapping:
modelBuilder.Entity<Order>()
.HasOptional(i => i.TreatGuestEntry)
.WithMany(i => i.Orders)
.HasForeignKey(i => i.TreatGuestEntryID)
.WillCascadeOnDelete(false);
Generated Migration:
CreateTable(
"dbo.Orders",
c => new
{
ID = c.Int(nullable: false, identity: true),
Date = c.DateTime(nullable: false),
From = c.String(nullable: false, maxLength: 100),
TreatGuestEntryID = c.Int(),
})
.PrimaryKey(t => t.ID)
.ForeignKey("dbo.TreatedGuestEntries", t => t.TreatGuestEntryID)
.Index(t => t.TreatGuestEntryID);
CreateTable(
"dbo.TreatedGuestEntries",
c => new
{
ID = c.Int(nullable: false, identity: true),
Company = c.String(maxLength: 200),
TypeOfTreat = c.String(),
})
.PrimaryKey(t => t.ID);

EF Code First cascade delete doesn't work

I have 4 tables:
User table
public enum SEX { Male, Female }
public abstract class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public SEX Sex { get; set; }
}
Doctor table inherites from User
[Table("Doctor")]
public class Doctor : User
{
public string Department { get; set; }
public string Occupation { get; set; }
public string CabinetNumber { get; set; }
public virtual List<Treat> Treats { get; set; }
}
Patient table inherites from User
[Table("Patient")]
public class Patient : User
{
public int InsuranceNumber { get; set; }
public int CardNumber { get; set; }
public virtual List<Treat> Treats { get; set; }
}
public class Treat
{
public int TreatId { get; set; }
public int DoctorUserId { get; set; }
public int PatientUserId { get; set; }
public virtual Doctor Doctor { get; set; }
public virtual Patient Patient { get; set; }
}
public class HospitalContext: DbContext
{
public HospitalContext() : base("DBConnectionString") {
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<HospitalContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Treat>()
.HasRequired(x => x.Doctor)
.WithMany( x => x.Treats)
.HasForeignKey( x => x.DoctorUserId)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Treat>()
.HasRequired(x => x.Patient)
.WithMany( x => x.Treats)
.HasForeignKey( x => x.PatientUserId)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
public DbSet<User> Users { get; set; }
public DbSet<Treat> Treats { get; set; }
}
I have found much answers here but no one from them works. I have spend a few hours trying to make it work. I know that Entity Framework must enable cascade delete when there is one-to-many relation, but it didn't
Entity Framework doesn't apply cascade deletion with TPT (Table Per Type) inheritance. You can solve this with Code Fist migrations:
CreateTable(
"dbo.Treats",
c => new
{
TreatId = c.Int(nullable: false, identity: true),
DoctorUserId = c.Int(nullable: false),
PatientUserId = c.Int(nullable: false),
})
.PrimaryKey(t => t.TreatId)
.ForeignKey("dbo.Doctor", t => t.DoctorUserId, cascadeDelete: true)
.ForeignKey("dbo.Patient", t => t.PatientUserId, cascadeDelete: true)
.Index(t => t.DoctorUserId)
.Index(t => t.PatientUserId);
The important part is cascadeDelete: true. You have to manually add it after migration code generation. After that you will have cascade deletion in your database:
FOREIGN KEY ([DoctorUserId]) REFERENCES [dbo].[Doctor] ([UserID]) ON DELETE CASCADE,
FOREIGN KEY ([PatientUserId]) REFERENCES [dbo].[Patient] ([UserID]) ON DELETE CASCADE