Deleting a child object of an aggregate root in Entity framework - c#

This might be asked before but I can't seem to find a solution on the site so here we go:
Here is an oversimplified version of my domain model. I have 2 classes representing 2 tables in the database:
public Class Person
{
public int Id { get; set;}
public string Name { get; set;}
public virtual List<Contact> Contacts { get; set;}
public void AddContact(string value)
{
//some validation code
Contacts.Add(new Contact(value));
}
public void DeleteContact(Contact contact)
{
//some validation code
Contacts.Remove(contact);
}
}
public Class Contact
{
public int Id { get; set;}
public string Value { get; set;}
public virtual Person Person { get; set;}
public int PersonId { get; set;}
}
Now Person is my aggregate root here. I am trying to follow the DDD principal by only making the repository for the aggregate root. Adding contact works fine.
The problem I have is when deleting the contact. It gives 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.
Is there anyway past it. If the relation property is non-nullable shouldn't entity framework automatically delete the contact.
Now I know that deleting from the collection is not the same as deleting it from the context but I don't want to reference DbContext from my domain model.
I only have PersonRepository.
Kindly provide a solution or help me understand if I am getting any concept wrong.

That's a common problem when doing DDD with EF. Two solutions worked well for me so far:
Return the removed instance from your DeleteContact method. The method is most probably called from an application service which holds a repository. You can then use it to remove the instance from DbContext.
If you use domain events you can use one to notify others about contact removal. You could then place a handler for this event in the infrastructure layer which would remove the contact from DbContext.

It looks like you're having the same problem as in this post. Basically, when you remove the contact from the collection, you are not actually deleting it; you are only orphaning it, and in the process, setting its PersonId to null (which is not possible for an int, of course).
One possible solution is to make PersonId nullable in the Contact class:
public int? PersonId { get; set; }
Then, in your DbContext, override SaveChanges to automatically delete the orphaned records:
public override int SaveChanges()
{
foreach (Contact contact in Contacts.Local.Where(c => c.PersonId == null))
{
Contacts.Remove(contact);
}
return base.SaveChanges();
}
Disclaimer: I haven't tested that code but hopefully it is a good starting point.

Related

Entity framework 6 Concept clear regarding Remove, RemoveRange, EntityState.Deleted

I use Entity framework 6 in my projects and I always have doubts regarding some of the concepts which are used to delete objects using EF.
I still don't know which one works in which scenario. I just try all and if one works I leave it until the code is working. But no wi need to understand this concept once and for all. I did my research my unable to understand the concept clearly.
I have a domain class in EF which have multiple referencing entities. For example. I have a domain class called Course and It has multiple referencing objects mentioned below in the code.
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
public virtual PricingSchedule PricingSchedule { get; set; }
public virtual ICollection<CustomerCourse> AssignedCustomers { get; set; }
public virtual ICollection<License> Licenses { get; set; }
public virtual ICollection<GroupLicense> GroupLicenses { get; set; }
public virtual ICollection<GroupCourse> GroupCourses { get; set; }
public virtual ICollection<Learner> Learners { get; set; }
}
Now I have to delete the course from the DB with all of its referencing entities. For example, If the course is deleting then its properties like AssignedCustomers, Licenses etc all must be deleted.
But I don't understand one thing using Entity framework.
For deleting an entity from DB we have multiple options like.
Remove
RemoveRange
EntityState.Deleted
Sometimes Remove works but sometime RemoveRange Works and sometime Entitystate.Deleted works. Why?
My code is for deleting a Course
var courses = _context.Courses
.Include("AssignedCustomers")
.Include("PricingSchedule")
.Include("Licenses")
.Include("GroupCourses")
.Include("GroupLicenses")
.Where(e => courseIds.Contains(e.Id)).ToList();
if (courses != null && courses.Count > 0)
{
courses.ForEach(currentCourse =>
{
_context.Entry(currentCourse.PricingSchedule).State = EntityState.Deleted;
Sometime remove range works and code run successfully
_context.CustomerCourses.RemoveRange(currentCourse.AssignedCustomers);
Below line of code gives me error but in other scenario it works why?
//currentCourse.AssignedCustomers.ToList().ForEach(ac =>
//{
// //currentCourse.AssignedCustomers.Remove(ac);
// _context.Entry(ac).State = EntityState.Deleted;
//});
_context.Entry(currentCourse).State = EntityState.Deleted;
});
}
_context.SaveChanges();
Can anyone explain to me the difference in which situation I should use what?
The error I receive most of the time is
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.
This error comes up when I use this piece of code
currentCourse.AssignedCustomers.ToList().ForEach(ac =>
{
_context.Entry(ac).State = EntityState.Deleted;
});
OR
currentCourse.AssignedCustomers.ToList().ForEach(ac =>
{
currentCourse.AssignedCustomers.Remove(ac):
});
after that when I hit SaveChanges The error comes up.
You need to set up the cascade rules in your schema and within Entity Framework so that it knows which related entities will be deleted when you go to delete a course. For instance you will want to cascade delete while others like Learner would likely have a null-able key which can be cleared if a course is removed.
Provided it is set up correctly, you should just need to use: context.Courses.Remove(course); and the related entities will be removed or disassociated automatically. Start with a simpler example of your parent-child relationships, one child to cascade delete, another to disassociate with a nullable FK. Your current example looks to also have many-to-many associations (GroupCourses) so depending on the mapping/relationships the approach will vary.

