Extra foreign key in Entity Framework 6 - c#

I designed employee and employee relation tables as shown below:
public class Employee
{
public int EmployeeId { get; set; }
public EmployeeType EmployeeType { get; set; }
public int? ReportingManagerId { get; set; }
public Employee ReportingManager { get; set; }
public virtual List<EmployeeRelation> Colleagues { get; set; }
}
public class EmployeeRelation
{
public int EmployeeRelationId { get; set; }
public int EmployeeId { get; set; }
public Employee Employee { get; set; }
public int ColleagueId { get; set; }
public Employee Colleague { get; set; }
}
And corresponding configuration classes as:
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
HasKey(e => e.EmployeeId);
Property(e => e.EmployeeId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
HasOptional(e => e.ReportingManager).WithMany().HasForeignKey(e => e.ReportingManagerId);
}
}
public class EmployeeRelationConfiguration : EntityTypeConfiguration<EmployeeRelation>
{
public EmployeeRelationConfiguration()
{
HasRequired(e => e.Employee).WithMany().HasForeignKey(e => e.EmployeeId).WillCascadeOnDelete(false);
HasRequired(e => e.Colleague).WithMany().HasForeignKey(e => e.ColleagueId).WillCascadeOnDelete(false);
}
}
With the above code, everything is fine except that, for the table EmployeeRelations, EF 6.0.2 is creating one additional ForeignKey for EmployeeRelation as Employee_EmployeeId (in addition to EmployeeId & ColleagueId ForeignKeys). But if, I use Data Annotations instead of Fluent API, this extra ForeignKey is gone. I don't like Annotations and want to use Fluent API only. Can someone help me what is wrong with my code?
Thanks in advance.
UPDATE:
I resolved it myself.
Problem is multiple relations. If we look at EmployeeRelation class, it has 2 properties of type Employee which are binding to the Employee class and this is where EF is getting confused. To avoid this problem, we need to use overloaded version of WithMany API:
HasRequired(e => e.Employee).WithMany().HasForeignKey(e => e.EmployeeId).WillCascadeOnDelete(false);
HasRequired(e => e.Colleague).WithMany(e => e.Colleagues).HasForeignKey(e => e.EmployeeId).WillCascadeOnDelete(false);

Related

Efcore issue with many 2 many on same entity (Products & RelatedProducts)

