Entity Framework Delete Entity without its Dependency - c#

While I am trying to delete my entity I am getting following error in my function;
A relationship from the 'ProjectWebsiteTag_ProjectUser' AssociationSet is
in the 'Deleted' state.
Given multiplicity constraints, a corresponding 'ProjectWebsiteTag_ProjectUser_Target' must also in the 'Deleted' state.
Here is my code to delete;
public bool Delete(int id)
{
try
{
using (ProjectDataContext context = new ProjectDataContext())
{
ProjectWebsiteTag websiteTag = context.WebsiteTags.FirstOrDefault(p => p.WebsiteTagId == id);
context.WebsiteTags.Remove(websiteTag);
int saveChanges = context.SaveChanges();
return saveChanges > 0;
}
}
catch (DbEntityValidationException e)
{
FormattedDbEntityValidationException newException = new FormattedDbEntityValidationException(e);
throw newException;
}
}
Here is my data class;
public class ProjectWebsiteTag
{
public int WebsiteTagId { get; set; }
public ProjectUser ProjectUser { get; set; }
public ProjectWebsite ProjectWebsite { get; set; }
}
My Config Class;
public ProjectWebsiteTagConfiguration()
{
ToTable("ProjectWebsiteTags");
HasKey(p => p.WebsiteTagId);
HasRequired(p => p.ProjectUser).WithRequiredDependent().WillCascadeOnDelete(false);
HasRequired(p => p.ProjectWebsite).WithRequiredDependent().WillCascadeOnDelete(false);
}
It looks like it is trying to delete User record, but I do not want that.
I just want to delete "ProjectWebsiteTag" and that's it.
What I am missing here?

It isn't trying to delete the ProjectUser. It's trying to insert it. ProjectWebsiteTagConfiguration() is saying that the ProjectWebsiteTags table has a ProjectUser foreign key in it. When you call
ProjectWebsiteTag websiteTag = context.WebsiteTags.FirstOrDefault(p => p.WebsiteTagId == id)
websiteTag has a ProjectUser with an empty string for its UserId property. So either the record in the ProjectWebsiteTags table has an empty string for the foreign key, or EF is newing a ProjectUser (with an empty UserId) when you get from the context. Either way, EF isn't aware of the existence of a ProjectUser with an empty string id, so when you call SaveChanges() it tries to add it. It can't because the UserId field is required and empty.

I had to fix my problem by adding one-to-many relationship configuration in my User class.
It comes to a resolution of I need to add relationship hint on both sides of Configuration declarations.
public class ProjectUserConfiguration : EntityTypeConfiguration<ProjectUser>
{
public ProjectUserConfiguration()
{
ToTable("ProjectUsers");
HasKey(p => p.UserId);
HasMany(p=>p.ProjectWebsiteTags).WithRequired(q=>q.ProjectUser).WillCascadeOnDelete(false);
}
}

Related

EF Core 5 One-to-many relationship problem