EF eagerly loading Navigation Properties issue

I am using EF6 with Generic Repository pattern. Recently I experienced a problem trying to delete a composite entity in a single go. Here is a simplified scenario:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Parent")]
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
For deleting the Parent entity with related Children I am doing something like this:
public virtual T GetById(int id)
{
return this.DBSet.Find(id);
}
public virtual void Delete(T entity)
{
DbEntityEntry entry = this.Context.Entry(entity);
if (entry.State != EntityState.Deleted)
{
entry.State = EntityState.Deleted;
}
else
{
this.DBSet.Attach(entity);
this.DBSet.Remove(entity);
}
}
First I find the parent object by ID and then pass it to the delete method to change it's state to deleted. The context.SaveChanges() finally commits the delete.
This worked fine. The find method only pulled up Parent object and Delete worked since I have a cascade on delete enabled on Children.
But the moment I added another property in Child class:
[ForeignKey("Gender")]
public int GenderId { get; set; }
public virtual Gender Gender { get; set; }
For some reason EF started pulling related Children on the Parent.Find() method. Because of this I get the following 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.
Even after reverting the changes (removing the Gender property) the problem still exists. I am not able to understand this weird behavior!!
All I want to do is Delete the Parent object along with the Children.
There are some solutions around it but none really serves my purpose:
Turn LazyLoading to false - this.Configuration.LazyLoadingEnabled = false; This works but in my real application I need this property to true.
Iterate all children first and Delete them and then delete the Parent. This seems at best a workaround and is very verbose.
Use Remove() rather than just changing the EntityState to Deleted. I need to track Changes for Auditing so EntityState helps there.
Can someone explain why EF is loading related Entities even when I am not using them?
It seems that the problem was related to the life-cycle of context. I am using Unit Of Work and injecting it into my service layers using ninject.
kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();
The UnitOWork class implements IDisposable.
public bool DeleteView(int viewId)
{
// This is a workaround. It seems ninject is not disposing the context.
// Because of that all the info (navigation properties) of a newly created view is presisted in the context.
// Hence you get a referential key error when you try to delete a composite object.
using (var context = new ApplicationDbContext())
{
var repo = new GenericRepository<CustomView>(context);
var view = repo.GetById(viewId);
repo.Delete(view);
context.SaveChanges();
}
//var model = _unitOfWork.CustomViews.GetById(viewId);
//_unitOfWork.CustomViews.Delete(model);
//_unitOfWork.Save();
return true;
}
The commented code throws and error, while the un-commented one (using block) works. A controller method before this call loads the CustomView entity (which is of a similar structure as Parent with a list of children). And a subsequent user action can be triggered to delete that view.
I believe this has something to do with the context not being disposed. Maybe this has something to do with Ninject or UnitOfWork, I haven't been able to pin-point yet. The GetById() might be pulling the whole entity from context cache or something.
But the above workaround works for me. Just putting it out there so that it might help somebody.

