1

I have an Order table that contains a nullable foreign key to another table called Product (each order can only map to one product). Each order will only point to one product. The relation is configured by Fluent API as shown here (in Entity Framework 6.1.3):

public class OrderConfiguration : EntityTypeConfiguration<Order> { public OrderConfiguration() { // the same issue still happens if switched to 'HasRequired' HasOptional(o => o.Product) .WithMany() .HasForeignKey(o => o.ProductKey) .WillCascadeOnDelete(false); } } 

Now, when I call Remove (or RemoveRange) on Order, why does it set the foreign key ProductKey in Order to null? What is causing this behavior to happen and how do I prevent this?

As a side note, please correct me if I am wrong, but in my understanding "Cascade On Delete" shouldn't have anything to do with the issue here (setting WillCascadeOnDelete(false) doesn't do anything here, nor does Conventions.Remove<OneToManyCascadeDeleteConvention>() do anything here). It is meant to cascade to dependent entities when deleting the principal entity, not the other way around.

Why this behavior exists? Do I really have to copy the entity or store the foreign keys' value before delete?

public Order RemoveOrder(int orderID) { using (var context = new MyDBContext()) { var order = context.Order .Where(o => o.OrderID == orderID) .FirstOrDefault(); if (order == null) return null; // Here foreign key `productKey` on `order` is not null yet var removed = context.Orders.Remove(order); // Now `productKey` on both 'order' and 'removed' are null context.SaveChanges(); return removed; } } 

These are the entity clases if needed! (There is no other configuration for this navigation property except the one showed above. There is also no ON DELETE CASCADE or any constrain set in the DB as far as I can see.)

public class Order { [Key] public int OrderID { get; set; } public int? ProductKey { get; set; } public string PONumber { get; set; } public virtual Product Product {get; set; } } public class Product { [Key] public int ProductKey { get; set; } public string ProductName { get; set; } } 

For context I was refactoring a class that were originally using TableAdapters. In some cases it follows this pattern (retreive the data, remove, and return the retreived data by using OUTPUT DELETED.*). These bug aren't caught by tests and causes some methods to always return false when comparing to that field, and GridView link links to nowhere.

17
  • So, to be clear, after context.Orders.Remove(order); and saving changes, the order is not removed, only its ProductKey is set to null? Could you share the Oder and Product classes to make this reproducible? Commented Jun 18 at 20:22
  • Check this documentation, you may need to save the state of the entity before removing it: learn.microsoft.com/en-us/dotnet/api/… Commented Jun 18 at 20:27
  • @GertArnold No the orders is removed in the database after calling SaveChanges. The Remove() method returns the removed entity, which is further needed. And yes, let me provide the entity class removing all unrelated fields! Commented Jun 18 at 21:15
  • @phiwo sorry I didn't find it in the document. Do you mean the EntityState of the entity? If so, I'm not sure how that will help. Or do you mean to clone the whole entity? Commented Jun 18 at 21:20
  • 1
    Hmm, i guess, it looks a bit fishy to me, shouldn't you be able to just say one order has one product by way of property, if you don't care about the other way around. Commented Jun 18 at 22:34

1 Answer 1

0

I still don't know the purpose of this behavior, but after digging into the source code, I figured that as long as we are calling remove, this behavior will not be avoidable.

Remove calls an internal delete method, which calls NullAllForeignKeys(). Since doFixup is forced to be true when calling Remove (under EntityEntry, which is a sealed class), I can't think of a way to overwrite this behavior.

internal void Delete(bool doFixup) { this.ValidateState(); if (this.IsKeyEntry) throw ...... if (doFixup && this.State != EntityState.Deleted) { this.RelationshipManager.NullAllFKsInDependentsForWhichThisIsThePrincipal(); this.NullAllForeignKeys(); this.FixupRelationships(); } ....... } 

The solution for me right now is to have an extension method that copies the values, kind of similar to what Gert Arnold suggested in the comment. However, I can't tell what are foriegn keys and what are not, and I don't think it is expensive to just copy the all the fields. Hence, I just define an extension method that copies all fields for my convinience.

// TDestination is the type that defines all the non-navigation fields. public TDestination CopyInto<TDestination>(TDestination destination) where TDestination : TType { var properties = typeof(TType).GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (var prop in properties) { if (prop.CanRead && prop.CanWrite) { var value = prop.GetValue(_source); prop.SetValue(destination, value); } } return destination; } 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.