Entity Framework - Code First saving many to many relation - c#

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?

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.

Entity framework 6 foreign key is not being updated on attach and save

I have a issue with entity framework 6 not updating the foreign key when I try to update a entity object. It works on insert (but then I have to set the state to Unchanged for not to reinsert a new entity in the foreign key table). I am using code first approach and generated the models myself. It's a web application so the entity objects gets detached, so I have to reattach them.
I've created a simplified example so it's easy to explain what my problem is. In this example I have a car object which has a one to many relation to make. I want to update a car and change what make it is and its name. The name is updated but not the foreign key value. How do I go ahead to get the foreign key to be updated too?
The code handeling the attaching
public void UpdateCars(Car car){
var dbContext = new CarsDbContext(); //Inherits DbContext
dbContext.Cars.Attach(car);
dbContext.Entry(car).State = EntityState.Modified;
dbContext.SaveChanges();
}
The car Entity
[Table("Car")]
public class Car
{
public Guid Id { get; set; }
public String Name { get; set; }
public virtual Make Make { get; set; }
}
[Table("Make")]
public class Make
{
public Guid Id { get; set; }
public String Name { get; set; }
}
Edit:
I did a few more changes with help from comments (thanks guys!) and I made something work but it feels like I'm doing it the wrong way because the code is far from pretty. Here's what I did:
Modified the Car object to:
public String Name { get; set; }
public Guid Make_Id
[ForeignKey("Make_Id")]
public virtual Make Make { get; set; }
In my update
var dbContext = new CarsDbContext(); //Inherits DbContext
car.Make_Id = car.Make.Id;
dbContext.Cars.Attach(car);
There surely must be a better practice around this when doing EF code first when working with detached entities?
I ended up using graphdiff which solved all my problems. Also when my entities became more complex and it tried to attachs same entity several times. Here's a article to read about it: http://blog.brentmckendrick.com/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/

What's the fundamental concept of EF that I'm missing?

