We have a database that is creating some multiple cascade paths errors.
I do understand what that means and what is happening, but I'd like to know the best approach since we have to remove some of the OnDelete Cascade options we have.
This is our table structure:
SCHOOL
- SchoolId
- Name
- ....
STUDENT
- StudentId
- SchoolId
- Name
- ...
COURSE
- CourseId
- SchoolId
- Name
STUDENT-COURSE
- StudentId
- CourseId
- ....
SchoolId FK in Student deletes all the students related in cascade when deleting a school
SchoolId FK in Course deletes all the courses related in cascade when deleting a school
StudentId FK in Student-Course deletes all the student-courses related in cascade when deleting a student
CourseId FK in Student-Course deletes all the student-courses related in cascade when deleting a course
So here we have the cycle, because when deleting a school the entity Student-Course is going to be deleted from 2 different sides, creating the cycle.
I understand what happens here:
HasMany(p => p.Students)
.WithRequired(p => p.School)
.HasForeignKey(p => p.SchoolId)
.WillCascadeOnDelete(true);
And I know I can do:
HasMany(p => p.Students)
.WithRequired(p => p.School)
.HasForeignKey(p => p.SchoolId)
.WillCascadeOnDelete(false);
The question here is what would be the best approach.
Thanks.
I would remove on delete cascade from the School, and leave the others as is, and when deleting a school add some logic to delete related students, courses (but his keeps a consistency for deletes in lower levels).
If you remove on delete cascade from the Students, you will need to manually add deletion logic for them to clean related student courses first, and will be somehow inconsistent with courses delete (hierarchical level is not consistent in terms of deletion logic).
Related
I have a case where I need to implement the softDelete feature of TypeORM. Somewhere in my entity(let's call it Lead), I have a column that maps to another entity(let's call it Customer) with OneToOne relation.
............
#OneToOne(type => Customer, {})
#JoinColumn()
customer: Customer;
..........
The problem here is since soft remove doesn't remove the record completely from the database, whenever I remove any record from the lead table I can't add another lead for the same customer because of the OneToOne relation.
When surfing through the internet, I got a few solutions for similar scenarios of unique constraints like using:
Partial indexes &
Virtual columns
But here I'm searching for some kind of TypeORM level solutions while mapping relations. What could be the best play around for this case?
One to One relationship in TypeORM creates a unique foreign key constraint by default. Though the row is soft-deleted from the Lead table, there will still be a unique value in the table. So, while inserting another lead for the same customer, TypeORM will throw us a unique constraint error.
The solution for this issue is to remove a foreign key constraint from the relationship. This will now allow us to insert data for the same customerId in the Lead table.
Now what we have to make sure of is:
before inserting value in the Lead table, check if there already exists another lead for that customerId that is not soft deleted.
We also have to ensure that before deleting any customer from the Customer table, their particular leads are soft-deleted from the Lead table.
P.S: In a way this solution is a hacky solution. But since soft delete doesn't consider the foreign key constraint references, this is the suitable way that I have found so far.
I have a legacy system that I am hoping to use EF to clean up the mess of ADO code it uses for data access.
The system uses the following class hierachy: Sponsors which are Members which are People. In the database, each class has it's own table, each with its own key.
Person
PersonId (PK)
Name
Member
MemberId (PK)
PersonId (FK)
Sponsor
SponsorId (PK)
MemberId (FK)
Mappings
Person
ToTable("Person");
HasKey(p => p.Id).Property(p => p.Id).HasColumnName("PersonId").IsRequired();
Member
ToTable("Member");
HasKey(m => m.MemberId).Property(m => m.MemberId).HasColumnName("MemberId").IsRequired();
Sponsor
ToTable("ChapterSponsor");
HasKey(cs => cs.MemberId).Property(cs => cs.MemberId).HasColumnName("MemberId").IsRequired();
I can map the Person and Member classes fine. But when I try to map the Sponsor class, I get an error because it is trying to find a field named PersonId on the Sponsor table.
Is it possible to tell EF that the Sponsor query needs to join using the MemberId Foreign Key? Or is this poor db structure not going to work with EF?
I'm having a hard time finding the exact answer to this question, so my apologies if this is redundant.
So I have 3 tables defined such that:
Person :PersonId, FirstName, LastName
Company: CompanyId, CompanyName
Order: OrderId, PersonId, CompanyId
On the Order table, there is a foreign key defined on the PersonId and CompanyId columns, thus, my Order entity class generated by EF has a navigation properties of type Person (not PersonId) and Company.
So, to insert into the Order table, I first need to query the person and company tables to get the person and company entities. Then I can construct the Order object using the Person and Company entities and save it to the db.
In my scenario, I am being passed a PersonId and CompanyId.
In classic SQL I would just do INSERT INTO Order Set (CompanyId, PersonId) - 1 database call. But with EF, I have to do 3 db calls. This seems like overkill.
Is there any way around this?
PS - I'm using EF 6. I know I could generate an expression and make it single call..but that would still yield two subselects.
You can just include foreign key properties in addition to the navigation properties and then set them using the ids you have. If you do this will not have to go to the database to get related entities for just a sake of setting the relationship.
I have to create an entity framework model for a badly designed database. The database uses table per type inheritance but it does so with using a PK->FK relationship, not a PK->PK relationship. E.g.
Person
PersonID (PK)
Name
Employee
EmployeeID (PK)
PersonID (FK)
DateStarted
HourlyEmployee
HourlyEmployeeID (PK)
EmployeeID (FK)
HourlyRate
Obviously this is just badly designed, but I can't change it. Table per type inheritance in the entity framework essentially wants EmployeeID not to exist and the PK for Employee to be PersonID. Is it possible to create a model for this database, or do I choose another tool? any recommendations?
You will not map this as TPT inheritance because your database is configured in the way that doesn't allow you cheating EF.
If Employee.EmployeeID is auto-generated in the database and Employee.PersonID is unique (uniqueness must be enforced in the database) you should be able (not tested) to cheat EF by simply mapping:
public Employee : Person {
public DateTime DateStarted { get; set; }
}
This class will tell EF that Employee inherits key from Person (PersonID) and you will hide the real key from EF - this should work if the real key is auto-generated.
The problem is your next level of inheritance which breaks this pattern. To make this work your HourlyEmployee will have to reference PersonID - not EmployeeID. EF now doesn't know about EmployeeID existence so it even cannot map relation with HourlyEmployee.
TPT inheritance in code first has one additional limitation - PK column must have the same name in all tables.
You can create a model from the database if it exists but it might not be what you expect. EF sometimes doesn't work that great with weird database structures.
EF 4.1 Database First approach.
Say I have this table schema
Users 1---M UserRoles M---1 Roles
Cascade delete is setup in the Foreign Keys
The UserRoles table has additional columns like CreatedDate so I create a model for UserRoles and map accordingly.
I end up with the following Models:
User
----
int Id
string Name
List<UserRoles> UserRoles
UserRoles
---------
int UserId
int RoleId
DateTime CreatedDate
User User
Role Role
Role
----
int Id
string Name
List<UserRoles> UserRoles
If I have my configuration correct, should I be able to delete a user and will the user roles rows be deleted WITHOUT having to clear the UserRoles collection manually?
So can I just do this:
DbContext.Entry(user).State = EntityState.Deleted;
DbContext.SaveChanges();
Or do I HAVE to do this:
user.UserRoles.Clear();
DbContext.Entry(user).State = EntityState.Deleted;
DbContext.SaveChanges();
My testing shows I HAVE to clear the child collection, but I find conflicting information that if I have cascade delete setup correctly it should work by only deleting the User.
When I DON'T clear the UserRoles I receive this error:
The relationship could not be changed because one or more of the
foreign-key properties is non-nullable
Thanks for you help in clarifying this!
You must use
DbContext.Users.Remove(user);
It is not the same thing as setting the state to Deleted. Setting the state won't mark any child objects with cascading delete setup as Deleted but Remove will do.
Setting the state to Deleted should work IF no children are loaded into the context because EF will send only a DELETE statement for the parent to the database and the database will delete the children as well due to the cascading delete in the database.
IF however you have loaded children into the context setting the state on the parent to Deleted won't set the state of the children. EF will throw the exception, it's not the database who complains.
You should be able to specify that deleting a Role or User will in turn delete the child grants. You can use the WillCascadeOnDelete() method on the fluent DbModelBuilder API:
modelBuilder.Entity<UserRoles>
.HasRequired(d => d.User)
.WithMany(p => p.UserRoles)
.HasForeignKey(d => d.UserId)
.WillCascadeOnDelete();
modelBuilder.Entity<Role>
.HasMany(p => p.UserRoles)
.WithRequired(d => d.Role)
.HasForeignKey(d => d.RoleId)
.WillCascadeOnDelete();
With this setup, deleting a User or a Role should also delete all of the UserRoles.