Deleting a listing in table throws a SQL error - entity-framework

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.

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

EF Core 5 many to many relationship to the same table?

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));

Foreign key constraint may cause cycles or multiple cascade paths but only in one project

I've gone through the other questions on SO and none of them have helped in my instance, so please don't mark it as duplicate. I downloaded and ran the method from the documentation to get a many-to-many linked table. The sample project has
public class Book {
[Key]
public int BookId { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public ICollection<BookCategory> BookCategories { get; set; }
}
public class Category {
[Key]
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public ICollection<BookCategory> BookCategories { get; set; }
}
public class BookCategory {
public int BookId { get; set; }
public Book Book { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
with
modelBuilder.Entity<BookCategory>()
.HasKey(bc => new { bc.BookId, bc.CategoryId });
modelBuilder.Entity<BookCategory>()
.HasOne(bc => bc.Book)
.WithMany(b => b.BookCategories)
.HasForeignKey(bc => bc.BookId);
modelBuilder.Entity<BookCategory>()
.HasOne(bc => bc.Category)
.WithMany(c => c.BookCategories)
.HasForeignKey(bc => bc.CategoryId);
in OnModelCreating. This works fine and creates the correct migration file which works fine and looks like this
table.ForeignKey(
table.ForeignKey(
name: "FK_BookCategory_Book_BookID",
column: x => x.BookID,
principalTable: "Book",
principalColumn: "BookID",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_BookCategory_Category_CategoryID",
column: x => x.CategoryID,
principalTable: "Category",
principalColumn: "CategoryID",
onDelete: ReferentialAction.Restrict);
Copying the code into my project and adding a migration creates the same migration file. But, when I try to update the database I get the error
Introducing FOREIGN KEY constraint 'FK_BookCategory_Category_CategoryID' on table 'BookCategory' 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.
Swapping the two table.ForeignKey( statements around means that the error refers to FK_BookCategory_Book_BookID and only having one of them the database is updated successfully.
Any ideas why the migration works in one project but not another?
In protected override void OnModelCreating(ModelBuilder modelBuilder) { I removed
.HasForeignKey(bc => bc.BookId);
and replaced it with
.OnDelete(DeleteBehavior.Restrict);
so it looked like this.
modelBuilder.Entity<BookCategory>()
.HasOne(bc => bc.Book)
.WithMany(b => b.BookCategories)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<BookCategory>()
.HasOne(bc => bc.Category)
.WithMany(c => c.BookCategories)
.OnDelete(DeleteBehavior.Restrict);
This removes the cascade part so everything works. I've tested deleting records from each of the tables in SQL Management Studio and in code and none of the code cascades any deletes which means I'm happy.

How to create a many-to-many with ASPNet EF Core

I want to create a contact list in my application. I therefore created a mapping table between ApplicationUsers:
public class UserContacts
{
public string UserId { get; set; }
public virtual ApplicationUser User { get; set; }
public string ContactId { get; set; }
public virtual ApplicationUser Contact { get; set; }
}
And then registered the relations between my ApplicationUser and this table:
builder.Entity<UserContacts>()
.HasOne(p => p.Contact)
.WithMany(p => p.Contacts)
.HasForeignKey(p => p.ContactId);
builder.Entity<UserContacts>()
.HasOne(p => p.User)
.WithMany(p => p.Contacts)
.HasForeignKey(p => p.UserId);
And added a property in my ApplicationUser class:
public virtual ICollection<UserContacts> Contacts { get; set; }
But whenever I try to update the database I get the following error:
Cannot create a relationship between 'ApplicationUser.Contacts' and 'UserContacts.User', because there already is a relationship between 'ApplicationUser.Contacts' and 'UserContacts.Contact'. Navigation properties can only participate in a single relationship.
Can you please explain to me what I am missing?
Thanks.
You need to add another collection to ApplicationUser:
public virtual ICollection<UserContacts> **Users** { get; set; }
Then change the fluent code to point there (they can't point to same collection):
builder.Entity<UserContacts>()
.HasOne(p => p.User)
.WithMany(p => **p.Users**)
.HasForeignKey(p => p.UserId);

Defining relationships with existing database in code first

I have an existing database with two tables: MemData and MemPhone.
MemData has the following structure
uidMemberId (PK, uniqueidentifier not null)
strFirstName (varchar(50), null)
strLastName (varchar(50), null)
MemPhone has the following structure
uidMemberId (uniqueidentifier not null)
strPhoneNumber (varchar(50), null)
There's no PK on the MemberPhoneNumber table. The two tables are joined by uidMemberId but no foreign keys have been set up in the database.
My MemData Model looks like this:
[Table("MemData")]
public class Member
{
[Key]
public Guid MemberId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual IList<MemberPhoneNumber> MemberPhoneNumbers { get; set; }
}
With the configuration map looking like this:
public MemberMap()
{
Property(t => t.MemberId).HasColumnName("uidMemberID");
Property(t => t.FirstName).HasColumnName("strFirstName");
Property(t => t.LastName).HasColumnName("strLastName");
HasMany(x => x.MemberPhoneNumbers);
}
My MemPhone Model looks like this:
[Table("MemPhone")]
public class MemberPhoneNumber
{
[Key]
public Guid MemberId { get; set; }
public string PhoneNumber { get; set; }
public virtual Member Member { get; set; }
}
With the configuration map looking like this:
public MemberPhoneNumberMap()
{
Property(t => t.MemberId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(t => t.MemberId).HasColumnName("uidMemberID");
Property(t => t.PhoneNumber).HasColumnName("strPhone");
}
In my MVC app, when I call
Uow.Members.GetAll().Include(p => p.MemberPhoneNumbers).ToList()
I get the following error
Invalid column name 'Member_MemberId'.
It should be looking for MemData_uidMemberId instead but I can't figure out what I've missed in the configuration.
Thanks
By default foreign key name will be propertyName _ entityId. That's why EF tries to map to Member_MemberId column. Provide foreign key configuration for one-to-many relationship
public class MemberMap : EntityTypeConfiguration<Member>
{
public MemberMap()
{
ToTable("MemData");
HasKey(m => m.MemberId);
Property(m => m.MemberId).HasColumnName("uidMemberID");
Property(m => m.FirstName).HasColumnName("strFirstName");
Property(m => m.LastName).HasColumnName("strLastName");
HasMany(x => x.MemberPhoneNumbers)
.WithRequired(p => p.Member)
.HasForeignKey(p => p.MemberId);
}
}
Also keep in mind, that you can configure key and table with fluent configuration, you don't need to pollute your entity with data annotations attributes.