EF Core 5 many to many relationship to the same table? - entity-framework-core

A product can have many other related products. The following creates the join table perfectly but when I add a product to another product's RelatedProducts I get the following error on save: 'The value of 'ProductAssociation.ProductID' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.'
modelBuilder.Entity<Product>()
.HasMany(x => x.RelatedProducts)
.WithMany(r => r.RelatedProducts)
.UsingEntity<ProductAssociation>(
x => x.HasOne(p => p.RelatedProduct).WithMany().HasForeignKey(f => f.RelatedProductID),
x => x.HasOne(p => p.Product).WithMany().HasForeignKey(f => f.ProductID).OnDelete(DeleteBehavior.NoAction),
x => x.ToTable("ProductAssociations"));

I think this is a similar answer
Entity Framework Core 2.0 many-to-many relationships same table
I also solved it with that provide link above like this in core 3+.
Product table
public class Product
{
// Other properties members ....
public ICollection<Product> Products { get; set; }
public ICollection<Product> ProductOf { get; set; }
}
Product Association table "ProductSet"
public class ProductSet
{
public int ProductId { get; set; }
public int SubProductId { get; set; }
}
OnModelCreating()
modelBuilder.Entity<Product>()
.HasMany(e => e.Products)
.WithMany(e => e.ProductOf)
.UsingEntity<ProductSet>(
e => e.HasOne<Product>().WithMany().HasForeignKey(e => e.ProductId),
e => e.HasOne<Product>().WithMany().HasForeignKey(e => e.SubProductId));

Related

How to create relationship with temporal table in EF Core

I want to create a 1-0..1 relation between a table and a temporal table using EF Core code-first approach. But when I add the migration I get the error :
Navigation 'Ticket (Dictionary<string, object>).TicketAdministration' was not found. Please add the navigation to the entity type before configuring it.
I let you see step by step what I am doing:
I create the Ticket and TicketAdministration classes:
public partial class Ticket
{
public long Id { get; set; }
//... other unuseful props
public virtual TicketAdministration TicketAdministration { get; set; }
}
public class TicketAdministration
{
public long Id { get; set; }
//... other unuseful props
public long? TicketId { get; set; }
public virtual Ticket Ticket { get; set; }
}
Then I configured the two classes/tables:
modelBuilder.Entity<Ticket>(entity =>
{
entity.ToTable("tickets");
entity.Property(e => e.Id)
.HasColumnType("bigint")
.ValueGeneratedOnAdd();
});
modelBuilder.Entity<TicketAdministration>(entity =>
{
entity.ToTable("ticket_admininistration", "admin", t => t.IsTemporal());
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).UseIdentityColumn(1, 1);
entity.HasOne(d => d.Ticket)
.WithOne(p => p.TicketAdministration)
.HasForeignKey<TicketAdministration>(b => b.TicketId)
.OnDelete(DeleteBehavior.ClientCascade)
.HasConstraintName("FK_dbo.Tickets_admin.TicketAdministration_TicketId");
});
How do I have to configure the relationship? Why is it expecting a dictionary? What the dictionary represent?
Thank you

Deleting a listing in table throws a SQL error