So i want to add RelatedProducts to my products. So i applied the same relationship type as i did for category. The only difference there is no category class, but we target the same entity. (product). Because the regular many2many works fine, i removed that from my example.
Goal:
Any suggestions?
Exception:
"Exception occured: Cannot create a relationship between 'Product.RelatedProducts' and 'RelatedProduct.Related' because a relationship already exists between 'Product.RelatedProducts' and 'RelatedProduct.Product'. Navigation properties can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation 'RelatedProduct.Related' first in 'OnModelCreating'."
Product.cs:
public class Product : IExportable, IEntityBase
{
public int Id { get; set; }
public string Name { get; set;}
public ICollection<RelatedProduct> RelatedProducts { get; set; }
}
ProductEntityTypeConfiguration.cs
internal class ProductEntityTypeConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
config.HasKey(p => p.Id);
}
}
RelatedProducts.cs
public class RelatedProduct
{
public int Id { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public int RelatedId { get; set; }
public virtual Product Related { get; set; }
}
RelatedProductEntityTypeConfiguration.cs
public class RelatedProductsEntityConfiguration : IEntityTypeConfiguration<RelatedProduct>
{
public void Configure(EntityTypeBuilder<RelatedProduct> builder)
{
builder.HasKey(rp => rp.Id);
builder.ToTable("RelatedProducts");
builder
.HasOne(rp => rp.Product)
.WithMany(p => p.RelatedProducts)
.HasForeignKey(rp => rp.ProductId)
.OnDelete(DeleteBehavior.Restrict);
builder
.HasOne(rp => rp.Related)
.WithMany(p => p.RelatedProducts)
.HasForeignKey(rp => rp.RelatedId)
.OnDelete(DeleteBehavior.Restrict);
}
}
I am unable to test this suggestion but I am sure it might work. Why don't you just define your RelatedProduct as:
public class RelatedProduct
{
public int Id { get; set; }
public virtual ICollection<Product> Related { get; set; }
}
I also suggest you comment out the code in your RelatedProductsEntityConfiguration Configure method

Is it possible to use an owned type for many-to-many relations?

In Entity Framework Core version 2.2 or 3.0, is it possible to use owned/complex types in such a way that this kind of configuration is possible:
public class Product {
public int Id { get; set; }
public string Name { get; set; }
public ProductProperties Properties { get; set; }
}
public class ProductProperties {
public List<ProductSize> Sizes { get; set; }
}
public class Size {
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductSize {
public int ProductId { get; set; }
public Product Product { get; set; }
public int SizeId { get; set; }
public Size Size { get; set; }
}
modelBuilder.Entity<ProductSize>()
.HasOne(x => x.Product)
.WithMany(x => x.Properties.Sizes)
.HasForeignKey(x => x.ProductId);
modelBuilder.Entity<ProductSize>()
.HasOne(x => x.Size)
.WithMany()
.HasForeignKey(x => x.SizeId);
The error message which is seen for this kind of approach usually ends up in:
'x => x.Properties.Sizes' is not a valid property expression. The expression should represent a simple property access: 't => t.MyProperty'
An earlier found answer is almost exactly matching my question, but this was posted in 2013. By the time it was almost certainly not possible.
HasForeignKey relationship through a Complex Type property
The sources on Microsoft are only giving examples for creating an entity with the complex type itself, not for creating relationships between them.
The cause of the issue
In your sample code it's quite clear there is no specific Many to Many relation. To make my argument a bit more convincing what follows is a model of your entities and their relations:
The new class structure
For a Many to Many relation to work in EF the product and size tables need to have an implicit relation with each other through a singular junction table. In my proposed solution I've chosen the ProductProperty table. There I've added the fields from the productsize junction table:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProductProperty> Properties { get; set; }
}
public class ProductProperty
{
public int ProductId { get; set; }
public Product Product { get; set; }
public int SizeId { get; set; }
public Size Size { get; set; }
}
public class Size
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProductProperty> Properties { get; set; }
}
The functions
modelBuilder.Entity<ProductProperty>()
.HasKey(pp => new { pp.ProductId, pp.SizeId });
modelBuilder.Entity<ProductProperty>()
.HasOne(pp => pp.Product)
.WithMany(p => p.Properties)
.HasForeignKey(pp => pp.ProductId);
modelBuilder.Entity<ProductProperty>()
.HasOne(pp => pp.Size)
.WithMany(p => p.Properties)
.HasForeignKey(pp => pp.SizeId);
Additional advice (EDIT)
Make the "Size" class a generic property class. This way the Many-to-Many relation won't get broken and querying will also be very easy:
public class Property
{
public int Id { get; set; }
public PropertyType propType { get; set; }
public string propValue { get; set; }
}
public enum PropertyType
{
Size,
Fontsize,
...
}
As a final argument this change will make it easier to change existing properties or add new ones
Sources
https://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration
You can check the owned entity types released in 2019 Check documentation here
An example from the link is the following:
public class Distributor
{
public int Id { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
The owns many function should help you like this:
modelBuilder.Entity<Distributor>().OwnsMany(p => p.ShippingCenters, a =>
{
a.WithOwner().HasForeignKey("OwnerId");
a.Property<int>("Id");
a.HasKey("Id");
});
Let me know if I misunderstood your question.

Entity Framework Core code first - 0..1 to many relationship and cascade delete

I'm trying to create a commenting system backed by Entity Framework Core where multiple entities of different type can have comments attached to them.
These are my entities. (In the real application there are about 7 in total with varying relationships but this is how it generally looks)
public class Comment : IEntityBase
{
public int Id { get; set; }
public int? FreezerId{ get; set; }
public Freezer Freezer { get; set; }
public int? BoxId{ get; set; }
public Box Box{ get; set; }
public string Author { get; set; }
public DateTime CreatedAt { get; set; }
public string Content { get; set; }
}
public class Freezer: IEntityBase
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public ICollection<Box> Boxes{ get; set; }
public ICollection<Comment> Comments { get; set; }
}
public class Box: IEntityBase
{
public int Id { get; set; }
public Freezer Freezer{get; set;}
public int FreezerId{get; set;}
public string Data{ get; set; }
public ICollection<Comment> Comments { get; set; }
}
I want the Comment entity to be attached to one Freezer or one Box, but not both at the same time.
I defined the relationship in the fluent API as the following:
builder.Entity<Box>(boxBuilder=>
{
boxBuilder.HasOne(box=> box.Freezer)
.WithMany(freezer => freezer.boxes)
.HasForeignKey(box => box.FreezerId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
boxBuilder.HasMany(box => box.Comments)
.WithOne(comment => comment.Box)
.HasForeignKey(comment => comment.BoxId)
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<Freezer>(freezerBuilder =>
{
freezerBuilder.HasMany(freezer=> freezer.Comments)
.WithOne(comment => comment.Freezer)
.HasForeignKey(comment => comment.FreezerId)
.OnDelete(DeleteBehavior.Cascade);
});
When I try to update the database to this model I get the following error:
System.Data.SqlClient.SqlException: Introducing FOREIGN KEY constraint 'FK_Comment_Boxes_BoxId' on table 'Comment' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I think the error comes from the Box and the Freezer property in the Comment class not being optional which would make this a 1 to many relationship instead of a 0..1 to many relationship which is what I want.
With Entity Framework 6 I would just use the .HasOptional() method, but this doesn't exist in Entity Framework Core
I think one way to solve this would be to just subclass the Comment class and create a unique comment class for each entity that can be commented on and move the foreign key and reference property to that subclass instead.
But it feels like I shouldn't have to do it this way.
You have to disable the cascade delete(DeleteBehavior.Restrict) then it will works for you:
modelBuilder.Entity<Box>(boxBuilder =>
{
boxBuilder.HasOne(box => box.Freezer)
.WithMany(freezer => freezer.Boxes)
.HasForeignKey(box => box.FreezerId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
boxBuilder.HasMany(box => box.Comments)
.WithOne(comment => comment.Box)
.HasForeignKey(comment => comment.BoxId)
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity<Freezer>(freezerBuilder =>
{
freezerBuilder.HasMany(freezer => freezer.Comments)
.WithOne(comment => comment.Freezer)
.HasForeignKey(comment => comment.FreezerId)
.OnDelete(DeleteBehavior.Restrict);
});
base.OnModelCreating(modelBuilder);
}
Usage:
using (var myConext = new MyDbContext())
{
myConext.Database.EnsureCreated();
myConext.Boxes.Add(new Box() {Freezer = new Freezer()});
myConext.SaveChanges();
}

EF Code First many-to-many: how to specify name of th third table and add some other properties to it?

I need to implement the many-to-many relationship in the Entity Framework Code First, and map this relationship to the third table. And I want to add to this table some other fields such as autoincremented Id and the AppointmentDateTime for exaple:
public class User {
public int Id { get; set; }
// some other properties ......
public virtual List<Role> Roles { get; set; }
}
public class UserTypeConfiguration : EntityTypeConfiguration<User> {
public UserTypeConfiguration() {
HasKey(k => k.Id);
Property(p => p.Email).IsRequired();
//[∞ — ∞]
HasMany<Role>(u => u.Roles).WithMany(r => r.Users).Map(m => m.MapLeftKey("UserId").MapRightKey("RoleId").ToTable("UserRoles"));
}
}
But Entity Framework generates a table with the wrong name that which I have passed, and wrong names navigation proiperties I have passed to the mapping.
The name of table "RoleUsers" and the names of navigation proiperties are "User_Id" and "Role_Id".
How to implement correct names of mapping and how to add some other properties to the UserRoles table?
Since you need to add additional properties to describe the relationship, you have to consider many-to-many association as two one-to many ones:
public class User
{
// other properties omitted
public virtual List<UserRole> UserRoles { get; set; }
}
public class Roles
{
// other properties omitted
public virtual List<UserRole> UserRoles { get; set; }
}
public class UserRole
{
public int Id { get; set; }
public DateTime AppointmentDateTime { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; }
}
Configuration:
public class UserRoleConfiguration : EntityTypeConfiguration<UserRole>
{
public UserRoleConfiguration()
{
// scalar properties config omitted
HasRequired(_ => _.User)
.WithMany(_ => _.UserRoles)
.HasForeignKey(_ => _.UserId);
HasRequired(_ => _.Role)
.WithMany(_ => _.UserRoles)
.HasForeignKey(_ => _.RoleId);
ToTable("UserRoles");
}
}

Avoid circular dependency in Entity Framework Code First

Assume I have two entities: Nationality and Employee with relationship 1:n.
public class Employee
{
public int Id { get; set; }
// More Properties
public virtual Nationality Nationality { get; set; }
}
public class Nationality
{
public int Id { get; set; }
public string Name { get; set; }
}
In order to use Code-First with Entity Framework, I have to add one more property: Employees which I don't expect into Nationality (it creates circular dependency):
public class Nationality
{
public int Id { get; set; }
public string Name { get; set; }
// How to avoid this property
public virtual List<Employee> Employees { get; set; }
}
So that I can configure the relationship 1: n in Configuration class:
internal class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
HasKey(f => f.Id);
HasRequired(e => e.Nationality)
.WithMany(e => e.Employees)
.Map(c => c.MapKey("NationalityId"));
ToTable("Employees");
}
}
Is there any other approach which I can avoid circular dependency and eliminate property Employees out of Nationality class?
Use the WithMany() overload to configure the mapping.
internal class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
HasKey(f => f.Id);
HasRequired(e => e.Nationality)
.WithMany()
.Map(c => c.MapKey("NationalityId"));
ToTable("Employees");
}
}

Categories