Entity Framework on delete cascade - c#

I have problem with deleting related rows in Entity Framework 4.1. I have tables with relations
Book 1<--->* BookFormats
I have set the on delete cascade:
ALTER TABLE [dbo].[BookFormats] WITH CHECK ADD CONSTRAINT [FK_BookFormats_Book]
FOREIGN KEY([BookID]) REFERENCES [dbo].[Book] ([BookID]) on delete cascade
The EDMX property
Then, I want to remove the all BokFormats items related to my Book object:
var originalBook = m.db.Book.First(x => x.BookID == bookId);
originalBook.BookFormats.Clear();
m.db.SaveChanges();
But, I get the error:
The operation failed: The relationship could not be changed because
one or more of the foreign-key properties is non-nullable. When a
change is made to a relationship, the related foreign-key property is
set to a null value. If the foreign-key does not support null values,
a new relationship must be defined, the foreign-key property must be
assigned another non-null value, or the unrelated object must be
deleted.
I ran out of ideas on how to delete these objects. Any ideas?

You can use RemoveRange :
m.db.BookFormats.RemoveRange(originalBook.BookFormats);
m.db.SaveChanges();
But this is for EF 6.0

Cascade deletions concept is as follows:
When you delete Book from the DB all related BookFormats will be deleted for you by SQL Server (please note that it doesn't matter how deletion of Book will be initiated via EF or raw SQL). Thus it has nothing to do with your task: "I want to delete all BookFormats related to my Book". To accomplish it you need something like this:
foreach(var m in m.db.BookFormats.Where(f=>f.BookID == bookID))
{
m.db.BookFormats.Remove(m);
}
m.db.SaveChanges();

You are not deleting the BookFormats from the database, but you are removing the relationship, thus orpahning your BookFormats and setting the BookID column to NULL. The delete cascade you have put on the database says When I delete theBook, then delete all of theBookFormatsthat have aBookIDequal to mine. You are not deleting the book you are deleting the formats from the Book.
Instead of originalBook.BookFormats.Clear() you should have something like this...
List<int> idsToDelete = new List<int>();
foreach (BookFormat bf in originalBook.BookFormats)
{
idsToDelete.Add(bf.ID);
}
foreach (int id in idsToDelete)
{
BookFormat format = m.db.BookFormat.FirstOrDefault(x => x.ID == id);
if (format != null)
{
m.db.DeleteBookFormat(format);
}
}
m.db.SaveChanges();
It should be something along those lines. I don't have it right in front of me to remember how EF constructs the delete method in the EDMX.

I've tested it in EF 6.1.3 and this should work fine:
var originalBook = m.db.Book.First(x => x.BookID == bookId);
originalBook.BookFormats.Clear();
db.Books.Remove(originalBook);
m.db.SaveChanges();

I use EF6 and this works.
var itemBinding = db.ItemBinding.Where(x => x.BindingToId == id) ;
foreach (var ib in itemBinding)
{
db.Item.Remove(ib.Item);
db.ItemBinding.Remove(ib);
}
db.SaveChanges();

Related

How to deal with multiple newly Added Entities that depend on each otter with a Foreign key constraint - EF6

I had a bit of difficulties with getting the right title for this problem so I hope my explanation below will make it a bit clearer.
I am using EntityFramework 6 and I am doing multiple inserts within a function.
There are 3 different tables which get updated / inserted: table EntityMethod, EntityRoom and EntityRoomMethod. The table EntityRoomMethod has a foreign key relationship with the table EntityMethod.
In some cases, a EntityMethod row is missing and this is newly created by adding the object with entity framework:
if (mn == null)
{
mn = new Method
{
ElementId = floorProgram.ElementId,
ActionId = m.ActionId,
ElementCount = m.ElementCount,
ColorId = m.ColorId,
IsBase = m.IsBase,
IsHccp = m.IsHccp,
TimeNorm = m.TimeNorm,
Frequency5Id = m.Frequency5Id,
MaterialId = m.MaterialId,
ProductId = m.ProductId,
MethodTypeId = m.MethodTypeId,
};
}
In another part of code the Method foreign key (MethodId) of the EntityRoomMethod table is being set:
roomMethodObject.RightId = mn.Id;
RightId is in this case the relationship with the EntityMethod table.
On a later point the other 2 table objects (EntityRoom and EntityRoomMethod) are also added (DBSet.Add) using EF.
The problem however is, that when the EntityMethod is newly added, it gets the Id value of 0, because SaveChanges() is not yet executed. The foreign key reference in the EntityRoomMethod is therefor also being set to 0.
When the function returns to the caller, the SaveChanges() is being executed and all 3 objects (representing the 3 tables) are being saved.
This however will generate a FK error (because Id 0 does not exist obviously).
I tried to fix this by calling SaveChanges() after Adding the new Method (so directly in the function). This however will cause some other problems.
In the end I have gotten multiple errors but I assume it all has to do with the same thing, the errors were the following:
Unable to determine the principal end of the 'Solution.Data.RoomMethod_Method' relationship. Multiple added entities may have the same primary key.
The property 'RightId' is part of the object's key information and cannot be modified.
Conflicting changes to the role x of the relationship y have been detected
So now the actual question:
Is there an (easy) way to call SaveChanges() after all 3 entities have been added with EF but also handling the FK errors? Does this mean I have to generate the Id's myself? Or was the first approach better (Calling SaveChanges directly after adding the EntityMethod object).
For now I have some not-nice-looking solution with doing a direct INSERT statement after adding a new EntityMethod (using Dapper). This kind-of works but I assume there is a better way wherein I can just use EF6.
P.S. calling SaveChanges() after adding the EntityMethod was basically the same by doing it with Dapper, however this generated some other errors while using Dapper it didn't generate those errors.
You can wrap several SaveChanges within a transaction, and EF (or probably SQL) will generate the required ids at each point, allowing you to reference them later on.
using(var transaction = _context.Database.BeginTransaction())
{
var p1 = new Something { Name = "Fred" };
_context.SaveChanges();
var a2 = new Dependency { SomethingId = p1.Id }; <-- p1.Id now has an Id value
_context.SaveChanges();
var b3 = new OtherDependency { DependencyId = a2.Id }; <-- a2.Id now has an Id value
_context.SaveChanges();
transaction.Commit(); <-- All 3 changes are fully committed to the db at this point.
}

DbUpdateConcurrencyException for tables with foreign keys

Update: Ahh! Dumb error: I had two instances of a Repository available, and, our implemntation requires that we provide a connection string for each Repo, so the two Repos were pointing to different databases, and I was adding an entity from one DB to another, and its id probably could not be found in the updated DB.
I'm running .Net MVC 4.0, with EF 5. We implement a repository pattern. In the following Delete method of a WebApi controller I have the following code:
int userID = UserHelper.GetCurrentUserID();
DateTime now = DateTime.UtcNow;
ExhibitLinkRepository el = new ExhibitLinkRepository();
el.setCase = caseID;
ExhibitLink link = el.All.SingleOrDefault(l => l.id == id);
//Mark Link Deleted
link.usermodified_id = userID;
link.datetimemodified = now;
link.deleted_flag = true;
exhibitLinkRepository.InsertOrUpdate(link);
exhibitLinkRepository.SaveChanges();
Where exhibitLinkRepository.InsertOrUpdate:
public void InsertOrUpdate(ExhibitLink exhibitLink)
{
if (exhibitLink.id == default(int))
{
// New entity
context.ExhibitLinks.Add(exhibitLink);
}
else
{
// Existing entity
context.Entry(exhibitLink).State = EntityState.Modified;
}
}
When I invoke context.SaveChanges() I get the dreaded:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
Now, the ExhibitLink table has foreign key constraints as follows:
There happen to be triggers on the underlying DB for some of these related tables, but disabling them did not change the outcome.
I just don't get it. Any ideas?

Entity Framework inserting child objects in the wrong order

Question
Why is EF first inserting a child object (PersonnelWorkRecord) with a dependency, before the object that it is depended on (TimesheetActivity). Also what are my options on correcting this?
ERD (simplified)
This is predefined by another system out of my direct control.
EF setup and save code
I am not sure I understand why/how Entity Framework is inserting the objects I have in the order it does however here is the code I am using to insert a parent and several children.
using (var db = new DataContext(user))
{
timesheet.State = State.Added;
timesheet.SequenceNumber = newSequenceNumber;
this.PrepareAuditFields(timesheet);
//To stop EF from trying to add all child objects remove them from the timehseets object.
timesheet = RemoveChildObjects(timesheet, db);
//Add the Timesheet object to the database context, and save.
db.Timesheets.Add(timesheet);
result = db.SaveChanges() > 0;
}
SQL Trace of EF's Inserts
When I run the code I get a SQL foreign key violation on the PersonnelWorkRecord (TimesheetActivityID) because I have not yet added the Activity (see trace).
exec sp_executesql N'insert [dbo].[Timesheets]([ProjectID], [TimesheetStatusID], ...
exec sp_executesql N'insert [dbo].[PersonnelWorkdays]([TimesheetID], [PersonnelID], ...
exec sp_executesql N'insert [dbo].[PersonnelWorkRecords]([PersonnelWorkdayID],[TimesheetActivityID], ...
Data Context Summary
modelBuilder.Entity<PersonnelWorkday>().HasRequired(pwd => pwd.Personnel).WithMany(p => p.PersonnelWorkdays).HasForeignKey(pwd => pwd.PersonnelID).WillCascadeOnDelete(false);
modelBuilder.Entity<PersonnelWorkday>().HasRequired(pwd => pwd.Timesheet).WithMany(t => t.PersonnelWorkdays).HasForeignKey(pwd => pwd.TimesheetID).WillCascadeOnDelete(false);
modelBuilder.Entity<PersonnelWorkRecord>().HasRequired(pwr => pwr.PersonnelWorkday).WithMany(pwd => pwd.PersonnelWorkRecords).HasForeignKey(pwr => pwr.PersonnelWorkdayID).WillCascadeOnDelete(false);
modelBuilder.Entity<PersonnelWorkRecord>().HasRequired(pwr => pwr.TimesheetActivity).WithMany(ta => ta.PersonnelWorkRecords).HasForeignKey(pwr => pwr.TimesheetActivityID).WillCascadeOnDelete(false);
modelBuilder.Entity<TimesheetActivity>().HasRequired(ta => ta.ProjectActivity).WithMany(a => a.TimesheetActivities).HasForeignKey(ta => ta.ProjectActivityCodeID).WillCascadeOnDelete(false);
modelBuilder.Entity<TimesheetActivity>().HasOptional(ta => ta.Facility).WithMany(f => f.TimesheetActivities).HasForeignKey(tf => tf.FacilityID).WillCascadeOnDelete(false);
modelBuilder.Entity<TimesheetActivity>().HasRequired(ta => ta.Timesheet).WithMany(t => t.TimesheetActivities).HasForeignKey(ta => ta.TimesheetID).WillCascadeOnDelete(false);
Remove Child Objects
Here is the code for the child objects method. I added this method to remove the objects from the timesheets' child objects related objects that are not foreign keys. For example I have a Crew object but I also have a CrewID foreign key, so I have set Crew = null so that EF does not try to insert it since it already exists.
private Timesheet RemoveChildObjects(Timesheet timesheet, DataContext db)
{
timesheet.Crew = null;
timesheet.Foreman = null;
timesheet.Location = null;
timesheet.Project = null;
timesheet.SigningProjectManager = null;
timesheet.TimesheetStatus = null;
timesheet.Creator = null;
timesheet.Modifier = null;
if (timesheet.TimesheetActivities != null)
{
foreach (TimesheetActivity tsa in timesheet.TimesheetActivities)
{
tsa.Creator = null;
if (tsa.EquipmentWorkRecords != null)
{
tsa.EquipmentWorkRecords = RemoveChildObjects(tsa.EquipmentWorkRecords, db);
}
tsa.Facility = null;
tsa.Modifier = null;
if (tsa.PersonnelWorkRecords != null)
{
tsa.PersonnelWorkRecords = RemoveChildObjects(tsa.PersonnelWorkRecords, db);
}
tsa.ProjectActivity = null;
tsa.Structures = null;
tsa.Timesheet = null;
}
}
if (timesheet.TimesheetEquipment != null)
{
foreach (TimesheetEquipment te in timesheet.TimesheetEquipment)
{
te.Equipment = null;
te.Timesheet = null;
}
}
if (timesheet.EquipmentWorkdays != null)
{
timesheet.EquipmentWorkdays = RemoveChildObjects(timesheet.EquipmentWorkdays, true, db);
}
if (timesheet.TimesheetPersonnel != null)
{
foreach (TimesheetPersonnel tp in timesheet.TimesheetPersonnel)
{
tp.Personnel = null;
tp.PersonnelWorkday = null;
if (tp.PersonnelWorkday != null)
{
tp.PersonnelWorkday = RemoveChildObjects(tp.PersonnelWorkday, db);
}
tp.Timesheet = null;
}
}
if (timesheet.PersonnelWorkdays != null)
{
timesheet.PersonnelWorkdays = RemoveChildObjects(timesheet.PersonnelWorkdays, true, db);
}
return timesheet;
}
Debug of values before EF save
From my understanding anything an dbContex.ObjectNameHere.Local will be added/modified/deleted when a dbContext.Save() is called. (Depending on what the entity State is set too.) Here is what EF is trying to save before I call the save() and get an SQL FK exception.
Then I get the FK exception.
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_PersonnelWorkRecords_TimesheetActivities". The conflict occurred
in database "VPMTEST_GC", table "dbo.TimesheetActivities", column
'TimesheetActivityID'. The statement has been terminated.
Notes
Please let me know if there is anything I can post to help describe my question. I have looked around google / SO for answers but so far no solid answers, it looks like EF can not determine the order of inserting objects unless the Domain model is setup differently? I am not able to change the structure of most objects as they are used by another system. I can attempt to change my EF call, I would prefer not to use Raw SQL as the objects are quite a bit more extensive then the simplified versions I have posted here.
Similar questions: Self referencing entity and insert order
In your RemoveChildObjects method I see the line...
tsa.Timesheet = null;
So, apparently your are setting the inverse navigation property of Timesheet.TimesheetActivities to null. Are you doing the same with PersonnelWorkRecord.TimesheetActivity and PersonnelWorkRecord.PersonnelWorkday, i.e. do you set those properties to null as well in the nested RemoveChildObjects methods?
This could be a problem because you have two different paths from Timesheet to PersonnelWorkRecord, namely:
Timesheet -> TimesheetActivities -> PersonnelWorkRecords
Timesheet -> PersonnelWorkdays -> PersonnelWorkRecords
When you call db.Timesheets.Add(timesheet) I believe EF will traverse each branch in the object graph one by one and determine on the path which related objects ("nodes") are dependent and which are principal in a relationship to determine the order of insertion. timesheet itself is principal for all its relationships, therefore it is clear that it must be inserted first. Then EF starts to iterate through one of the collections Timesheet.TimesheetActivities or Timesheet.PersonnelWorkdays. Which one comes first doesn't matter. Apparently EF starts with Timesheet.PersonnelWorkdays. (It would not solve the problem if it would start with Timesheet.TimesheetActivities, you would get the same exception, but with PersonnelWorkRecord.PersonnelWorkday instead of PersonnelWorkRecord.TimesheetActivity.) PersonnelWorkday is only dependent on Timesheet which is already inserted. So, PersonnelWorkday can be inserted as well.
Then EF continues traversing with PersonnelWorkday.PersonnelWorkRecords. With respect to the PersonnelWorkday dependency of PersonnelWorkRecord there is again no problem because the PersonnelWorkday has already been inserted before. But when EF encounters the TimesheetActivity dependency of PersonnelWorkRecord it will see that this TimesheetActivity is null (because you've set it to null). It assumes now that the dependency is described by the foreign key property TimesheetActivityID alone which must refer to an existing record. It inserts the PersonnelWorkRecord and this violates a foreign key constraint.
If PersonnelWorkRecord.TimesheetActivity is not null EF would detect that this object hasn't been inserted yet but it is the principal for PersonnelWorkRecord. So, it can determine that this TimesheetActivity must be inserted before the PersonnelWorkRecord.
I would hope that your code works if you don't set the inverse navigation properties to null - or at least not the two navigation properties in PersonnelWorkRecord. (Setting the other navigation properties like tsa.Creator, tsa.Facility, etc. to null should not be a problem because those related objects really already exist in the database and you have set the correct FK property values for those.)
This may no longer be valid, however is it an option to use a transaction and adding each child object individually?
Note:
I think Slauma's solution is more complete, however a transaction call may still be an option for others with similar issues.

The relationship could not be changed because one or more of the foreign-key properties is non-nullable

I am trying to add some values as well as trying to remove the selected values from my Database.
I am using code as below:
[HttpPost]
public ActionResult SavePlaylist(List<ItemEditViewModel> content, long playlistid, List<long> deleted, string Title)
{
var playlist = db.Playlists.Include("PlaylistContents").FirstOrDefault(x => x.PlaylistId == playlistid);
for (int i = 0; i < content.Count; i++)
{
var pc = new PlaylistContent();
pc.Sequence = content[i].MetaID;
playlist.PlaylistContents.Add(pc);
}
for (int i = 0; i < deleted.Count; i++)
{
long delid = deleted[i];
ar remove = playlist.PlaylistContents.FirstOrDefault(x => x.PlaylistContentId.Equals(delid));
playlist.PlaylistContents.Remove(remove);
}
db.SaveChanges();
return JSON(playlist);
}
The Values get added Successfully But at the time of deleting the values from them then the Error is shown like this::
The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
what can I do to solve this Error. Is there any kind of Mistakes in the Business logic.
The following code removes the object from the collection:
playlist.PlaylistContents.Remove(remove);
But, when you call SaveChanges, it fails because the FK column is not nullable. Why? because EF is not deleting the row but setting the id value to zero. You need to delete the row and in order to do that you can do this:
db.PlaylistContents.Remove(remove); // this line removes the row from the database
playlist.PlaylistContents.Remove(remove); // this line removes the object form the collection
db.SaveChanges();
I solved this doing similar to this:
DataModel.PlayListContent.Remove(remove)
There is a relation between two objects which is not Optional, you can add the Optional to your mapping to enable setting the null value for foreign keys.
This question EF 4.1: Difference between .WithMany() and .WithOptional() ? will help.

Entity Framework: Updating a field to a null value?

I'm using stubs to update my entities and when the updated entity consists of columns that have values changed from non-nulls to nulls, the nulls are not persisted to the database i.e. the record continues to hold the previous non-null values.
What am I doing wrong?
public void UpdateEntity(Entity e)
{
_context.Works.Attach(new Entity{ Id = e.Id });
_context.ApplyCurrentValues("Entities", e);
_context.SaveChanges();
}
The problem is that you need to assign null to these properties after you Attach(), not before. Perhaps ApplyCurrentValues() only copies non-already-identical properties? (I've never tested, but it would be reasonable if it did.)
Try to avoid loading entities using Attach. If you just get entity from DB, you don't have to bother with settings flags on nulled items:
var stage = Db.Stages.First(s => s.ID == someId);
stage.Notation = vm.Notation; // can be also null
Db.SaveChanges();

Categories

Resources