I have a .NetCore Entity Framework project for a model airport.
When I try to delete an airport in the listings, I am getting the error below when trying to delete an airline and I'm trying to figure out why:
Microsoft.Data.SqlClient.SqlException (0x80131904): The DELETE statement conflicted with the REFERENCE constraint "FK_airlinePilots_airline". The conflict occurred in database "FrankAirportTeam", table "dbo.airlinePilots", column 'airlineId'.
The delete action is just an API method:
[HttpDelete("{id}")]
public async Task<ActionResult<Airline>> DeleteAirline(long id)
{
var airline = await _context.Airline.FindAsync(id);
if (airline == null)
{
return NotFound();
}
_context.Airline.Remove(airline);
await _context.SaveChangesAsync();
return airline;
}
My model for Airline is here below:
public partial class Airline
{
public Airline()
{
AirlinePilots = new HashSet<AirlinePilots>();
}
public long Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public long CityId { get; set; }
public virtual City City { get; set; }
public virtual ICollection<AirlinePilots> AirlinePilots { get; set; }
}
And AirlinePilots
public partial class AirlinePilots
{
public long PilotId { get; set; }
public long AirlineId { get; set; }
public virtual Airline Airline { get; set; }
public virtual Pilot Pilot { get; set; }
}
My DB context class looks like this for Airline:
modelBuilder.Entity<Airline>(entity =>
{
entity.ToTable("airline");
entity.HasIndex(e => e.Id)
.HasName("IX_airline");
entity.Property(e => e.Id)
.HasColumnName("id");
entity.Property(e => e.Description)
.IsRequired()
.HasColumnName("description");
entity.Property(e => e.CityId).HasColumnName("cityId");
entity.Property(e => e.Title)
.IsRequired()
.HasColumnName("title")
.HasMaxLength(255);
entity.HasOne(d => d.City)
.WithMany(p => p.Airline)
.HasForeignKey(d => d.CityId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_airline_city");
});
And on AirlinePilots [UPDATED WITH NEW DELETE METHOD]:
modelBuilder.Entity<AirlinePilots>(entity =>
{
entity.HasKey(e => new { e.PilotId, e.AirlineId });
entity.ToTable("airlinePilots");
entity.Property(e => e.PilotId).HasColumnName("pilotId");
entity.Property(e => e.AirlineId).HasColumnName("airlineId");
entity.HasOne(d => d.Airline)
.WithMany(p => p.AirlinePilots)
.HasForeignKey(d => d.AirlineId)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_airlinePilots_airline");
entity.HasOne(d => d.Pilot)
.WithMany(p => p.AirlinePilots)
.HasForeignKey(d => d.PilotId)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_airlinePilots_pilot");
});
AirlinePilots is just a lookup table that stores a AirlineId for each PilotId. One Airline can have many Pilots. It has a foreign key for AirlineId and one for PilotId.
In my database, the Airline doesn't have a foreign key for AirlinePilots. However, the AirlinePilots table does have a foreign key for Airline.
So, the DELETE action in the API would need to delete the Airline and any associated rows that contain the Airline Id being deleted in the AirlinePilots table.
I'm actually really scared to change anything because I don't want it to accidently delete everything in the AirlinePilots table.
Is there anything I can add to my context class to make this work?
thanks! :)
AirlineId is not a nullable foreign key on AirlinePilots, so you can't use ClientSetNull because it throws on SaveChanges, what you need is a cascading deletion behavior which is achieved using Cascade option for Airline fk mapping.:
.OnDelete(DeleteBehavior.Cascade)
Btw, from your mapping I get you are also controlling this collection from Pilot entity, usually this is a design flaw that can cause you troubles down the road, you might end having the same item loaded twice in memory pointing to the same database row, where concurrencies issues might pop out. If you need to show the airlines from Pilot then use a query to return read only entities instead.

Entity Framework - One-way one-to-one relation

I need to make a one-to-one relation between two tables, but only one-way.
[Accounts]
account_id
name
etc...
[SalesReps]
sales_rep_id
account_id
account
etc...
So, I want the SalesReps table to have a relation to Accounts, but I don't want Accounts to have a reference to SalesReps.
I tried this:
modelBuilder.Entity<sales_rep>()
.HasRequired(a => a.account)
.WithMany()
.HasForeignKey(a => a.account_id);
But that gives me:
sales_rep_account_Source: : Multiplicity is not valid in Role 'sales_rep_account_Source' in relationship 'sales_rep_account'. Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be '1'.
The way you do this is with WithRequiredDependent() configuration. Your entities will be:
public class Account
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SalesRep
{
public int Id { get; set; }
public Account Account { get; set; }
}
In OnModelCreating() method you should do this:
modelBuilder.Entity<Account>().HasKey(x => x.Id);
modelBuilder.Entity<SalesRep>().HasKey(x => x.Id);
modelBuilder.Entity<SalesRep>().HasRequired(x => x.Account).WithRequiredDependent().Map(x => x.MapKey("AccountId"));
It turns out, that all I had to do was:
modelBuilder.Entity<sales_rep>()
.HasRequired(a => a.account)
.WithOptional();

