Entity Framework - Deleting Complex Object - c#

I'm have the following Entity Framwork objects:
Evaluation, Stage, Apartment
Evaluation contains many Stages and Stage contains many Apartments.
I'm trying to deleted a certain stage as follows:
var deletedStages =
originalEvaluation.Stages.Where(s => s.State == StateTypes.Deleted);
deletedStages.ToList().ForEach(stage =>
{
stage.Apartments.ToList().ForEach(
apartment => stage.Apartments.Remove(apartment)
);
originalEvaluation.Stages.Remove(stage);
});
deletedStages.ToList().ForEach(stage =>
{
stage.Apartments.ToList().ForEach(apartment =>
shechtmanEntities.Apartments.DeleteObject(apartment)
);
shechtmanEntities.Stages.DeleteObject(stage);
});
}
}
try
{
shechtmanEntities.SaveChanges();
}
But I'm keep getting an Exception : "The relationship could not be changed because one or more of the foreign-key properties is non-nullable".
I know it has to do with a null foreign-key, but I can't understand which? and Why?
Thanks.

If you cannot Cascade Delete (SQL server can be funny about that: https://stackoverflow.com/a/6065583/613004) asdutzu suggests, then:
If it's one-to-many relationship (sounds like it is), then you'll need to manually delete each child object and save-changes before deleting the parent. I.E. delete the apartment (or re-assign it to another Stage), then delete the stage, and so on.
Otherwise, if it's many-to-many, and the joining table is exposed through the entity model, delete the joins between the stages and apartments first, then delete the stage. If not, then detach the apartment from the stage and save changes before deleting the stage.

Try just deleting the Evaluation or Stage and rely on a Cascade Delete to remove it's children entities (in your case Apartments) rather than removing them each individually.

Related

Entity Framework update the intermediate table of a many-to-many relation

I've got a many-to-many relation between user and project. Like:
class User
{
public ICollection<Project> Projects { get; set; }
}
class Project
{
public ICollection<User> Users { get; set; }
}
Entity Framework automatically generated the intermediate table.
The thing is I want to update the user, along with the entire list of projects. This list could've been modified in any way, projects could've been added and deleted. at the same time before the user object gets updated.
I always get the same error, that Entity Framework tried to add a duplicated entry in the intermediate table.
I've tried numerous things without success (a few listed below).
var tmp = Context.Entry(user); // user being the updated object.
tmp.State = EntityState.Modified;
tmp.Collection(e => e.Projects).IsModified = true;
Context.Users.Update(user);
Context.SaveChanges();
or
var tmp = Context.Users.SingleOrDefault(u => u.Id == user.Id);
if (tmp == null)
return null;
Context.Entry(tmp).CurrentValues.SetValues(user);
Context.SaveChanges();
return user;
or just plain old update:
Context.Users.Update(user);
Context.SaveChanges();
But none of these worked.
The issue sounds like you have a detached User Entity with a set of Projects and you want to pass that into a method, associate with the DbContext to persist the changes.
You are encountering issues with doubling up records because while you attach the user to the DbContext, it will treat each of the Project entities associated with the user as new instances because they don't reference tracked instances themselves.
Updating detached entities with associations is fairly involved, especially where you expect to possibly add or remove associations in an operation.
The recommended approach would be to load the current User and Projects from the DB then leverage Automapper to guard what values you can copy over from the detached entity, and then go through the associations to add/remove any project references that have changed. If it is possible to create a brand new project to associate to the user as part of this operation, you need to handle that as well.
var existingUser = Context.Users.Include(x => x.Projects).Single(x => x.UserId == user.UserId);
Mapper.Map(user, existingUser);
// Where Automapper is configured with a User to User mapping with allowed
// values to copy over, ignoring anything that cannot legally be changed.
var newProjectIds = user.Projects.Select(x => x.ProjectId).ToList();
var existingProjectIds = existingUser.Projects.Select(x => x.ProjectId).ToList();
var projectIdsToAdd = newProjectIds.Except(existingProjectIds).ToList();
var projectIdsToRemove = existingProjectIds.Except(newProjectIds).ToList();
var projectsToAdd = Context.Projects.Where(x => projectIdsToAdd.Contains(x.ProjectId)).ToList();
var projectsToRemove = existingUser.Projects.Where(x => projectIdsToRemove.Contains(x.ProjectId)).ToList();
foreach(var project in projectsToRemove)
existigUser.Projects.Remove(project);
foreach(var project in projectsToAdd)
existingUser.Projects.Add(project);
Context.SaveChanges();
... This example does not cover the possibility of brand new projects. If the updated user can include a brand new project then you need to detect those when looking for projectsToAdd to add any Projects from the passed in project list where the ID is in new project IDs but not found in the DB. Those detached references can be added to the User loaded from the DbContext, however you do need to handle any navigation properties that each Project might have to avoid duplication, substituting each of those with references to tracked entities, including any bi-directional reference back to the User if present.
In general, dealing with detached entities has various considerations that you need to keep in mind and handle very deliberately. It is generally much better to avoid passing detached entities around and instead aim to pass a minimal representation of the data you want to associate, then load and adjust that server-side. Usually the argument to using detached entities is to avoid having to load the data again, however this leads to more code when trying to synchronize these detached instances, and neglects the fact that data state could have changed since the detached instances were taken. The above code for instance should also be looking at entity versioning between the detached entity and the loaded state to detect if anyone else might have made changes since the detached copies were read at the start of the user process for making the changes.

Entity Framework Updating Many to Many relationships properly

In my School EF Model, I have Kids and Tutorials in many-to-many relationship.
Let's assume both Kids and Tutorials have existing items in them, now we just want to change their existing relationships. That is, to add/delete some tutorials from a kid.
var kid; //the request target to modify relationships
//kid.Tutorials has the old existing relationships to be modified by add/del
var tutorialsToAdd; //the request to add relationships
var tutorialsToDel; //the request to del relationships
using (var conn = new SchoolEFModels(efConnectionStr)) {
conn.Kids.Attach(kid);
kid.Tutorials.ForEach(t => conn.Tutorials.Attach(t));
kid.Tutorials.AddRange(tutorialsToAdd); //simple add extension in batch
kid.Tutorials.RemoveRange(tutorialsToDel); //simple del extension in batch
conn.SaveChanges();
}
When I do this, I got an exception saying:
"Cannot insert duplicate key in object 'dbo.Tutorials'. The duplicate key value is (10)."
I can see EF is trying to create new Tutorial items instead of updating the existing relationship for me. Which is what I don't want. You misunderstood me EF!
What is wrong with my code? How do I make it update Many-to-Many relationships?
I figured it out.
Adding/removing it will make EntityState turn to Added/Deleted. Therefore, causing it to reinsert existing Ids as the article mentioned, thank Gert there for the link.
So, if you modify each of the conn.entry(kid/tutorials).State to EntityState.Modified and then call conn.ChangeTracker.DetectChanges(); then conn.SaveChanges(); it will only update the many-to-many table as expected.
UPDATE:
One thing you need to be careful tho. If the in-memory objects list of Kids and Tutorials are linked to each other. e.g. Kids[0].Tutorials[0] == Tutorials[0] && Tutorial[0].Kids[0] == Kids[0] EF will not be able to handle this dead loop for you. You need to break this circular link first.
To do so, my approach is to open a Connection and read the Kid out Includes(Tutorials), and then use the result to update many to many relationship, but not to use the in-memory objects.

Is it possible to specify child objects that should also be deleted with their parents in Linq?

In order to make sure that all child elements are deleted, I am currently having to do this:
ComponentType type = db.ComponentTypes.First(t => t.ID == Convert.ToInt32(button.CommandArgument));
db.RigActions.DeleteAllOnSubmit(type.Components.SelectMany(c => c.RigActions));
db.Components.DeleteAllOnSubmit(type.Components);
db.ComponentTypes.DeleteOnSubmit(type);
db.SubmitChanges();
For maintainability purposes, this is scaring me that I (or another developer) might overlook all of the cleanup necessary when a parent (or in this case - a parent of a parent) element is deleted.
Does Linq have any type of dependency property I can set so that when a Component type is deleted, it deletes all component records, and all actions belonging to each component record?
I'm not sure it can be done in Linq (it probably can). It would be far easier if you defined a CASCADE DELETE on your database though. That way, child records would be deleted automatically and you'd not need to worry about forgetting anything.
This article might give you a bit more information
http://www.mssqltips.com/sqlservertip/2743/using-delete-cascade-option-for-foreign-keys/
This article is about setting up cascade delete within EF
Entity Framework on delete cascade

EntityState.Deleted does not work, Remove(entity) does?

I've been struggling with EF when trying to read records, then delete those records in the same transaction. I was initially using the EntityState.Deleted method, which would give an 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.
But if I change it to like I have below, using .Remove(), then all is well.
What is the difference and best times to use .Remove() vs .Deleted?
How could I make this work using the .Deleted method? I have tried creating a new instance of the context to my repository to read and another to delete, but then got errors related to IEntityTracker can't track multiple instances... I also tried .Include on the initial read to load the dependent records into EF so it knows about and deletes them. I also tried .Detaching the read records first. All to no avail.
Here is the method in question. Note that I do have a generic repository which uses the .Deleted method which has served me well until this scenario (reading then deleting the same records.)
//Delete Allocation Need and AllocatedContainers for alloc need id
public ActionConfirmation<int> DeleteAllocRecords(int intFacilityId, AllocNeedSourceTypes needSourceType, int intNeedSourceId)
{
var context = new InventoryMgmtContext();
var repository = new AllocationNeedRepository(context);
//Delete Allocation Need and hence children in Allocated Containers
var srcType = needSourceType.ToString();
List<AllocationNeed> allocNeeds = repository.SearchFor(
x => x.FacilityId == intFacilityId
&& x.NeedSourceType == srcType
&& x.NeedSourceId == intNeedSourceId
).ToList();
//var deleteRepository = new Repository<AllocationNeed>(); <--tried separate instance of context to delete...no worky.
foreach (AllocationNeed allocNeed in allocNeeds)
{
try
{
//NO WORK: context.Entry(allocNeed).State = System.Data.EntityState.Deleted;
context.AllocationNeeds.Attach(allocNeed);
context.AllocationNeeds.Remove(allocNeed); <-- Works
context.SaveChanges();
}
catch (Exception ex)
{
return ActionConfirmation<int>.CreateFailureConfirmation(ex.Message, allocNeed.Id);
}
}
Remove will also remove the child objects, but using Deleted will not. You should really be using Remove for this very reason. If you really want to use Deleted, you'd have to make your foreign keys nullable, but then you'd end up with orphaned records (which is one of the main reasons you shouldn't be doing that in the first place).
1.) What is the difference and best times to use .Remove() vs .Deleted?
It appears that setting the entity's state to Deleted causes SaveChanges() to delete only that specific entity from the database, not taking into account other rows that may reference it via a non-null foreign-key column.
Remove() will take into account rows that are part of the relationship.
2.) How could I make this work using the .Deleted method?
If you have ON CASCADE DELETE behavior specified for the related rows, the database should handle it automatically. This is the default behavior when you let EF generate the database.

How do I perform cascading deletes with EF4?

On a previous project we had EF4 performing cascading deletes (Delete a parent record and the child records are deleted, too). On this project (different company), EF4 is not performing cascading deletes. What do I need to do to make EF4 perform a cascading delete?
Using just EF4's cascading delete is not enough; you should set up cascading deletes on your database as well, in case not all children are loaded into the object context. That being said, the cascade delete properties are set on the assocation. Go to the model browser, select an assocation and view properties.
I am on the same boat. I only have cascade on delete in EDM/EF4 and not (yet) in the database. Try this...
In the relationship, set the OnDelete of parent's end (1 multiplicity) to Cascade . Then in your code load all children before saving the changes (deletion).
var parent = context.Parents.SingleOrDefault(p => p.Id == parentId);
parent.Children.Load();
if (parent != null)
{
context.Parent.DeleteObject(parent);
context.SaveChanges();
}
You have two ways of cascade deleting implementation:
Set up it on the database layer (triggers or relationships)
Try to use extension methods. You can define delete logic for current table and call methods for related table updating.

Categories

Resources