Insert one record into many records
I am having a problem with inserts in Entity Framework.
My application has the following two objects:
- Reminder
- Employee
A note can be associated with multiple employees.
An employee can be associated with multiple memos.
This means many to many. I have read several articles explaining to me that a join table should be created, which I think is obvious.
In the articles, I learned that Entity Framework will automatically create a join table for me. So I did it like this:
Reminder
public Guid MemoId { get; set; }
public String Message { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
Employee
public Guid EmployeeId { get; set; }
public String Name { get; set; }
public virtual ICollection<Memo> Memos { get; set; }
When updating my database using the Package Manager Console, a connection table was created in the database. I did it using the following line:update-database -force -verbose
I have an idea about creating a new note. Here you can select a list of employees and add them to the memo. However, populating this junction table does not proceed as planned. I think it has something to do with setting up my repositories. I created MemoRepository and EmployeeRepository.
My controller managing Memo creation looks like this:
MemoController
public class MemoController : Controller
{
private IMemoRepository _memoRepository;
private IEmployeeRepository _employeeRepository;
public MemoController(IMemoRepository memoRepository, IEmployeeRepository employeeRepository) {
_memoRepository = memoRepository;
_employeeRepository = employeeRepository;
}
public ViewResult Create() {
//Initializes MemoCreateViewModel here
return View(model);
}
[HttpPost]
public ActionResult Create(MemoCreateViewModel model) {
if(!ModelState.IsValid)
return RedirectToAction("Create");
Guid employeeId;
List<Guid> employeeIds = new List<Guid>();
foreach (var id in model.SelectedEmployeeIds) {
if (!Guid.TryParse(id, out employeeId)) {
continue;
}
employeeIds.Add(employeeId);
}
var employees = _employeeRepository.GetEmployeesByIds(employeeIds);
model.Memo.Employees = employees.ToList<Employee>();
_memoRepository.SaveMemo(model.Memo);
return RedirectToAction("List");
}
}
MemoRepository
public class EFMemoRepository : IMemoRepository
{
private EFDbContext context;
public EFMemoRepository(EFDbContext _context) {
context = _context;
}
public IQueriable<Memo> Memos {
return context.Memos;
}
public void SaveMemo(Memo memo) {
if(memo.MemoId == Guid.Empty) {
memo.MemoId = Guid.NewGuid();
context.Memos.Add(memo); //error 1 here
} else {
Memo dbEntry = context.Memos.Find(memo.MemoId);
if(dbEntry != null) {
dbEntry.Message = memo.Message;
dbEntry.Employees = memo.Employees;
}
}
context.SaveChanges(); //error 2 here
}
}
Error 1 I get on insert:
An entity object cannot reference multiple instances of IEntityChangeTracker.
Error 2 While updating:
The relationship between the two objects cannot be determined because they are bound to different ObjectContext objects.
How can I solve this, I have read threads about people saying it has to do with using different contexts, others say it has something to do with Attach()
, but I don't know how to resolve this in my application.
Please tell me if you need more information.
Note. I left the code behind to make it easier to read. Code can be added if required of course.
source to share
You get the first error because you are adding a Memo object that is being passed to the context. But the employee object that was added to the memo object inside the controller was retrieved using a different dbContext. To fix this, you must share the db context between the two OR you must explicitly bind the Employee object to the current context.
Option 1: CONTROLLER CODE
[HttpPost]
public ActionResult Create(MemoCreateViewModel model) {
if(!ModelState.IsValid)
return RedirectToAction("Create");
Guid employeeId;
List<Guid> employeeIds = new List<Guid>();
foreach (var id in model.SelectedEmployeeIds) {
if (!Guid.TryParse(id, out employeeId)) {
continue;
}
employeeIds.Add(employeeId);
}
EFDbContext dbContext = new EFDbContext();//Note
var employees = _employeeRepository.GetEmployeesByIds(dbContext, employeeIds);//Note the extra parameter
model.Memo.Employees = employees.ToList<Employee>();
_memoRepository.SaveMemo(dbContext,model.Memo);//Note the extra parameter
return RedirectToAction("List");
}
EFMemoRepository CLASS CLASS:
public void SaveMemo(EFDbContext dbContext, Memo memo)
{
if(memo.MemoId == Guid.Empty)
{
memo.MemoId = Guid.NewGuid();
context.Memos.Add(memo); //error 1 here
} else
{
Memo dbEntry = dbContext.Memos.Find(memo.MemoId);
if(dbEntry != null)
{
dbEntry.Message = memo.Message;
for (int i = 0; i < dbEntry.Employees.Count; i++)/*Please note that if lazy loading is not True then this reference must explicitly be loaded*/
{
dbEntry.Employees.Remove(dbEntry.Employees.First());
}
foreach (var item in memo.Employees)
{
dbEntry.Employees.Add(item);
}
context.Entry(dbEntry).State = EntityState.Modified;
}
}
dbContext.SaveChanges(); //error 2 here
}
OR
OPTION 2:
public void SaveMemo(Memo memo)
{
if(memo.MemoId == Guid.Empty)
{
memo.MemoId = Guid.NewGuid();
context.Memos.Add(memo); //error 1 here
} else
{
Memo dbEntry = context.Memos.Find(memo.MemoId);
if(dbEntry != null)
{
dbEntry.Message = memo.Message;
for (int i = 0; i < dbEntry.Employees.Count; i++)/*Please note that if lazy loading is not True then this reference must explicitly be loaded*/
{
dbEntry.Employees.Remove(dbEntry.Employees.First());
}
foreach (var item in memo.Employees)
{
dbEntry.Employees.Add(item);
}
context.Entry(dbEntry).State = EntityState.Modified;
}
}
context.SaveChanges(); //error 2 here
}
Personally, I would go with option 1 or something along those lines.
Scream if something is unclear
source to share