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.
Related
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.
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.
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.
I think I am missing something simple here. I am getting the error:
"Violation of PRIMARY KEY constraint 'PK_FeatureTypes'. Cannot insert duplicate key in object 'dbo.FeatureTypeCodes'. The duplicate key value is (28).\r\nThe statement has been terminated"
I have a look-up / linked table of FeatureType - (Mountain, Lake, River, etc.) which is already populated with data and is defined as:
[Table("FeatureTypeCodes")]
public class FeatureTypeCode {
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int FeatureTypeCodeID { get; set; }
public string Name { get; set; }
}
This is linked to my place table / object like this:
[Table("Places")]
public class Place {
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int PlaceID { get; set; }
public string Name { get; set; }
public FeatureTypeCode FeatureTypeCode { get; set; }
public ICollection<PlaceCoordinate> PlaceCoordinates { get; set; }
}
Then I am loading them from the old database like this (it is part of my conversion code):
foreach (DataRow r in table.Rows) {
int ftID = Convert.ToInt32(r["FeatureTypeId"]);
Place temp = new Place {
PlaceID = Convert.ToInt32(r["PlaceID"]),
Name = r["PlaceName"].ToString(),
FeatureTypeCode = featureTypeCodeRepository.FeatureTypeCodes.FirstOrDefault(o=>o.FeatureTypeCodeID == ftID)
};
places.Add(temp);
}
The error is being generated when it tries to insert a new FeatureType object with the same ID as an existing object while saving a Place. My thought was that by loading FeatureType from the context it would not attempt to insert a new FeatureType on saving the Place object. I am obviously wrong on that, but is it something simple I am missing?
I don't think that you use the same DBContext Object in your featureTypeCodeRepository and the places.Add(temp);. So I think that basically EF don't keep track of the FeatureTypeCodes becuse it's loaded by one context, and saved by another.
While I think that Simon Edström is right (+1), you may also consider to expose the primitive foreign key field (something like FeatureTypeId?) in your Place class. Then you can simply set
FeatureTypeId = ftID;
If you're not sure whether the FK field value really exists in the FeatureTypeCodes table, you can query for its existence using the featureTypeCodeRepository even when it has a different context. Using Any() is the cheapest way to do that:
var exists = featureTypeCodeRepository.FeatureTypeCodes
.Any(o => o.FeatureTypeCodeID == ftID)
It is not uncommon to do this in entity framework. Relationships consisting of only a reference (like Place.FeatureTypeCode) are called independent associations, those with a reference and a primitive FK property foreign key associations. Julia Lerman in her book DbContext says
unless you have a very
good reason not to expose the foreign key properties you will save yourself a lot of pain
by including them
Say I have two entities like so:
public class Response
{
public int Id { get; set; }
public int PatientId { get; set; }
public virtual Patient Patient { get; set; }
public string Text { get; set; }
}
public class Patient
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Response> Responses { get; set; }
}
I want to be able to call
Patient.Responses.Remove(someResponse);
And have entity delete not only the relationship but the Response entity as well. At present if I just delete the relationship I get the following error:
System.InvalidOperationException: 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.
Reading this blog post http://blogs.msdn.com/b/dsimmons/archive/2010/01/31/deleting-foreign-key-relationships-in-ef4.aspx I realised I can achieve this by having the following mapping:
modelBuilder.Entity<Response>().HasKey(m => new { m.Id, m.PatientId });
But I don't want to change my primary key. What I want to do is override DbContext.SaveChanges() and mark for deletion any Responses where the Patient relationship has been deleted. I tried this:
public override int SaveChanges()
{
// Need to manually delete all responses that have been removed from the patient, otherwise they'll be orphaned.
var orphanedResponses = ChangeTracker.Entries().Where(
e => e.State == EntityState.Modified &&
e.Entity is Response &&
e.Reference("Patient").CurrentValue == null);
foreach (var orphanedResponse in orphanedResponses)
{
Responses.Remove(orphanedResponse.Entity as Response);
}
return base.SaveChanges();
}
But I found it's possible to attach a Response with only Response.PatientId set and not Response.Patient, entity wont have loaded the Response.Patient property so my code thinks it's been orphaned and should be deleted.
In summary
What I want to know is how can I can tell that an entity has been modified because it's FK relationship has been removed.
Use this instead:
public override int SaveChanges()
{
var responses = Responses.Local.Where(r => r.Patient == null);
foreach (var response in responses.ToList())
{
Responses.Remove(response);
}
return base.SaveChanges();
}
You need to configure the mappings such that a cascade delete will occur. To do that you need to map the model with WillCascadeOnDelete to true.
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Patient>()
.HasMany(patient=> patient.Responses)
.WithRequired(response => response.Patient)
.HasForeignKey(response => response.PatientId)
.WillCascadeOnDelete(true);
}
}
I think my problem is not with the code but rather with how I assume entity's Attach() method works. I assumed that if I attach a response with PatientId set but not Patient property then entity would populate the Patient property for me.
In fact what I think happens is entity attaches it as it is, then if I mark that entity as modified and save it, entity sees the null Patient property and assumes I want to remove the relationship, so throws an error because it would be orphaned (can't null Response.PatientId). So perhaps everything is working as designed and my SaveChanges() solution works.