EF eagerly loading Navigation Properties issue - c#

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.

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.

Refreshing lazy loaded relationships in Entity Framework

I'm trying to find a way to refresh my EF entities after they've been modified by another context. Everything works fine, except for navigation properties, which are not updated.
After the change I've tried both:
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
objectContext.Refresh(RefreshMode.ClientWins, entity);
And:
context.Entry(entity).Reload();
But neither cause the relationship to update. This is the code-first model (with some stuff cut out):
public class ElementType : IElementType
{
[Key]
public Guid ID { get; set; } = Guid.NewGuid();
public virtual List<Element> Elements { get; set; }
}
public class ElementType : IElementType
{
[Key]
public Guid ID { get; set; } = Guid.NewGuid();
public virtual ElementType ElementType { get; set; }
}
I'm adding a new Element, and refresh is not updating the Elements relationship property in ElementType. I know things are getting updated by the other context fine, because when I close everything down and restart it, everthing looks like I expect.
The context is still connected, as I can get the new entity from the DB context. I can even force the ElementType to update it's Element collection in the debugger by navagating to the new Element, checking it's relationship property, (which then triggers ElementType to update):
So it's 0 after the update methods above:
If I navigate to the context in the debugger, check the Elements set, the new Element is present, and the relationship property is set right (and refers to the same Proxy ElementType object). So this is the DBContext's Elements collection:
And now back to the origional Element:
Everything is up to date!
So I'm pretty sure everything is working except the Refresh/Update method. This question here suggests that Reload should work for lazy loaded relationships, and I can't seem to find any further information on how to actually refresh this collection. Anyone know why it's not working as I'd expect it?
Thanks to Cristian Szpisjak pointing me in the direction of the Collection method for the DbEntityEntry.
I wrote a generic method for refreshing my collections:
public void Refresh(object entity)
{
DbEntityEntry entry = context.Entry(entity);
entry.Reload();
var values = entity.GetType().BaseType
.GetProperties()
.Where(propertyInfo => propertyInfo.GetCustomAttributes(typeof(ReloadCollectionOnRefresh), false).Count() > 0)
.Select(propertyInfo => propertyInfo.Name);
foreach (string value in values)
{
var collection = entry.Collection(value);
collection?.Load();
}
}
Where the collection properties are tagged with a ReloadCollectionOnRefresh Custom Attribute:
[ReloadCollectionOnRefresh]
public virtual List<MyEntity> MyEntities{ get; set; }
which is just a pretty much empty attribute to 'tag' the collection:
[AttributeUsage(AttributeTargets.Property)]
class ReloadCollectionOnRefresh : Attribute
{
// can we add checking that this is applied to a virtual collection?
}

Deleting a child object of an aggregate root in Entity framework

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.

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 - Code First saving many to many relation

I have two classes:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
public int Id { get; set; }
public string Email { get; set; }
public virtual ICollection<Company> Companies { get; set; }
}
In my MVC application controller get new Company from post. I want to add current user to created Company in something like this.
User user = GetCurrentLoggedUser();
//company.Users = new ICollection<User>(); // Users is null :/
company.Users.Add(user); // NullReferenceException
companyRepository.InsertOrUpdate(company);
companyRepository.Save();
How it should look like to work properly? I don't know it yet but after adding user to collection I expect problems with saving it to database. Any tips on how it should look like would be appreciated.
Use this approach:
public class Company
{
public int Id { get; set; }
public string Name { get; set;}
private ICollection<User> _users;
public ICollection<User> Users
{
get
{
return _users ?? (_users = new HashSet<User>());
}
set
{
_users = value;
}
}
}
HashSet is better then other collections if you also override Equals and GetHashCode in your entities. It will handle duplicities for you. Also lazy collection initialization is better. I don't remember it exactly, but I think I had some problems in one of my first EF test applications when I initialized the collection in the constructor and also used dynamic proxies for lazy loading and change tracking.
There are two types of entities: detached and attached. An attached entity is already tracked by the context. You usually get the attached entity from linq-to-entities query or by calling Create on DbSet. A detached entity is not tracked by context but once you call Attach or Add on the set to attach this entity all related entities will be attached / added as well. The only problem you have to deal with when working with detached entities is if related entity already exists in database and you only want to create new relation.
The main rule which you must understand is difference between Add and Attach method:
Add will attach all detached entities in graph as Added => all related entities will be inserted as new ones.
Attach will attach all detached entities in graph as Unchanged => you must manually say what has been modified.
You can manually set state of any attached entity by using:
context.Entry<TEntity>(entity).State = EntityState....;
When working with detached many-to-many you usually must use these techniques to build only relations instead of inserting duplicit entities to database.
By my own experience working with detached entity graphs is very hard especially after deleting relations and because of that I always load entity graphs from database and manually merge changes into attached graphs wich are able to fully track all changes for me.
Be aware that you can't mix entities from different contexts. If you want to attach entity from one context to another you must first explicitly detach entity from the first one. I hope you can do it by setting its state to Detached in the first context.
In your constructor for the Company entity you can create an empty collection on the Users property.
public class Company
{
public Company() {
Users = new Collection<User>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
As far as saving to the database is concerned, I asked a related question a few days ago and was assured that Entity Framework is able to track the changes made to related entities. Read up on that here:
Are child entities automatically tracked when added to a parent?

Categories