In this example a user has zero or many bills, one bill can be assigned to one user. Bill can also be created but never assigned.
public class User
{
public int Id{ get; set; }
public List<Bill> bills{ get; set; }
}
public class Bill
{
public int Id { get; set; }
public int userId{ get; set; }
public User user{ get; set; }
}
I've also added this in my DB context configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Bill>()
.HasOne(b => b.user)
.WithMany(u => u.bills)
.HasForeignKey(b => b.userId);
}
I've realized it through a unit of work + repository pattern. In my BillService.cs I would like to have a method that allows me to update/add a bill and assign it to a user.
If the user doesn't exist in DB it should add it. If the user exists it should update it.
I've tried two approaches.
First:
public async Task<void> AddUpdateBill(AddBillModel model){
Bill bill= await unitOfWork.BillRepository.GetByID(model.billId);
if( unitOfWork.UserRepo.GetById(model.userId) == null){
unitOfWork.UserRepo.Insert(model.user);
}else{
unitOfWork.UserRepo.Update(model.user);
}
bill.user = model.user;
unitOfWork.BillRepository.Update(bill);
unitOfWork.Save();
}
Second:
public async Task<void> AddUpdateBill(AddBillModel model)
{
Bill bill= await unitOfWork.BillRepository.GetByID(model.billId);
bill.user = model.user;
unitOfWork.BillRepository.Update(bill);
unitOfWork.Save();
}
In both cases, I've got the problem of duplicated primary-key or entity already tracked.
Which is the best approach or the right way to do it?
EDIT: Sorry, BillRepo and BillRepository are the same class.
public async Task<Bill> GetByID(int id)
{
return await context
.bill
.Include(b => b.user)
.Where(b=> b.id == id)
.FirstOrDefaultAsync();
}
public void Update(Bill bill)
{
context.Entry(bill).CurrentValues.SetValues(bill);
}
The first approach seems more right (to me).
First of all, comply with the naming rules: all properties must begin with upper case characters. "Bills", "UserId", "User" in your case.
if( unitOfWork.UserRepo.GetById(model.userId) == null){
unitOfWork.UserRepo.Insert(model.user);
}else{
unitOfWork.UserRepo.Update(model.user);
}
bill.user = model.user;
You don't need it here
bill.user = model.user;
because you have just attached your entity to context and updated/inserted it.
Also, don't forget to format your code, for example https://learn.microsoft.com/ru-ru/dotnet/csharp/programming-guide/inside-a-program/coding-conventions
It would be useful to consider inserting/updating your entities not straight from the model, something like:
if( unitOfWork.UserRepo.GetById(model.userId) == null){
var user = new User
{
//set properties
};
unitOfWork.UserRepo.Insert(user);
unitOfWork.Save();
bill.userId = user.Id;
}
Here:
if( unitOfWork.UserRepo.GetById(model.userId) == null){...
you retrieve the User from UserRepo but don't assign it to any variable. This may cause the exception stating that there are multiple tracked entities with the same ID.
Try to retrieve (including bills) or create the User entity and add the new bill in there. Then insert User entity to DB (if it was not there) and simply Save your work.

Adding an entity in Entity Framework with nested entities

I am running into an interesting issue when trying to add an entity to the database with Entity Framework. When I try to add a new exception to the database, I run into the following error:
"Cannot insert explicit value for identity column in table 'Notification' when identity_insert is set to off"
I am guessing this error is caused due to the Notification entity already having an Identifier (Id property). However, my goal is not to store the Notification entity. It just so happens that my NotificationException entity has a reference to an existing Notification entity.
How can I update my NotificationException entities properly without running into this problem? Actually turning the identity_insert off does not seem like a viable solution.
My two model classes:
public class Notification
{
// Primary Key
public long Id { get; set; }
// Properties
public bool IsSent { get; set; }
public bool IsExpired { get; set; }
public int RetryCount { get; set; }
public int RetryTime { get; set; }
}
public class NotificationException
{
// Primary Key
public long Id { get; set; }
// Properties
public int Timestamp { get; set; }
public string Exception { get; set; }
// Foreign Keys
public long NotificationId { get; set; }
// Navigation Properties
public virtual Notification Notification { get; set; }
}
Entity Configuration with Fluent API:
private void ConfigureNotificationEntity(EntityTypeBuilder<Notification> builder)
{
builder.ToTable("Notifications");
builder.HasKey(i => i.Id);
builder.Property(i => i.Id)
.IsRequired()
.ValueGeneratedOnAdd();
builder.HasMany(n => n.Exceptions)
.WithOne(e => e.Notification)
.OnDelete(DeleteBehavior.Cascade);
}
private void ConfigureNotificationExceptionEntity(EntityTypeBuilder<NotificationException> builder)
{
builder.ToTable("NotificationExceptions");
builder.HasKey(i => i.Id);
builder.Property(i => i.Id)
.IsRequired()
.ValueGeneratedOnAdd();
builder.HasOne(i => i.Notification)
.WithMany(j => j.Exceptions);
}
The main problem:
public async Task<NotificationException> Add (NotificationException item)
{
_context.NotificationExceptions.Add(item);
await _context.SaveChangesAsync();
return item;
}
As soon as the _context.SaveChangesAsync(); is called, the error mentioned above is thrown.
///Edit
I tested this issue with different objects as well. If the entity has no nested entities, then storing them works just fine. The issue is quite likely with the already known ID of the nested entity.
So it turns out in 2016, EF Core changed the behaviour of the Attach method from EF6: https://github.com/dotnet/efcore/issues/4424
to wit:
Add: Adds every reachable entity that is not already tracked
Attach: Attaches every reachable entity, except where a reachable entity has a store generated key and no key value is assigned, these will be marked as added.
Therefore, the solution is just to change
_context.NotificationExceptions.Add(item); to
_context.NotificationExceptions.Attach(item);
This will Add the new NotificationException pushed without a key; however the child Notification, with a declared key, will be attached as 'Unchanged'.
The problem looks like an issue with how you add notification object to your notificationException object.
Change your Add function like so:
public async Task<NotificationException> Add (NotificationException item)
{
var notification = _context.Notifications.Find( x => x.Id == yourNotificationId);
item.Notification = notification;
_context.NotificationExceptions.Add(item);
await _context.SaveChangesAsync();
return item;
}
The trick here is we got notification entity from same context that we will add notificationException object.
Check this link for more info
Found a fix!
var exception = new NotificationException()
{
Exception = "Very serious exception!",
QueuedNotificationId = notification.Id,// <-- setting the foreign key
Timestamp = 420
};
Instead of referencing the entire Notification object within the Exception entity, I just set the foreign key. This way I can store the entity just fine. Still a bit silly how EF doesn't automatically recognize the Notification entity already exists.

How to manually set entity primary key in Entity Framework code first database?

Well, I have the following model structure: I have one class - DatabaseEntity which is basically
public class DatabaseEntity
{
public int Id { get; set; }
}
so each entity like product, category etc will inherit DatabaseEntity and have Id property. Also I have typical EntityFramework repository class with InsertOrUpdate method:
private readonly DbContext _database;
public void InsertOrUpdate<TObject>(TObject entity) where TObject : DatabaseEntity
{
if(entity.Id == default(int))
{
// New entity
DbSet<TObject>().Add(entity);
}
else
{
// Existing entity
_database.Entry(entity).State = EntityState.Modified;
}
_database.SaveChanges();
}
Then I download from eBay via eBay api list of categoies I have to add to database. Basically category is:
public class EbayCategory : DatabaseEntity
{
// It has Id since it inherits DatabaseEntity
public string Name { get; set; }
// ... some other properties
}
But, the problem is, when I download those categories I download and their Id properties, which, of course, already have values. And when I try to save them to database like:
public void UpdateCategories(IEnumerable<EbayCategory> newCategories)
{
foreach (var newCategory in newCategories)
{
_repository.InsertOrUpdate(newCategory);
}
}
I face some issues... First of all, entity.Id != default(int) because it has value, so repository tries to update this entity, instead of adding, but it is not in the database or context so it throws the following exception:
System.Data.Entity.Infrastructure.DbUpdateConcurencyException
"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries."
... because it thinks that someone else deleted entity which I am trying to update. How can I save this InsertOrUpdate logic, since a lot of projects are based on it, and be able to add items (EbayCategories) with primary key (Id) to database and then update/delete them like other entities without discarding EbayCategory.Id value?
To allow you to manually generate Ids you need a class that has a manually generated ID - so it cannot inherit from DatabaseEntity
public class EbayCategory
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
// ... some other properties
}
Now you will need a different InsertOrUpdate to handle entities that have manually generated keys:
public void InsertOrUpdate(EbayCategory entity)
{
if(Find(entity.ID == null)
{
// New entity
DbSet<EbayCategory>().Add(entity);
}
else
{
// Existing entity
_database.Entry(entity).State = EntityState.Modified;
}
_database.SaveChanges();
}
Colin's answer above quite correctly shows how to achieve this setting using data annotations.
But in the presented problem the entity is a subclass so you can't add the annotation without changing the entity class.
There is an alternative configuration method: Fluent Configuration. Here's my example using an EntityTypeConfiguration class:
public class LookupSchoolsConfiguration : EntityTypeConfiguration<LookupSchools>
{
public LookupSchoolsConfiguration()
{
Property(l => l.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
}
}
You can also add configuration directly to the modelBuilder as per this post: https://stackoverflow.com/a/4999894/486028

Can EF automatically delete data that is orphaned, where the parent is not deleted?

For an application using Code First EF 5 beta I have:
public class ParentObject
{
public int Id {get; set;}
public virtual List<ChildObject> ChildObjects {get; set;}
//Other members
}
and
public class ChildObject
{
public int Id {get; set;}
public int ParentObjectId {get; set;}
//Other members
}
The relevant CRUD operations are performed by repositories, where necessary.
In
OnModelCreating(DbModelBuilder modelBuilder)
I have set them up:
modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects)
.WithOptional()
.HasForeignKey(c => c.ParentObjectId)
.WillCascadeOnDelete();
So if a ParentObject is deleted, its ChildObjects are too.
However, if I run:
parentObject.ChildObjects.Clear();
_parentObjectRepository.SaveChanges(); //this repository uses the context
I get the exception:
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 makes sense as the definition of the entities includes the foreign key constraint which is being broken.
Can I configure the entity to "clear itself up" when it gets orphaned or must I manually remove these ChildObjects from the context (in this case using a ChildObjectRepository).
It is actually supported but only when you use Identifying relation. It works with code first as well. You just need to define complex key for your ChildObject containing both Id and ParentObjectId:
modelBuilder.Entity<ChildObject>()
.HasKey(c => new {c.Id, c.ParentObjectId});
Because defining such key will remove default convention for auto incremented Id you must redefine it manually:
modelBuilder.Entity<ChildObject>()
.Property(c => c.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Now calling to parentObject.ChildObjects.Clear() deletes dependent objects.
Btw. your relation mapping should use WithRequired to follow your real classes because if FK is not nullable, it is not optional:
modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects)
.WithRequired()
.HasForeignKey(c => c.ParentObjectId)
.WillCascadeOnDelete();
Update:
I found a way that doesn't need to add navigational properties from the child to the parent entity or to set up a complex key.
It's based on this article which uses the ObjectStateManager to find the deleted entities.
With a list ObjectStateEntry in hand, we can find a pair of EntityKey from each, which represents the relationship that was deleted.
At this point, I couldn't find any indication of which one had to be deleted. And contrary to the article's example, simply picking the second one would get the parent deleted in cases where the child had a navigation property back to the parent. So, in order to fix that, I track which types should be handled with the class OrphansToHandle.
The Model:
public class ParentObject
{
public int Id { get; set; }
public virtual ICollection<ChildObject> ChildObjects { get; set; }
public ParentObject()
{
ChildObjects = new List<ChildObject>();
}
}
public class ChildObject
{
public int Id { get; set; }
}
The other classes:
public class MyContext : DbContext
{
private readonly OrphansToHandle OrphansToHandle;
public DbSet<ParentObject> ParentObject { get; set; }
public MyContext()
{
OrphansToHandle = new OrphansToHandle();
OrphansToHandle.Add<ChildObject, ParentObject>();
}
public override int SaveChanges()
{
HandleOrphans();
return base.SaveChanges();
}
private void HandleOrphans()
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
objectContext.DetectChanges();
var deletedThings = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).ToList();
foreach (var deletedThing in deletedThings)
{
if (deletedThing.IsRelationship)
{
var entityToDelete = IdentifyEntityToDelete(objectContext, deletedThing);
if (entityToDelete != null)
{
objectContext.DeleteObject(entityToDelete);
}
}
}
}
private object IdentifyEntityToDelete(ObjectContext objectContext, ObjectStateEntry deletedThing)
{
// The order is not guaranteed, we have to find which one has to be deleted
var entityKeyOne = objectContext.GetObjectByKey((EntityKey)deletedThing.OriginalValues[0]);
var entityKeyTwo = objectContext.GetObjectByKey((EntityKey)deletedThing.OriginalValues[1]);
foreach (var item in OrphansToHandle.List)
{
if (IsInstanceOf(entityKeyOne, item.ChildToDelete) && IsInstanceOf(entityKeyTwo, item.Parent))
{
return entityKeyOne;
}
if (IsInstanceOf(entityKeyOne, item.Parent) && IsInstanceOf(entityKeyTwo, item.ChildToDelete))
{
return entityKeyTwo;
}
}
return null;
}
private bool IsInstanceOf(object obj, Type type)
{
// Sometimes it's a plain class, sometimes it's a DynamicProxy, we check for both.
return
type == obj.GetType() ||
(
obj.GetType().Namespace == "System.Data.Entity.DynamicProxies" &&
type == obj.GetType().BaseType
);
}
}
public class OrphansToHandle
{
public IList<EntityPairDto> List { get; private set; }
public OrphansToHandle()
{
List = new List<EntityPairDto>();
}
public void Add<TChildObjectToDelete, TParentObject>()
{
List.Add(new EntityPairDto() { ChildToDelete = typeof(TChildObjectToDelete), Parent = typeof(TParentObject) });
}
}
public class EntityPairDto
{
public Type ChildToDelete { get; set; }
public Type Parent { get; set; }
}
Original Answer
To solve this problem without setting up a complex key, you can override the SaveChanges of your DbContext, but then use ChangeTracker to avoid accessing the database in order to find orphan objects.
First add a navigation property to the ChildObject (you can keep int ParentObjectId property if you want, it works either way):
public class ParentObject
{
public int Id { get; set; }
public virtual List<ChildObject> ChildObjects { get; set; }
}
public class ChildObject
{
public int Id { get; set; }
public virtual ParentObject ParentObject { get; set; }
}
Then look for orphan objects using ChangeTracker:
public class MyContext : DbContext
{
//...
public override int SaveChanges()
{
HandleOrphans();
return base.SaveChanges();
}
private void HandleOrphans()
{
var orphanedEntities =
ChangeTracker.Entries()
.Where(x => x.Entity.GetType().BaseType == typeof(ChildObject))
.Select(x => ((ChildObject)x.Entity))
.Where(x => x.ParentObject == null)
.ToList();
Set<ChildObject>().RemoveRange(orphanedEntities);
}
}
Your configuration becomes:
modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects)
.WithRequired(c => c.ParentObject)
.WillCascadeOnDelete();
I did a simple speed test iterating 10.000 times. With HandleOrphans() enabled it took 1:01.443 min to complete, with it disabled it was 0:59.326 min (both are an average of three runs). Test code below.
using (var context = new MyContext())
{
var parentObject = context.ParentObject.Find(1);
parentObject.ChildObjects.Add(new ChildObject());
context.SaveChanges();
}
using (var context = new MyContext())
{
var parentObject = context.ParentObject.Find(1);
parentObject.ChildObjects.Clear();
context.SaveChanges();
}
Want to share another .net ef core solution that worked for me, may be somebody will find it usefull.
I had a child table with two foreign keys (either or), so the accepted solution didn't work for me. Based on the answer by Marcos Dimitrio I came up with the following:
In my custom DbContext:
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
var modifiedEntities = this.ChangeTracker.Entries().Where(c => c.State == EntityState.Modified);
foreach (var entityEntry in modifiedEntities)
{
if (entityEntry.Entity is ChildObject)
{
var fkProperty = entityEntry.Property(nameof(ChildObject.ParentObjectId));
if (fkProperty.IsModified && fkProperty.CurrentValue == null && fkProperty.OriginalValue != null)
{
// Checked if FK was set to NULL
entityEntry.State = EntityState.Deleted;
}
}
}
return await base.SaveChangesAsync(cancellationToken);
}
In EF Core, it can be done by Delete Orphans.
Like this:
dbContext.Children.Clear();
This is my generic solution for Entity Framework 6.4.4, without knowledge of the particular schema.
Note that I start my search for orphan entities from modified entity entries, as in my case I could not find anything searching for deleted relationship entries like other answers suggest.
The logic behind the approach is that an entity removed from a collection of a required relationship will have its foreign key updated to null by the Entity Framework. So we search for all modified entities which have at least one relationship to an end with multiplicity 'One' but having the foreign key set to null.
Add this method to your DbContext subclass. You could override the SaveChanges / SaveChangesAsync methods to call this method automatically.
public void DeleteOrphanEntries()
{
this.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
var orphanEntityEntries =
from entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified)
where !entry.IsRelationship
let relationshipManager = entry.RelationshipManager
let orphanRelatedEnds = from relatedEnd in relationshipManager.GetAllRelatedEnds().OfType<EntityReference>()
where relatedEnd.EntityKey == null // No foreign key...
let associationSet = (AssociationSet)relatedEnd.RelationshipSet
let associationEndMembers = from associationSetEnd in associationSet.AssociationSetEnds
where associationSetEnd.EntitySet != entry.EntitySet // ... not the end pointing to the entry
select associationSetEnd.CorrespondingAssociationEndMember
where associationEndMembers.Any(e => e.RelationshipMultiplicity == RelationshipMultiplicity.One) // ..but foreign key required.
select relatedEnd
where orphanRelatedEnds.Any()
select entry;
foreach (var orphanEntityEntry in orphanEntityEntries)
{
orphanEntityEntry.Delete();
}
}
Yes. The following works in EF Core:
Make sure you set the cascade behavior to Cascade like so:
entity.HasOne(d => d.Parent)
.WithMany(p => p.Children)
.HasForeignKey(d => d.ParentId)
.OnDelete(DeleteBehavior.Cascade);
Then set the Parent property to be null in all the child entities that are to be deleted like so:
var childrenToBeRemoved = parent.Children.Where(filter);
foreach(var child in childrenToBeRemoved)
{
child.Parent = null;
}
Now, context.SaveAsync() should delete all the orphaned children entities.
This is not something that is supported automatically by EF right now. You can do it by overriding SaveChanges in your context and manually deleting an child objects that no longer have a parent. The code would be something like this:
public override int SaveChanges()
{
foreach (var bar in Bars.Local.ToList())
{
if (bar.Foo == null)
{
Bars.Remove(bar);
}
}
return base.SaveChanges();
}

Remove Dependent Entity When Relationship Deleted

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 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.
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.

Categories