EF Core – Complicated Relationships - c#

I have complicated relationships between these entities:
Country
Airport
Airline
Flight
Country has many Airlines and many Airports.
Airline has one Countries, the same about Airport.
Airline has many Flights,
Airport has many DepartureFlights and ArrivalFlights (both are Flight type).
Flight has one Airline and one DepartureAirport and one ArrivalAirport (both are Airport type).
Country can have no airlines and airports,
Airline can have no Flights,
Airport can have neither DepartureFlights nor ArrivalFlights.
What I am trying to do is when Country is deleted, then all related Airlines and Airports are deleted, also when Airline or DepartureAirport or ArrivalAirport are deleted, all related Flights are deleted also,
but when updating my db after the migration is created I'm getting an error:
Introducing FOREIGN KEY constraint "FK_Flights_Airports_DepartureAirport" on table "Flights" may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
How to implement this behavior and prevent an error?
Here are my models:
Country:
public class Country
{
public int Id { get; set; }
/* other properties */
public virtual ICollection<Airport>? Airports { get; set; }
public virtual ICollection<Airline>? Airlines { get; set; }
}
Airline:
public class Airline
{
public int Id { get; set; }
/* other properties */
public int CountryId { get; set; }
public virtual Country Country { get; set; }
public virtual ICollection<Flight>? Flights { get; set; }
}
Airport:
public class Airport
{
public int Id { get; set; }
public string Name { get; set; }
/* other properties */
public int CountryId { get; set; }
public virtual Country Country { get; set; }
public virtual ICollection<Flight>? DepartureFlights { get; set; }
public virtual ICollection<Flight>? ArrivalFlights { get; set; }
}
Flight:
public class Flight
{
public int Id { get; set; }
/* other properties */
public int AirlineId { get; set; }
public int DepartureAirportId { get; set; }
public int ArrivalAirportId { get; set; }
public virtual Airline Airline { get; set; }
public virtual Airport DepartureAirport { get; set; }
public virtual Airport ArrivalAirport { get; set; }
}
After all the DBContext file:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Airline> Airlines { get; set; }
public DbSet<Airport> Airports { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Flight> Flights { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Airline>(x =>
{
x.HasKey(a => a.Id);
x.HasOne(c => c.Country)
.WithMany(a => a.Airlines)
.HasForeignKey(a => a.CountryId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity<Airport>(x =>
{
x.HasKey(a => a.Id);
x.HasOne(c => c.Country)
.WithMany(a => a.Airports)
.HasForeignKey(a => a.CountryId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity<Country>(x =>
{
x.HasKey(c => c.Id);
});
modelBuilder.Entity<Flight>(x =>
{
x.HasKey(f => f.Id);
x.HasOne(a => a.Airline)
.WithMany(f => f.Flights)
.HasForeignKey(f => f.AirlineId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
x.HasOne(a => a.DepartureAirport)
.WithMany(f => f.DepartureFlights)
.HasForeignKey(f => f.DepartureAirportId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
x.HasOne(a => a.ArrivalAirport)
.WithMany(f => f.ArrivalFlights)
.HasForeignKey(f => f.ArrivalAirportId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
}
}

How to implement this behavior and prevent an error?
This is officially known issue. Therefore, We have two ways to handle this scenario thus the error:
1. Change one or more of the relationships to not cascade delete.
In this scenario, we could make the Country relationship with Airport, Flight and Airlines optional by giving it a nullable foreign key property: for instance we can do something like:
.IsRequired(false);
Note: You can check our official document for more details.
2. Configure the database without one or more of these cascade deletes,
then ensure all dependent entities are loaded so that EF Core can
perform the cascading behavior.
Considering this appreach we can keep the Airport, Flight and Airlines relationship required and configured for cascade delete, but make this configuration only apply to tracked entities, not the database: So we can do somethng like below:
modelBuilder.Entity<Airline>(x =>
{
x.HasKey(a => a.Id);
x.HasOne(c => c.Country)
.WithMany(a => a.Airlines)
.HasForeignKey(a => a.CountryId)
.OnDelete(DeleteBehavior.ClientCascade);
.IsRequired(false);
});
modelBuilder.Entity<Airport>(x =>
{
x.HasKey(a => a.Id);
x.HasOne(c => c.Country)
.WithMany(a => a.Airports)
.HasForeignKey(a => a.CountryId)
.OnDelete(DeleteBehavior.ClientCascade);
.IsRequired(false);
});
Note: You can apply same for Flight as well. In addition, As you may know OnDelete(DeleteBehavior.ClientCascade); or ClientCascade allows the DBContext to delete entities even if there is a cyclic ref or LOCK on it. Please read the official guideline for more details here

add these lines in "OnModelCreating"
var cascades = modelBuilder.Model.GetEntityTypes()
.SelectMany(t => t.GetForeignKeys())
.Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade);
foreach (var fk in cascades)
fk.DeleteBehavior = DeleteBehavior.Restrict;

Related

EF Core: Invalid column name 'companyId1'

I have trouble understanding why a EF generated SELECT clause contains the primary key twice, the second one is postfixed with '1'.
exec sp_executesql N'SELECT [entity.WebAdminCompanyUser].[CompanyId], [entity.WebAdminCompanyUser].[AspNetUserId], [entity.WebAdminCompanyUser].[CompanyId1]
FROM [SafeProtect].[WebAdminCompanyUser] AS [entity.WebAdminCompanyUser]
INNER JOIN (
SELECT [entity1].[AspNetUserId]
FROM [SafeProtect].[WebAdminUser] AS [entity1]
WHERE ([entity1].[RowEnabled] = 1) AND EXISTS (
SELECT 1
FROM [SafeProtect].[WebAdminCompanyUser] AS [companyUser1]
WHERE ([companyUser1].[CompanyId] = #__companyId_0) AND ([entity1].[AspNetUserId] = [companyUser1].[AspNetUserId]))
) AS [t0] ON [entity.WebAdminCompanyUser].[AspNetUserId] = [t0].[AspNetUserId]
ORDER BY [t0].[AspNetUserId]',N'#__companyId_0 int',#__companyId_0=1
It fails with Invalid column name 'CompanyId1'.
Following are the entities and the corresponding configurations (fluent API):
WebAdminCompanyUser:
public partial class WebAdminCompanyUser : ITrackable, IMergeable
{
public WebAdminCompanyUser()
{
AdditionalInit();
}
public int CompanyId { get; set; }
public int AspNetUserId { get; set; }
public virtual Company Company { get; set; }
[NotMapped]
public TrackingState TrackingState { get; set; }
[NotMapped]
public ICollection<string> ModifiedProperties { get; set; }
[NotMapped]
public Guid EntityIdentifier { get; set; }
partial void AdditionalInit();
}
}
Configuration:
builder.Entity<WebAdminCompanyUser>(entity =>
{
entity.ToTable(name: "WebAdminCompanyUser", schema: SqlSchema.SafeProtect);
entity.HasKey("CompanyId", "AspNetUserId");
entity
.HasOne(d => d.Company)
.WithMany()
.HasForeignKey(d => d.CompanyId)
.IsRequired();
});
WebAdminUser:
public partial class WebAdminUser : IdentityUser<int>, IAuditInfo, IRowDisableableWithDateTime, ITrackable, IMergeable
{
public WebAdminUser()
{
WebAdminCompanyUser = new HashSet<WebAdminCompanyUser>();
WebAdminUserRole = new HashSet<WebAdminUserRole>();
WebAdminUserClaim = new HashSet<WebAdminUserClaim>();
WebAdminUserLogin = new HashSet<WebAdminUserLogin>();
AdditionalInit();
}
public string DisplayName { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public bool RowEnabled { get; set; }
public DateTime? DisabledOn { get; set; }
public virtual ICollection<WebAdminCompanyUser> WebAdminCompanyUser { get; set; }
public virtual ICollection<WebAdminUserRole> WebAdminUserRole { get; set; }
public virtual ICollection<WebAdminUserClaim> WebAdminUserClaim { get; set; }
public virtual ICollection<WebAdminUserLogin> WebAdminUserLogin { get; set; }
[NotMapped]
public TrackingState TrackingState { get; set; }
[NotMapped]
public ICollection<string> ModifiedProperties { get; set; }
[NotMapped]
public Guid EntityIdentifier { get; set; }
partial void AdditionalInit();
}
Configuration:
builder.Entity<WebAdminUser>(entity =>
{
entity.ToTable(name: "WebAdminUser", schema: SqlSchema.SafeProtect);
entity.Property(e => e.Id).HasColumnName("AspNetUserId");
// authorize multiple user name
entity.HasIndex((p) => new { p.UserName }).IsUnique(false);
entity
.HasMany(user => user.WebAdminUserClaim)
.WithOne()
.HasForeignKey(userClaims => userClaims.UserId)
.IsRequired();
entity
.HasMany(user => user.WebAdminUserLogin)
.WithOne()
.HasForeignKey(userLogin => userLogin.UserId)
.IsRequired();
entity
.HasMany(user => user.WebAdminUserRole)
.WithOne()
.HasForeignKey(userRole => userRole.UserId)
.IsRequired();
entity
.HasMany(user => user.WebAdminCompanyUser)
.WithOne()
.HasForeignKey(companyUser => companyUser.AspNetUserId)
.IsRequired();
});
EF query:
IQueryable<WebAdminUser> query =
from WebAdminUser user WebAdminUserRepository.All()
.Include(user => user.WebAdminUserRole)
.ThenInclude(userRole => userRole.AspNetRole)
.Include(user => user.WebAdminCompanyUser)
where user.WebAdminCompanyUser.Any(companyUser => companyUser.CompanyId == companyId)
select user;
return query.ToList();
Any help appreciated.
This usually happens when you have improperly mapped relationship by leaving some navigation property out of fluent configuration.
Remember that each navigation property (collection or reference) represents a relationship. If you fluently configure relationships and use HasOne / HasMany / WithOne / WithMany w/o passing the navigation property, you are telling EF that the relationship has no navigation property for the corresponding end. But if you actually do have navigation property, EF will map it to a separate relationship with default FK column name. If the default property/column name is already used, EF will append index to it until it gets unique.
In your case, the WebAdminUser class and configuration you've shown are irrelevant. The invalid column name CompanyId1 indicates that the problem is with Company class which you haven't shown, and the WithMany() call here
.HasOne(d => d.Company)
.WithMany() // <--
Most likely your Company class has collection navigation property to WebAdminCompanyUser, something like this (virtual and the name of the property doesn't matter):
public virtual ICollection<WebAdminCompanyUser> CompanyUsers { get; set; }
then you need to change the above .WithMany() call with something like
.WithMany(c => c.CompanyUsers)
and the problem will be solved.

EF Can't update one-to-zero or one relationship fields, but updates the rest

Am trying to make an update on the database with one-to-one relationship models, but only the Student model fields are the only ones working. How can I make the rest update too?
This is my Repository method for updating :
public void Edit(Student student)
{
var existingStudent = _context.Students
.Include(e => e.Education)
.Include(s => s.Siblings)
.Include(p => p.Parents)
.Include(g => g.Guardian)
.Single(s => s.Id == student.Id);
if (existingStudent != null)
{
// do some updating.
_context.Attach(existingStudent);
_context.Entry(existingStudent).CurrentValues.SetValues(student);
_context.Entry(existingStudent).State = EntityState.Modified;
_context.SaveChanges();
}
}
Then, this is my Student model class :
public class Student
{
[Key]
public long Id { get; set; }
[Display(Name="First Name")]
public string FirstName { get; set; }
[Display(Name="Middle Name")]
public string MiddleName { get; set; }
[Display(Name="Last Name")]
public string LastName { get; set; }
[Display(Name="Nationality")]
public string Nationality { get; set; }
[Display(Name="Gender")]
public string Gender { get; set; }
[Display(Name="Religion")]
public string Religion { get; set; }
[Display(Name="Medical Condition")]
public string MedicalCondition { get; set; }
[Display(Name="Deceased")]
public string Deceased { get; set; }
[Display(Name="Home Address")]
public string HomeAddress { get; set; }
[Display(Name="Country Of Residence")]
public string CountryOfResidence { get; set; }
[Display(Name="City")]
public string City { get; set; }
[Display(Name="Date Of Birth")]
public DateTime DateOfBirth { get; set; }
public int Age { get; set; }
public virtual Parents Parents { get; set; }
public virtual Education Education { get; set; }
public virtual Guardian Guardian { get; set; }
public virtual Siblings Siblings { get; set; }
}
Then one of the one-to-one relationship classes is as below :
public class Siblings
{
public long Id { get; set; }
[Display(Name="Number of Brothers")]
public int NumberOfBrothers { get; set; }
[Display(Name="Number of Sisters")]
public int NumberOfSisters { get; set; }
public Student Student { get; set; }
public long? StudentId { get; set; }
}
The rest of the related model classes Parents, Education, Guardian are the same as Siblings.
How can I be able to ensure that the update cuts across all the fields. Thanks.
EDIT
This is what I have in OnModelCreating() :
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Student>()
.HasOne(e => e.Education)
.WithOne(s => s.Student)
;
builder.Entity<Student>()
.HasOne(g => g.Guardian)
.WithOne(s => s.Student);
builder.Entity<Student>()
.HasOne(p => p.Parents)
.WithOne(s => s.Student);
builder.Entity<Student>()
.HasOne(s => s.Siblings)
.WithOne(s => s.Student);
builder.Entity<Education>()
.HasOne(s => s.Student)
.WithOne(e => e.Education);
builder.Entity<Guardian>()
.HasOne(s => s.Student)
.WithOne(g => g.Guardian);
builder.Entity<Parents>()
.HasOne(s => s.Student)
.WithOne(p => p.Parents);
builder.Entity<Siblings>()
.HasOne(s => s.Student)
.WithOne(p => p.Siblings);
}
I suggest you try two ways below:
Modify the navigation property
_context.Entry(existingStudent).CurrentValues.SetValues(student);
_context.Entry(existingStudent).State = EntityState.Modified;
_context.Entry(existingStudent.Siblings).CurrentValues.SetValues(student.Siblings);
_context.Entry(existingStudent.Siblings).State = EntityState.Modified;
Update with _context.Update
var existingStudent = _context.Student
.Include(s => s.Siblings)
.AsNoTracking()
.Single(s => s.Id == 4);
if (existingStudent != null)
{
existingStudent = student;
_context.Update(existingStudent);
_context.SaveChanges();
}
I'm not sure if you have any Fluent configuration, but if you don't,
As seen in this link
You need to configure the relationship using the Fluent API.
Without knowing your complete requirement, I'm going to take a stab in the dark that this is probably what you're looking for.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasOptional(s => s.Siblings) // Mark Siblings property optional in Student entity
.WithRequired(ad => ad.Student); // mark Student property as required in Siblings entity. Cannot save Siblings without Student
}
EDIT
If HasOptional and WithRequired are giving you red squiggles, then you're most likely using EF CORE and not EF 6, unless you have a major problem elsewhere.
Addressing your current problem, in your Fluent configuration (OnModelCreating())
First, what you are looking for is not how to configure one-to-one relationships. Your intending to configure one-to-zero or one relationships.
Second, given the following configuration
builder.Entity<Student>()
.HasOne(s => s.Siblings)
.WithOne(s => s.Student);
This means: Entity<Student> requires one Sibling, and Entity<Sibling> requires one Student So your configuration is not doing what you want it to do.
Last, since EF Core has some pretty smart conventions, it will actually figure out that your nullable foreign key means it's optional, and will create your (0->0..1) relationship for you with out any config.
The Solution, simply remove all fluent config from your OnModelCreating() and let EF do it's job. And don't forget to update-database.

Entity Framework 6 code-first relations with Fluent API

I have 3 related tables in an existing database.
Many-to-Many (Many Person Many Group) and a table between - Event, witch ralate these two tables.
public class Person
{
public int PersonID { get; set; }
public string No { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
public class Event
{
public int EventID { get; set; }
public string EventData { get; set; }
public int PersonID { get; set; }
public int GroupID { get; set; }
public virtual Person Person { get; set; }
public virtual Group Group { get; set; }
}
public class Group
{
public int GroupID { get; set; }
public string GroupName { get; set; }
public virtual ICollection<Event> PersonGroup { get; set; }
}
I ddescribed relations using Fluent API.
First I declared PK's
modelBuilder.Entity<Person>()
.HasKey(k => k.PersonID });
modelBuilder.Entity<Event>()
.HasKey(k => k.EventID);
modelBuilder.Entity<Group>()
.HasKey(k => k.GroupID });
Now foreign keys:
Person has many Events
modelBuilder.Entity<Person>()
.HasKey(k => k.PersonID })
.HasMany(k => k.Events)
.WithRequired()
.HasForeignKey(f => f.PersonID);
In Event class I have Person (and all its parameters)
modelBuilder.Entity<Event>()
.HasRequired(s => s.Person)
.WithMany()
.HasForeignKey(fk => fk.PersonID);
Also I need a Group with all data:
modelBuilder.Entity<Event>()
.HasOptional(s => s.Group)
.WithMany()
.HasForeignKey(fk => fk.GroupID });
At least I need a Person group participating in an Event
modelBuilder.Entity<Group>()
.HasKey(k =>k.GroupID })
.HasMany(k => k.PersonGroup)
.WithOptional()
.HasForeignKey(fk => fk.GroupID);
It seems I have everything I need, but I need one mo collection (Group of persons with their names)
What I get through PersonGroup relation I have all events but also need to get Persons. Could You help?
Edit
I just realize it's not a typical many-to-many, since your Event-Group relation is optional. Is this intended?
Maybe your model should be changed to reflect a more natural event structure:
Group-Person: many-to-many
Event-Group: many-to-many
Event-Person: no direct relation, only via groups; alternative many-to-many
The way your model is currently designed, a single event entry can't be related to more than one group and one person and a person can't be part of a group unless they are associated in the context of an event entry.
Basically, the thing you ask for is not directly available, because you decided to explicitely create the many-to-many table with additional properties.
However, in queries you can always write a select to get the persons collection
db.Groups.Select(g => new {Group = g, PeopleInGroup = g.PersonGroup.Select(ev => ev.Person)})
Few side-notes regarding your model:
Consider removing EventID and instead use modelBuilder.Entity<Event>().HasKey(k => new { k.PersonID, k.GroupID }), like a typical many-to-many table would be designed.
Mention reverse properties in fluent api:
.
modelBuilder.Entity<Person>()
.HasKey(k => k.PersonID })
.HasMany(k => k.Events)
.WithRequired(e => e.Person)
.HasForeignKey(f => f.PersonID);
// redundant with the previous configuration
modelBuilder.Entity<Event>()
.HasRequired(s => s.Person)
.WithMany(p => p.Events)
.HasForeignKey(fk => fk.PersonID);
// same to be done for groups
In order to have a convenient access to the associated people of a group, you could create a not-mapped property getter which wraps the necessary query:
public class Group
{
public int GroupID { get; set; }
public string GroupName { get; set; }
public virtual ICollection<Event> PersonGroup { get; set; }
[NotMapped]
public IEnumerable<Person> PersonsInGroupEvents
{
return PersonGroup.Select(ev => ev.Person);
}
}
// or fluent api way of NotMapped:
modelBuilder.Entity<Group>()
.Ignore(x => x.PersonsInGroupEvents);

Trouble when trying to create a database with Entity Framework and migrations

I want to create database EF migrations via the developer command prompt for VS2015. When I try to use this command line:
dotnet ef migrations add v1
I get this error:
The property 'partCategoriess' cannot be added to the entity type
'PartCategoryPart' because a navigation property with the same name
already exists on entity type 'PartCategoryPart'.
Is anything wrong with the DbContext? I am trying to create a many-to-many table between categoryParts and parts.
public class ShoppingDbContext : IdentityDbContext<User>
{
public ShoppingDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
public DbSet<PartCategory> PartCategories { get; set; }
public DbSet<Part> Parts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PartCategoryPart>()
.HasKey(t => new { t.partCategoriess, t.Part });
modelBuilder.Entity<PartCategoryPart>()
.HasOne(pt => pt.partCategoriess)
.WithMany(p => p.PartCategoryPart)
.HasForeignKey(pt => pt.PartCategoryId);
modelBuilder.Entity<PartCategoryPart>()
.HasOne(pt => pt.Part)
.WithMany(t => t.PartCategoryPart)
.HasForeignKey(pt => pt.PartId);
}
}
public class PartCategoryPart
{
public int Id { get; set; }
public int PartCategoryId { get; set; }
public PartCategory partCategoriess { get; set; }
public int PartId { get; set; }
public Part Part { get; set; }
}
public class PartCategory
{
public int PartCategoryId { get; set; }
public string Category { get; set; }
public List<ProductPartCategory> ProductPartCategories { get; set; }
public List<PartCategoryPart> PartCategoryPart { get; set; }
}
public class Part
{
public int PartId { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public double? Price { get; set; }
public List<PartCategoryPart> PartCategoryPart { get; set; }
}
The problem here is how you are defining the primary key for the PartCategoryPart intermediate entity. You are using the navigation properties to define the PK and you have to use the FKs like this:
modelBuilder.Entity().HasKey(t => new { t.PartCategoryId, t.PartId});
Referring to my own assignment, here's how you properly create an entity. You did not define a key..
modelBuilder.Entity<Price>()
.HasKey(input => input.PriceId)
.HasName("PrimaryKey_Price_PriceId");
// Provide the properties of the PriceId column
modelBuilder.Entity<Price>()
.Property(input => input.PriceId)
.HasColumnName("PriceId")
.HasColumnType("int")
.UseSqlServerIdentityColumn()
.ValueGeneratedOnAdd()
.IsRequired();
//modelBuilder.Entity<Price>()
// .Property(input => input.MetricId)
// .HasColumnName("MetricId")
// .HasColumnType("int")
// .IsRequired();
modelBuilder.Entity<Price>()
.Property(input => input.Value)
.HasColumnName("Value")
.HasColumnType("DECIMAL(19,4)")
.IsRequired();
modelBuilder.Entity<Price>()
.Property(input => input.RRP)
.HasColumnName("RRP")
.HasColumnType("DECIMAL(19,4)")
.IsRequired(false);
modelBuilder.Entity<Price>()
.Property(input => input.CreatedAt)
.HasDefaultValueSql("GetDate()");
modelBuilder.Entity<Price>()
.Property(input => input.DeletedAt)
.IsRequired(false);
// Two sets of Many to One relationship between User and ApplicationUser entity (Start)
modelBuilder.Entity<Price>()
.HasOne(userClass => userClass.CreatedBy)
.WithMany()
.HasForeignKey(userClass => userClass.CreatedById)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
modelBuilder.Entity<Price>()
.HasOne(userClass => userClass.DeletedBy)
.WithMany()
.HasForeignKey(userClass => userClass.DeletedById)
.OnDelete(DeleteBehavior.Restrict);
Notice that after identifying which is the key you still need to declare its properties before you declare any relationships.
modelBuilder.Entity<Price>()
.HasKey(input => input.PriceId)
.HasName("PrimaryKey_Price_PriceId");
// Provide the properties of the PriceId column
modelBuilder.Entity<Price>()
.Property(input => input.PriceId)
.HasColumnName("PriceId")
.HasColumnType("int")
.UseSqlServerIdentityColumn()
.ValueGeneratedOnAdd()
.IsRequired();

many to many mapping with intermediate object in entity framework code first

I have a many to many relation, and I want to add an intermediate class, which would enable me adding the many to many relations using repository pattern.
What I can't figure out is the mapping.
Here's the structure
public class Product
{
public Product()
{
Categories = new HashSet<Category>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Category> Categories { get; set; }
}
public class Category
{
public int Id { get; set; }
public String Name { get; set; }
public ICollection<Product> Products { get; set; }
}
public class PCMap
{
public int product_id { get; set; }
public int category_id { get; set; }
public Product Product { get; set; }
public Category Category { get; set; }
}
And the Mapping
modelBuilder.Entity<Product>()
.HasEntitySetName("PCMap")
.HasMany(p=>p.Categories)
.WithMany(p=>p.Products)
.Map(m=>
{
m.MapLeftKey("product_id");
m.MapRightKey("category_id");
m.ToTable("PCMap");
});
modelBuilder.Entity<PCMap>()
.ToTable("PCMap");
modelBuilder.Entity<PCMap>().HasKey(k => new
{
k.category_id,
k.product_id
});
modelBuilder.Entity<PCMap>()
.HasRequired(p=>p.Product)
.WithMany()
.HasForeignKey(p => p.product_id);
modelBuilder.Entity<PCMap>()
.HasRequired(p => p.Category)
.WithMany()
.HasForeignKey(p => p.category_id);
Here's the error that I get..
How do I fix this ?
The way you've set it up. PCMap is a non entity and is just used to facilitate the M:N join under the hood.
Product p = new Product();
p.Categories ...
Category c = new Category();
c.Products ...
So beacuse you've already defined PC as part of the Product Entity definition here.
.Map(m=>
{
m.MapLeftKey("product_id");
m.MapRightKey("category_id");
m.ToTable("PCMap");
});
I don't believe you need to (or it's possible to) define it again, separately below. Try deleting all this code.
modelBuilder.Entity<PCMap>()
.ToTable("PCMap");
modelBuilder.Entity<PCMap>().HasKey(k => new
{
k.category_id,
k.product_id
});
modelBuilder.Entity<PCMap>()
.HasRequired(p=>p.Product)
.WithMany()
.HasForeignKey(p => p.product_id);
modelBuilder.Entity<PCMap>()
.HasRequired(p => p.Category)
.WithMany()
.HasForeignKey(p => p.category_id);

Categories