EF Core 6 code first one to zero or one navigation - entity-framework-core

I have googled as well as searched SO for this extensively, to no avail.
class Account
{
[Key]
[StringLength(80)]
public string AccountID { get; set; }
[StringLength(80)]
public string Name { get; set; }
[StringLength(80)]
public string Email { get; set; }
[ForeignKey("AccountID")]
public virtual Address? Address { get; set; }
}
class Address
{
[Key]
[StringLength(80)]
public string AccountID { get; set; }
[StringLength(80)]
public string City { get; set; }
[StringLength(80)]
public string Street { get; set; }
}
Address should be optional - if it's not there, the application should still work.
The migration builder seems to have the relationship between the tables inverted:
migrationBuilder.CreateTable(
name: "Account",
columns: table => new
{
AccountID = table.Column<string>(type: "varchar(80)", maxLength: 80, nullable: false),
Name = table.Column<string>(type: "varchar", nullable: true),
Email = table.Column<string>(type: "varchar", nullable: true),
},
constraints: table =>
{
table.PrimaryKey("PK_Account", x => x.AccountID);
table.ForeignKey(
name: "FK_Account_Address_AccountID",
column: x => x.AccountID,
principalTable: "Address",
principalColumn: "AccountID");
});
Now when I try to insert data in the Account table, I am getting an error message about a conflict with a foreign key restraint in the Address table. It looks like there must already be a record in the Address table with the account id I am using to insert data in the Account table.
How do I fix this?
Hint: My fluent API does not offer .Optional() or .WithOptional().

Related

Adding navigation properties EF Core .NET Core 3.1 - CREATE UNIQUE INDEX Error

I have the following two tables:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
...
public ApplicationUser RegisteredPerson { get; set; }
}
public class ApplicationUser : IdentityUser
{
public DateTime DateJoined { get; set; }
public int PersonId { get; set; }
public string DisplayName { get; set; }
...
public Person Person { get; set; }
}
When creating a new migration to add the navigation properties, the following migration code is produced:
migrationBuilder.CreateIndex(
name: "IX_AspNetUsers_PersonId",
table: "AspNetUsers",
column: "PersonId",
unique: true);
migrationBuilder.AddForeignKey(
name: "FK_AspNetUsers_Persons_PersonId",
table: "AspNetUsers",
column: "PersonId",
principalTable: "Persons",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
I get the following error when trying to update the database with the new migration
CREATE UNIQUE INDEX [IX_AspNetUsers_PersonId] ON [AspNetUsers] ([PersonId]);
The CREATE UNIQUE INDEX statement terminated because a duplicate key was found for the object name 'dbo.AspNetUsers' and the index name 'IX_AspNetUsers_PersonId'. The duplicate key value is (0).
Suggestions for fix appreciated.
Thanks

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".

Many to Many relationship Entity Framework Core 5

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.

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

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.

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)