I'm running into an interesting bug in my ASP.NET MVC 3 application using Entity Framework 4.1 Code First. I have three classes/tables that are joined in sequence. There's an Invitation
that has a reference to a Project
which then has a reference to Company
.
When I load a company and save it everything is fine. Same for projects. However when the invitation gets edited and saved, it wipes out the fields in the company. They're all just blank!
When I edit the project, I need to show some info from the company, so I'm explicitly loading that with .Include(x => x.Company)
. When I edit the invitation though, I don't need the company so I haven't bothered including it.
I'd think that if the object wasn't ever loaded, then there shouldn't be any reason for EF to flag it as edited, right?
Update: After much debugging via commenting out lines of code I've narrowed it down some.
The actual object being cleared was a Contact
object that's referenced by the company. And it wasn't really being cleared so much as a new contact was created in the constructor (so it wouldn't be null for new Companies.)
So I guess that changes my question: Is there a way to have a referenced property set to a default value without breaking EF?
public class InvitationController
{
[HttpPost]
public RedirectToRouteResult AcceptInvitation(int id, int companyId, int projectId, Invitation invitation)
{
// This line triggered the problem by loading a company, without
// eagerly loading the contacts.
CheckAuthorizationEdit(companyId, CommunicationService.GetById(id));
var dbResponse = InvitationService.GetPreviousResponse(companyId, projectId);
dbResponse.WillBid = invitation.WillBid;
InvitationService.Save(dbResponse);
return RedirectToAction("Response", new { id, companyId } );
}
private void CheckAuthorizationEdit(int companyId, Communication communication)
{
var companyIds = communication.DistributionList.Companies.Select(c => c.Id).ToList();
//CheckAuthorization(companyIds);
}
}
public class InvitationService
{
public Invitation GetPreviousResponse(int companyId, int projectId)
{
return (from invitation in _db.Invitations
where invitation.ProjectId == projectId && invitation.SenderCompanyId == companyId
select invitation).SingleOrDefault();
}
public void Save(Invitation invitation)
{
_db.SaveChanges();
}
}
public class Invitation
{
public int Id { get; set; }
public int ProjectId { get; set; }
[ForeignKey("ProjectId")]
public virtual Project Project { get; set; }
// ...
}
public class Project
{
public int Id { get; set; }
public int CompanyId { get; set; }
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
// ...
}
public class Company
{
public Company()
{
MainContact = new Contact();
}
public int Id { get; set; }
public virtual Contact MainContact { get; set; }
// ...
}
public class Contact
{
public int Id { get; set; }
public string AddressLine1 { get; set; }
// ...
}
If I understand right you have something like this:
A simple code like this...
...will indeed create a new empty contact in the database.
The main conclusion I would draw is: Don't instantiate reference navigation properties in the constructor! (Instantiating navigation collections is OK, I think, as long as you leave their content empty. Also instantiating properties of complex types in the constructor is fine because they are not other entities.)
If you want to make sure to create a new contact with a new company, perhaps a static factory method in the
Company
class is the better option:This would also work:
But such a procedure looks really ridiculous.
Edit:
It's by the way automatic change detection which causes the behaviour. This code...
...doesn't create a new contact. It's the change detection working internally in
SaveChanges
Find
which thinks to identify theMainContact
incompany
as a new entity and puts it intoAdded
state into the context.