Why is this column getting generated in EF Code-First migrations?

I have a Mentorship entity, which has Student and Mentor as FKs:
[Required]
public int MentorId { get; set; }
public virtual User Mentor { get; set; }
[Required]
public int StudentId { get; set; }
public virtual User Student { get; set; }
User model:
public virtual ICollection<Mentorship> Mentorships { get; set; }
Fluent API:
modelBuilder.Entity<Mentorship>()
.HasRequired(c => c.Mentor)
.WithMany()
.HasForeignKey(u => u.MentorId);
modelBuilder.Entity<Mentorship>()
.HasRequired(c => c.Student)
.WithMany()
.HasForeignKey(u => u.StudentId);
In my database, I see StudentId and MentorId columns which have been populated correctly, but I also see a User_UserId column that is not being used by anything. What have I done wrong?
You have used the WithMany() overload that configures the relationship to be required:many without a navigation property on the other side of the relationship - but you do have a navigation property on the other side of the relationship.
Try this:
modelBuilder.Entity<Mentorship>()
.HasRequired(c => c.Mentor)
.WithMany(d => d.Mentorships)
.HasForeignKey(u => u.MentorId);
modelBuilder.Entity<Mentorship>()
.HasRequired(c => c.Student)
.WithMany(d => d.Mentorships)
.HasForeignKey(u => u.StudentId);//oops! just realised that we just
//specified that Mentorships is using MentorId
//as the FK
References:
Required WithMany Method
Why do I get an extra foreign key?
Edit
Scratch that. Just realised you are trying to create two relationships with only one navigation property on the many side. You can't have a navigation property with 2 foreign keys. You need to introduce inheritance on the User side or remove the Mentorships navigation property from the User class or introduce separate StudentMentorships and MentorMentorships navigation properties
Edit 2
Finding a user once you've defined separate navigation properties
int userId = 123;//the one we want to find
var user = Users.Where(x => x.StudentMentorships.Any(s => s.StudentID == userId)
|| x.MentorMentorships.Any(s => s.MentorID == userId);

Configure a one directional relationship in entity framework

I have a Model
HUBS -< SECTIONS
sections are a tree hierarchy but they all belong to a hub (there is another table managing the hierarchy as one section can appear twice in the tree)
A hub should also have a root section, so on my hub entity I have:
public partial class Hub
{
public Hub()
{
this.Sections = new List<Section>();
}
public int HubId { get; set; }
public string Name { get; set; }
public virtual ICollection<Section> Sections { get; set; }
public int RootSectionId { get; set; }
public virtual Section RootSection { get; set; }
}
If I don't set an mapping as per:
public class HubMap : EntityTypeConfiguration<Hub>
{
public HubMap()
{
// Primary Key
this.HasKey(t => t.HubId);
// Table & Column Mappings
this.ToTable("Hubs");
this.Property(t => t.HubId).HasColumnName("HubId");
this.Property(t => t.Name).HasColumnName("Name");
// Relationships
this.HasRequired(t => t.Site)
.WithMany(t => t.Hubs)
.HasForeignKey(d => d.SiteId);
}
}
I get an error about not finding RootSection_SectionId column. Now I could just rename the column to match, but in the interests of my understanding of EF mapping, I'd like to be able to specify they column, which is "RootSectionId"
What would I need to include in the mapping file to map this field?
Something like :
this.HasRequired(t => t.RootSection)
.WithMany()
.HasForeignKey(d => d.RootSectionId);

Categories