I have a Parent entity with a 0-to-Many relationship to a Child entity. When I delete a parent I want it to automatically cascade delete all the attached children. Attempting to do so gives the following 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.
I do not understand the message talking about setting a null reference. Because of the cascade delete the children will be removed so there is no need to set any children to have null references.
My two simple entities are defined as...
public class Parent
{
[Key]
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
[Key]
public int Id { get; set; }
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
With the following mapping...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Child>()
.HasRequired(x => x.Parent)
.WithMany(x => x.Children)
.HasForeignKey(x => x.ParentId)
.WillCascadeOnDelete(true);
}
Looking at the generated database, it does indeed mark the foreign key relationship as cascade on delete. So the database schema looks fine. The actual code that throws the error...
Parent p = context.Parents.Find(id);
context.Entry<Parent>(p).State = System.Data.Entity.EntityState.Deleted;
context.SaveChanges();
Any ideas?
Your error is being generated by Entity Framework, not by your database.
The problem is that you're using context.Entry<Parent>(p).State = EntityState.Deleted instead of context.Parents.Remove(p). The main difference is that calling Remove on the parent handles setting the entity state to deleted for any children with a required relationship that are loaded into the context. State = EntityState.Deleted does not.
In your case, you probably have some related Child entities loaded into the context and EF is complaining about the orphaned children. If you didn't have any children loaded, the DELETE statement would be sent to the database, and the database would handle the cascade delete normally.
Using DbSet.Remove would be preferable.
See this for more details:
Delete parent with children in one to many relationship
You code matches this well so I think you configured it correctly: https://msdn.microsoft.com/en-us/data/jj591620.aspx#CascadeDelete
I don't know why this doesn't work, cascade has always worked for me so far. I suspect some configuration. I always disable lazy loading, proxy generation and validation so that could be some this to look at.
Here are some things to try:
1) Can you delete the item manually from the database and does the cascade work then?
2) Is this a sql error or an "app" error? I suspect it's an app error an that EF validation is somehow triggering. If it's from the db the exception should and SqlException. I usually check with Sql Profiler if the command get sent too.
3) If it's an app error try disabling EF's validation and see what happens.
ctx.Configuration.ValidateOnSaveEnabled = false;
4) When you use find does that load the children (might do if it's found in the local context) or does it have lazy loading enabled. Setting the state is recursive if I remember correctly and that could possibly mess up some things. You can check in the changetracker how many entities that are marked for deletion.
Related
I'm having a hard time deleting an object tree. My model doesn't use any kind of built-in cascade deletion mechanism, so I have to perform the explicit deletion of each of the related entities.
The entity I want to delete has 3 levels of indirection (navigation properties)
class Parent
{
public ICollection<Child> Children { get; set; }
}
class Child
{
public ICollection<Grandchild> Grandchildren { get; set; }
}
public class Grandchild
{
}
my DbContext is
public class Context
{
DbSet<Parent> Root {get; set;}
DbSet<Grandchild> Grandchildren {get; set;}
}
Please, notice that the context doesn't expose a DbSet for the class Children.
So, what's the correct way to delete everything under a Parent?
First you need to make sure, Entity Framework has a Foreign Key.
Then you should be able to cascade delete:
Cascade delete automatically deletes dependent records or sets null to
ForeignKey columns when the parent record is deleted in the database.
Cascade delete is enabled by default in Entity Framework for all types
of relationships such as one-to-one, one-to-many and many-to-many.
So the following code should remove every children
var parent = _dbContext.Single(predicate)
_dbContext.Remove(parent);
_dbContext.SaveChanges();
I guess it will be enough to remove DbSet<> declarations you dont need and then migrate the database.
PowerShell:
dotnet ef migrations add [name]
dotnet ef database update
Edit: above applies if you used code-first approach.
I have a DbContext with many classes/entities targeting a Firebird database. I used to have an entity named DinnerPasses, which was declared (as all others) using Code First:
public class FreezedOrdersContext : DbContext
{
public FreezedOrdersContext()
: base("name=ConnectionString")
{
}
// The deleted entity... (NOTE: THIS IS NO LONGER IN THE FILE...)
public virtual DbSet<DinnerPass> DinnerPasses { get; set; }
public virtual DbSet<FirebirdFreezedSalesOrder> FreezedSalesOrders { get; set; }
// Other entities, removed for clarity...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
Database.SetInitializer<FreezedOrdersContext>(null);
// Some of the lines still in the file...
modelBuilder.Entity<FirebirdFreezedSalesOrder>().HasKey(p => new { p.ORDERTYPE, p.CUSTOMERTYPE, p.TABLEID, p.SUBORDERNUMBER });
modelBuilder.Entity<FirebirdFreezedSalesOrder>().ToTable("FREEZEDSALESORDER");
// NOTE: This 2 lines are NO LONGER in the file...
modelBuilder.Entity<DinnerPass>().HasKey(p => p.NUMBER);
modelBuilder.Entity<DinnerPass>().ToTable("GENERALINFO");
}
}
The DinnerPasses entity was deleted. Now, reading any of the other entities goes fine, but when I try to save some other entities into the database db.SaveChanges();, I get the following exception:
"Dynamic SQL Error SQL error code = -204 Table unknown DinnerPasses At line 6, column 8"
System.Exception {FirebirdSql.Data.FirebirdClient.FbException}
So, I guess that EF created some model file somewhere that allows it "remember" that entity (and since the line modelBuilder.Entity<DinnerPass>().ToTable("GENERALINFO"); is no longer part of the file, it is trying to get the DinnerPass entity from a table of the same name).
How can I fix this? Where are the model files for this? They are certainly not found anywhere in my project folder...
Thanks in advance.
You need to remove the DbSet and the entity class from your context entirely (including references from other entities!). If you don't specify any additional metadata, then Entity Framework will assume a tablename derived from the entity classname.
Ok, so I found out what happened:
I removed the DinnerPasses entity and replaced it with a new entity called GeneralInfo that was more appropriate (it gets more info from the table, instead of my initial needs that was the Dinner Pass information within that table).
Here is the issue: When I created the new GeneralInfo class, I copied a property from the old DinnerPass class (called FormattedDinnerPasses) into the new GeneralInfo class/entity. When I changed that property to a method, it no longer tried to access the "DinnerPasses" table.
So, I think the problem was having a property that didn't have a backing variable (the property just did a bunch of calculations and returned a list of formatted items). Maybe you can have one but need to give it an attribute indicating that it must be ignored when Entity Framework is binding to the table...
I'm using EF Code first. a simple model:
item { public int Id {set; get;},... ,ICollection<ItemImages> {set; get;} }
itemImages {
public int Id {set; get; },
public int ItemId {set; get; }
, ... ,
public Item Item {set; get; }
}
ItemConfig:EntityTypeConfiguration<Item>
{
//some config statement;
//...
// mark child delete when parent delete: waterfall delete.
HasRequired(rs => rs.ItemCat).WithMany(rs => rs.Items).HasForeignKey(rs => rs.ItemCatId).WillCascadeOnDelete(true);
}
when delete entity by Remove(), it delete item and related child (item images records) well.
_db.Item.Remove(DeleteThisObj);
_db.SaveChanges();
but when mark it to delete:
_db.Entry(DeleteThisObj).State = EntityState.Deleted;
_db.SaveChanges();
get error:
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.
If you really want to use Deleted, you'd have to make your foreign keys nullable, but then you'd end up with orphaned records (which is one of the main reasons you shouldn't be doing that in the first place). So just use Remove()
ObjectContext.DeleteObject(entity) marks the entity as Deleted in the context. (It's EntityState is Deleted after that.) If you call SaveChanges afterwards EF sends a SQL DELETE statement to the database. If no referential constraints in the database are violated the entity will be deleted, otherwise an exception is thrown.
EntityCollection.Remove(childEntity) marks the relationship between parent and childEntity as Deleted. If the childEntity itself is deleted from the database and what exactly happens when you call SaveChanges depends on the kind of relationship between the two:
If the relationship is optional, i.e. the foreign key that refers from the child to the parent in the database allows NULL values, this foreign will be set to null and if you call SaveChanges this NULL value for the childEntity will be written to the database (i.e. the relationship between the two is removed). This happens with a SQL UPDATE statement. No DELETE statement occurs.
If the relationship is required (the FK doesn't allow NULL values) and the relationship is not identifying (which means that the foreign key is not part of the child's (composite) primary key) you have to either add the child to another parent or you have to explicitly delete the child (with DeleteObject then). If you don't do any of these a referential constraint is violated and EF will throw an exception when you call SaveChanges - the infamous "The relationship could not be changed because one or more of the foreign-key properties is non-nullable" exception or similar.
If the relationship is identifying (it's necessarily required then because any part of the primary key cannot be NULL) EF will mark the childEntity as Deleted as well. If you call SaveChanges a SQL DELETE statement will be sent to the database. If no other referential constraints in the database are violated the entity will be deleted, otherwise an exception is thrown.
A thing worth noting is that setting .State = EntityState.Deleted does not trigger automatically detected change.
-----edit-----
I have my code working now. I tried making the relationship one-to-many and it works now by using:
newInspection.Sites.Add(newSite)
The Unique Key constraint is still present in the database, so I'm not completely comfortable with this as being the "answer" since it's more of a work around. I have no idea how to make the one-to-one work, as every time I have tried it (with other tables too for testing purposes) it always gives me this error.
----/edit-----
I am using EF 6 Code First for an application I am developing. I used the EF 6.1 tools to reverse engineer the code first model. I am running into a problem with a 1-to-1 relationship when trying to add new items to the database.
Here is the object that's causing a problem:
[Table("childTable")]
public partial class Site
{
[Key]
public int siteID{ get; set; }
public int inspectionID { get; set; }
...
public virtual Inspection inspection { get; set; }
}
The main "inspection" class has a 1-to-1 relationship with the site, and the class is organized like this:
[Table("someTable")]
public partial class Inspection
{
[Key]
public int inspectionID { get; set; }
...
public virtual Site site { get; set; }
}
The context defines this:
modelBuilder.Entity<Inspection>()
.HasOptional(e => e.site)
.WithRequired(e => e.inspection);
I am creating a new "Site" object and setting everything in it except the "siteID" and "inspectionID" properies -- the primary and foreign key respectively. I am adding it to a new "Inspection" object as a navigation property, then adding the "Inspection" to the context and trying to save:
Inspection newInspection = new Inspection
{
...
site = newSite; // Constructed earlier, no explicit ID. ID = 0 if checked
};
using (var db = new Context())
{
db.Inspections.Add(newInspection);
db.SaveChanges();
}
When I call the SaveChanges() I get the "Cannot insert explicit value for identity column in table '--------' when IDENTITY_INSERT is set to OFF."
I cannot understand why it is doing this, so I used db.Database.Log to check out the SQL being generated, and it is trying to pass an ID for the siteID after the "Inspection" insert. That doesn't make sense to me, because if I check the siteID before calling SaveChanges() the ID is 0, as a "new" one should be. However, it is actually trying to insert a number, like 16. I am unsure where it is getting the number. I thought when adding a new item to the context (i.e. db.Inspections.Add()) that it flagged everything in there as new and treated it as such during insert.
I have no idea why it is trying to insert the ID, but it appears to do this for any navigation property that is 1-to-1. That requires setting the navigation property explicitly, as opposed to using the .Add() method. 1-to-many have always worked fine for me (and do in this Context).
Does anyone know why my DBContext is trying to pass the ID?
Entities that have a 1 to 1 relationship should have the same value in the primary key. That means that the primary key in the dependent should also be a foreign key to the principal, and should not be an identity field.
You should change your tables in line with that requirement so that EF can insert the Inspection object then take its new ID and insert that value into the Sites table as the foreign key/primary key.
EF will add the foreign key constraint when you migrate back to a one-to-one but you will need to add sql to the migration to remove the Identity because EF can't do that (yet)
References:
What does principal end of an association means in 1:1 relationship in Entity framework
Do I define a relationship between two entities on the dependent or the principal?
Configuring a Required-to-Optional Relationship (One-to-Zero-or-One)
I'm trying to implement an Undo / Redo feature based on entity framework with POCO entities. Therefor after each change that I want to track I call the ChangeTracker for any modification to entities an I call the ObjectStateManger for changes to relationships (i.e. navigation properties). I store all the changes with the current and previous values to be able to go back and forth in the history.
My problem now is that for entities with a navigation property and a corresponding foreign key these two values are not properly synced if the referenced entity was newly added to the DbContext.
To clarify: Say I have these classes:
public class EntityFilter
{
[Key]
public Guid ID { get; set; }
public virtual ICollection<EntityConnection> IsSource { get; set; }
public virtual ICollection<EntityConnection> IsSink { get; set; }
//other stuff
}
public class EntityConnection
{
[Key]
public Guid SinkFilterID { get; set; }
[ForeignKey("SinkFilterID")]
public virtual EntityFilter Sink { get; set; }
public Guid SourceFilterID { get; set; }
[ForeignKey("SourceFilterID")]
public virtual EntityFilter Source { get; set; }
//more stuff
}
EntityConnection is basically a many-to-many relationship between filters, but it actually contains more fields which is why I cannot get rid of it. Also I want to be as general as possible and not depend on our actual data model.
The problem arises if I add a new filter and then connect it to an existing filter (could also be a new one). Undoing the connection is still ok, but when I try to redo my program will crash. I can see in the restored connection that the foreign key SinkFilterID has the correct value but Sink is null (the same might happen for source, depending on the direction of the connection). Manually calling DetectChanges makes no difference. Adding a connection between two existing filters (i.e. they are already stored in the db before) is no problem.
The detected changes for a new connection of this type only contain entity changes from the ChangeTracker and no relationship changes from the ObjectStateManger. I guess this is because the relationship is already handled by the foreign key, which is included in the properties from PreviousValues.
I've read that entities in the EntityState.Added state get temporary keys and that change tracking for them is not fully supported. Can I get this to work somehow?
I've tried to check with the MetadataWorkspace if my updated entities have a foreign key and a corresponding navigation property and in that case update it manually via reflection, but I'm not sure what data I actually have to check.
Is there a way to keep foreign keys and navigation properties to added entities in sync? Or do you have any suggestions what I might try?
Thank you very much.
Here is what I ended up with:
I keep a separate list of all the added entities. Then when I have to restore a navigation property that is backed by a foreign key I search that list and manually set the navigation property. The hardest part was to figure out how to check in the data model if this fixup was at all needed and to find the name of the corresponding property.
The overall system still has some flaws for maximum generality but it works quite well for what we need.