Fail to remove ef entity with parent property required

I am not fantastic with EF so maybe it's an easy one.
I Have
public void DeleteLicense(int licenseId)
{
var entityToDelete = context.Licenses.Find(licenseId);
context.Licenses.Remove(entityToDelete);
}
I have checked that it finds correct license, and context is a ninject (one per request) DbContext,
But I get a weird error when I call SaveChanges() on the context after running the function above. I get: "The CustomerName field is required."
Now this is weird because CustomerName is in Account (not Licence) they are linked, but still. So here follows some more:
My Account entity
[Required]
public String CustomerName { get; set; }
public virtual ICollection<License> Licenses { get; set; }
...
My License entity
public virtual Account Account { get; set; }
...
My fluent setup
modelBuilder.Entity<Account>().HasMany(x => x.Licenses)
.WithRequired(x => x.Account).WillCascadeOnDelete(false);
I don't understand, because even if there is a failing restraint then why missing CustomerName. I don't touch CustomerName when I delete a license and the CustomerName is set since before.
Update
So here is some more details from the code. The full execution path as far as I can see is
DeleteLicenseAPI below takes the call, the ID is correct, it passes over to a private function.
The private function calls the DeleteLicense shown close to the top of the question.
The Commit() only calls context.SaveChanges();
public ActionResult DeleteLicenseAPI(int licenseId)
{
if (DeleteLicense(licenseId))
{
return Content("ok");
}
return Content("[[[Failed to delete license]]]");
}
private bool DeleteLicense(int licenseId)
{
//todo: sort out busniess rules for delete, is cascaded?
_accountRepository.DeleteLicense(licenseId);
_accountRepository.Commit();
return true;
}
The _accountRepository looks like this
public class EFAccountRepository : EntityFrameworkRepository<Account>
, IAccountRepository
public EFAccountRepository(EvercateContext context) : base(context)
{
}
And here is the code in Ninject that sets it all up
kernel.Bind<EvercateContext>()
.To<EvercateContext>()
.InRequestScope()
.WithConstructorArgument("connectionStringOrName", "EvercateConnection");
kernel.Bind<IAccountRepository>().To<EFAccountRepository>();
So even tho I use Unit of Work as far as I can see (and it shouldn't) nothing else is called in this request before running SaveChanges.
Is there any way to see what a DbContext will do on SaveChanges, without actually running the method (as it throws DbEntityValidationException)
I can imagine that this weird exception could occur if you are initializing the Account navigation property in the License constructor like so:
public License
{
Account = new Account();
}
The flow when you call...
var entityToDelete = context.Licenses.Find(licenseId);
context.Licenses.Remove(entityToDelete);
...is then probably:
License entity gets loaded (without navigation property Account) and attached to the context (state Unchanged)
The constructor sets the Account navigation property, but it doesn't get attached (state Detached)
When you call Remove for the License entity DetectChanges is called internally by EF. It detects that License.Account is refering to a detached entity and attaches it to the context (in state Added). The state of the License is changed to Deleted.
When you call SaveChanges the change tracker finds two entities: The License in state Deleted and the Account in state Added.
Validation runs and finds that the required property CustomerName for the entity Account that is supposed to be inserted into the database is null (because only the default constructor of Account is called).
The validation exception is thrown.
I'm not sure if the details are right but something like that is probably happening.
In any case you should delete the Account = new Account(); from the License constructor and also check if you initialize other reference navigation properties in entity constructors in your codebase as well. (Initializing empty navigation collections is OK.) This is a common source of notoriously strange problems that are difficult to find and understand.
I tried overriding SaveChanges as recommended.
When I did I found a License about to be deleted (as it should) but I also found an Account about to be created.
I changed the DeleteLicense as displayed below.
public void DeleteLicense(int licenseId)
{
var entityToDelete = context.Licenses.Find(licenseId);
entityToDelete.Account = null;
context.Licenses.Remove(entityToDelete);
}
And right away the code works. The License is removed and the account is still there, but no new account is created.
But why, I do not understand why at all.
Is it something in the relation i set with fluent api?
In my case this happened because my entity had a [Required] property that was of type int? which made it nullable. While inspecting the model that came back from the db I saw the property had a value but the entity that ended up being saved to the database had that value stripped during SaveChanges for some reason. When I switched the value to the expected int type all worked just fine. :shrug:
I had a similar issue and for me, it looked like I hadn't correctly established the relationship between Parent and Child in their respective classes.
My fix was to add the attributes specified below to the Child class, for the property that represented its Parent's Id
public class Child
{
[Key, Column(Order = 1)]
public string Id { get; set; }
[Key, ForeignKey("Parent"), Column(Order = 2)] // adding this line fixed things for me
public string ParentId {get; set;}
}
public class Parent
{
[Key, Column(Order = 1)]
public string Id { get; set; }
...
public virtual ICollection<Child> Children{ get; set; }
}

Entity Framework: Create and update related objects in WPF

I'm using EF 6.0 and code-first approach.
I have problem with create and update data in db via Entity Framework. I'm not sure if I need to make db.Groups.Attach(student.Group) before storing Student. Without this after saving Student I also have new Group with the same Name but other GroupId.
Moreover I can't update student because I'm getting exception: The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
public class Student {
[Key]
public int StudentId {get; set;}
public string Name {get; set;}
public Group Group {get; set;}
}
public class Group {
[Key]
public int GroupId{ get; set;}
public string Name {get; set;}
public virtual ICollection<Student> Students {get; set;}
}
.
public class StudentDao {
public void createStudent(Student student) {
using (var db = new StorageContext()) {
// without this also creates new Group.
db.Groups.Attach(student.Group);
db.Students.Add(student);
db.SaveChanges();
}
}
public void updateStudent(Student student) {
using (var db = new StorageContext()) {
var original = db.Students.Find(student.StudentId);
if (original != null) {
original.Name = student.Name;
original.Group = student.Group;
db.SaveChanges(); //exception
}
}
}
}
Without this (db.Groups.Attach(student.Group)) after saving Student I also have new Group
That's because Adding an entity to a DbSet marks all adhering entities that are not yet tracked by the context as Added. This is an EF feature, like it or not, so you have to first attach the entities you don't want to re-insert.
Moreover I can't update student because I'm getting exception
For some reason, in the update method the student and its group are still attached to a context. Apparently, there is some other context active in the StudentDao class. You have to make sure this context's lifespan is over when you update the student or else (second best) detach the student and the group from it.
An off-topic advice: if you can, abandon this DAO pattern. EF works much better when you use the DbContext and its DbSets in service-like methods that handle one unit of work. With these DAO's it's impossible to work transactionally and they cause piles of repeated code.
Been a while since I worked on Entity but from what I remember you can't just change the Group, you have to give it a GroupID field as well, change that instead and then reload/update the Student object from the database so that the Group object gets loaded and assigned from within the same context.
This is just one of the reasons I use NHibernate.

deleting related entities in a one-to-many relationship

I have this domain:
public class Phone {
public int Id { get; set; }
public string Number { get; set; }
public Person Person { get; set; }
}
public class Person {
public int Id { get; set; }
public IList<Phone> Phones { get; set; }
}
I load a Person and clear its Phones. But the operation cause an error:
// actually loads person from repository...
var person = _personRepository.Include(p => p.Phones).Where(p => p.Id == 1).First();
person.Phones.Clear();
_personRepository.Update(person);
Above you can see the simpled logic of loading a Person and clearing its Phones. But this error occurs:
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.
Actually I want to clear all of Person.Phones and add some new items. But I want to clearing them in one query, not delete them one by one.
Have you any idea? Can you help me please? Thanks in advance.
You can't generate set based SQL in EF. So there's no way in EF to generate a single SQL statement that deletes all Phone records given a Person.Id.
You can write the SQL yourself and pass it to either ObjectContext.ExecuteStoreCommand or DbContext.Database.ExecuteSqlCommand depending on your model.
foreach(var phone in person.Phones)
{
context.DeleteObject(phone);
}
person.Phones.Clear();
may help.

Categories