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 re开发者_运维问答lationship, 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.
精彩评论