I'm sure I am misunderstanding something fundamental about how EF5 works.
In a [previous question] I asked about how to pass values between actions in an ASP.NET MVC application and it was suggested I could use TempData as a mechanism to pass around data (in my case I've gone for the POCOs that represent my data model in EF).
My controllers in MVC are not aware of any persistence mechanism within EF. They make use of a service layer which I've called "Managers" to perform common tasks on my POCOs and read/persist them to the underlying datastore.
I'm writing a workflow to allow an "employee" of my site to cancel a "LeaveRequest". In terms of controllers and actions, there's an HttpGet action "CancelLeaveRequest" which takes the ID of the LeaveRequest in question, retrieves the LeaveRequest through the service layer, and displays some details, a warning and a confirm button. Before the controller returns the relevant View, it commits the LeaveRequest entity into TempData ready to be picked up in the next step...
The confirm button causes an HttpPost to "LeaveRequest" which then uses the LeaveRequest from TempData and a call down to the service layer to make changes to the LeaveRequest and save them back to the database with EF.
Each instance of a manager class in my code has it's own EF DBContext. The controllers in MVC instantiate a manager and dispose of it within the page lifecycle. Thus, the LeaveRequest is retrieved using one instance of a DBContext, and changes are made and submitted via another instance.
My understanding is that the entity becomes "detached" when the first DBContext falls out of scope. So, when I try to commit changes against the second DBContext, I have to attach the entity to the context using DBContext.LeaveRequests.Attach()? There is an added complication that I need to use an "Employee" entity to note which employee cancelled the leave request.
My code in the service layer for cancelling the leave request reads as follows.
public void CancelLeaveRequest(int employeeId, LeaveRequest request)
{
_DBContext.LeaveRequests.Attach(request);
request.State = LeaveRequestApprovalState.Cancelled;
request.ResponseDate = DateTime.Now;
using (var em = new EmployeesManager())
{
var employee = em.GetEmployeeById(employeeId);
request.Responder = employee;
_DBContext.Entry(request.Responder).State = System.Data.EntityState.Unchanged;
}
_CommitDatabaseChanges();
}
You can see that I retrieve an Employee entity from the EmployeesManager and assign this employee as the responder to the leave request.
In my test case, the "responder" to the Leave Request is the same employee as the "requestor", another property on Leave Request. The relationships are many-to-one between leave requests and a requesting employee, and many-to-one between leave requests and a responding employee.
When my code runs in it's present state, I get the following error:
AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges.
I suspect this is because EF thinks it knows about the employee in question already. The line that fails is:
_DBContext.Entry(request.Responder).State = System.Data.EntityState.Unchanged;
However, if I remove this line and don't try to be clever by telling EF not to change my employee object, the leave request gets cancelled as expected but some very strange things happen to my Employees.
Firstly, the employee who made/responded to the request is duplicated. Then, any navigation properties (like "Manager", a many-to-one relationship between an Employee and other Employees) seem to get duplicated too. I can understand that the duplication of the Manager property on Employee is because I am loading the Manager object graph in as part of GetEmployeeById and I think I understand that the original Employee is being duplicated because, as far as the LeaveRequest DBContext is concerned, it has just appeared out of nowhere (I retrieved the Employee through a different DBContext). However, assuming those two points are correct, I'm at a loss as to how I can a) prevent the Employee and it's associated object graph being duplicate in the database and b) how I can ensure the modified LeaveRequest is persisted correctly (which it seems to stop doing with various combinations of attaching, changing state to modified etc... on the employee and leave request).
Please can someone highlight the error of my ways?
My LeaveRequest entity:
public class LeaveRequest
{
public LeaveRequest()
{
HalfDays = new List<LeaveRequestHalfDay>();
}
public int CalculatedHalfDaysConsumed { get; set; }
public Employee Employee { get; set; }
public virtual ICollection<LeaveRequestHalfDay> HalfDays { get; set; }
public int LeaveRequestId { get; set; }
public DateTime RequestDate { get; set; }
public int ResponderId { get; set; }
public virtual Employee Responder { get; set; }
public DateTime? ResponseDate { get; set; }
public LeaveRequestApprovalState State { get; set; }
public LeaveRequestType Type { get; set; }
public ICollection<LeaveRequest> ChildRequests { get; set; }
public LeaveRequest ParentRequest { get; set; }
}
The "Employee" field (of type Employee...) is the person who submitted the request. The "Responder" is potentially a different, but could be the same, employee.
You should change your navigation properties to this:
public int ResponderId {get;set;}
public virtual Employee Responder { get; set; }
This scalar property will be auto-mapped to the navigation property by EF. Next you can simply do the following (and you don't need the Unchanged state):
var employee = em.GetEmployeeById(employeeId);
request.ResponderId = employee.Id;
See also this article about relationships in EF.

EntityFramework adding new object to nested object collection

This question is a continuation of:
EntityFramework adding new object to a collection
Now I understand that when using DbSet EF won't load the entire collection into memory
But what if I have something like the following code:
public class User
{
public int UserID { get; set; }
public string UserName { get; set; }
public ICollection<Role> Roles { get; set; }
}
public class Role
{
public int RoleID { get; set; }
public string RoleName { get; set; }
public User User { get; set; }
}
public class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
public class SomeClass
{
public void AssignRoleToUser(int userID, Role role)
{
var ctx = new MyContext();
var user = ctx.Users.First(x => x.UserID.Equals(userID));
user.Roles.Add(role);
ctx.SaveChanges();
}
}
In this case, I'm not using the DbSet object to add a new object to the Role collection, instead, I'm adding a new role to a specific user using an ICollection collection
So what happens in this case?
Does EntityFramewrk have to load into memory all the user's roles in order to perform the insert?
In provide code above you are not adding new role as your ctx.Users is just used to retrieve data. Somewhat similar issue is addressed in this SE post - Linq To Entities - how to filter on child entities.
I would advice to look at this short and useful article - Entity Framework 4.0 FAQ – Getting Started Guide.
No. EF does not need to know what 'Roles' the User has.
You need to learn what happens in regards to change tracking:
Once the query is run the change tracker receives an object entry for 'user'. The state of this user is 'Unchanged'
You add a new role to the user's Roles collection. This simply adds a change tracker entry for this new role and marks it as 'Added'
On SaveChanges() EF will look at your change tracker and see that the user object has not changed so nothing needs to be done there. There is also an entry for the new role which states that it needs to be added. So an SQL query will be written to insert this role.
Simple as that. You can always debug and add a watch for the state of the change tracker. The entries can be found by calling DbContext.ChangeTracker.Entries().
EF will blindly send off the 'add' to the DB.

Categories