Breeze Delete Parent with Guid PK and Children are Modified - angularjs

I am trying to delete both the parent and the children entities in an Angular/Breeze application.
The backing store is a code-first Entity Framework.
The entities are as follows:
public class Ingredient
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public virtual ICollection<IngredientDescription> Descriptions { get; set; }
}
public class IngredientDescription
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public Guid IngredientId { get; set; }
[ForeignKey("IngredientId")]
public virtual Ingredient Ingredient { get; set; }
public string Description { get; set; }
[Required]
public int Culture { get; set; }
}
When I delete the Ingredient, I need to delete the IngredientDescription. I have tried doing it both ways, where I delete either first (children, then parent or parent, then children).
Whenever I delete the Ingredient (parent), Breeze is setting the children IngredientDescription.IngredientId to Guid.Empty or {0000-0000-...}. This is causing the children entities to a state of Modified (as opposed to the Deleted I had already set them).
I have tried everything I can think of to get this to work from the clientside code. I have gotten it to work by previewing the changes in the controller and re-marking it as Deleted. But, I'd like to just get it to work from the clientside.
I don't have to have cascading deletes, just if I delete it, to prevent Breeze from modifying it from Deleted to Modified.
My clientside function is as below:
function removeIngredient(ingredient) {
var descriptions = ingredient.descriptions;
for (var d = 0; d < ingredient.descriptions.length; d++) {
var thisDescription = ingredient.descriptions[d];
thisDescription.entityAspect.setDeleted();
}
ingredient.entityAspect.setDeleted();
}
Any thoughts on how I can keep Breeze from marking the child objects as Modified after I have already marked them as Deleted?
Below is an image of where the child's EntityState is being set to modified from the removeFromRelations call.

I believe the problem was in the loop that deletes the children
for (var d = 0; d < ingredient.descriptions.length; d++) {
var thisDescription = ingredient.descriptions[d];
thisDescription.entityAspect.setDeleted();
}
By setting the record to Deleted via setDeleted(), the record is being removed from the array. So, records were getting skipped for deletion and then when attempting to save the parent the FK violation was coming up.
I changed the function as below with a while length > 0
function removeIngredient(ingredient) {
while (ingredient.descriptions.length > 0) {
ingredient.descriptions[0].entityAspect.setDeleted();
}
ingredient.entityAspect.setDeleted();
}

Ok, I've just retested Breeze's deletion logic ( on breeze version 1.5.0) to try to confirm your issue, and have been unable to repro the behavior you see. Just to recap, the order in which you perform deletions does matter.
Delete parent then delete children
This will mark the parent as deleted and will force the update of the children's foreign keys to either null or the default value of the key depending on whether the foreign key property is nullable or not. The one exception to this rule is that breeze will never attempt to modify a primary key, so if the foreign key of the child is also part of the primary key then breeze will not attempt to modify it.
All child navigation properties will now return either empty arrays or null depending on whether the navigation property is scalar or not. At this point each of the children will marked as modified, as a result of the foreign key change.
Then the children will each be marked deleted.
Delete children then parent ( recommended)
The children will each be marked deleted. Foreign keys will NOT change but the corresponding navigation property will return null, instead of the returning the parent. At this point all of the parent's navigation properties that previously returned these children will now return either null or an empty array.
The parent is marked deleted. No change is made to any of its children ( because it has none at this point).
I have been unable to repro a case where an entity is set to an entity state of modified after it has been deleted. However, this may have been a bug in an earlier version of Breeze, so please try your test with the latest version.

Related

Exception The value of 'X' is unknown when attempting to save changes

There are these two entities:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public CompanyVehicle CompanyVehicle { get; set; }
}
and
public class CompanyVehicle
{
public int Id { get; set; }
public string Name { get; set; }
public Employee Employee { get; set; }
}
Using Entity Framework Core 5.0.8 on SQL Server 2019, the configuration for CompanyVehicle is:
entityBuilder.HasOne(t => t.Employee)
.WithOne(t => t.CompanyVehicle)
.HasForeignKey<Employee>(t => t.Id)
.IsRequired();
And we'll try to insert something:
public void Create(Employee employee)
{
employee.CompanyVehicle = new CompanyVehicle();
dbContext.Add<Employee>(employee);
dbContext.SaveChanges();
}
The above code used to work fine in EF6. Two new records in both Employee and CompanyVehicle tables were created with the same Id. After migrating to EF Core 5.0.8, dbContext.SaveChanges() throws an exception:
System.InvalidOperationException: 'The value of 'Employee.Id' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.'
Note that these entities are just examples and the database design should not be altered in my case.
Update
After some more investigation, I've found out my problem is:
Having X (principal) and Y (dependent) as two tables where X.Id is PK for X and Y.Id is PK for Y and also FK to X, in EF Core a record of X cannot be inserted.
So I finally found the problem, configuring a Property to be both PK and FK is possible and very easy. We had our old codes after migrating to EFCore from EF6 in an assembly. The project is a framework so in OnModelCreating we use modelBuilder.ApplyConfigurationsFromAssembly in our base DbContext to register configurations in the guest projects. The project will automatically find all the configurations in all of assemblies referenced by the project or DLLs in the application path.
The key point is: In EF Core explicit fluent FK configuration is in the reverse order compared to EF6. So in EF6 for Employee we used to write:
this.HasRequired(t => t.CompanyVehicle)
.WithRequiredDependent(t => t.Employee)
.HasForeignKey(d => d.Id);
and in EF Core we should write:
b.HasOne(t => t.CompanyVehicle)
.WithOne(t => t.Employee)
.HasForeignKey<Employee>(t => t.Id).IsRequired();
The parameter d used in the first part is of type CompanyVehicle. So our migrator converted the old code to:
b.HasOne(t => t.CompanyVehicle)
.WithOne(t => t.Employee)
.HasForeignKey<CompanyVehicle>(t => t.Id).IsRequired();
Which is incorrect. The generic parameter should be the dependent table type. We later fixed the issue in a new namespace but the ApplyConfigurationsFromAssembly method kept applying the obsolete code after our configuration too.
I used the following block of code at the end of OnModelCreating to investigate the issue:
foreach (var entity in modelBuilder.Model.GetEntityTypes())
foreach(var key in entity.GetForeignKeys())
{
//Check what is in the key...
}
and noticed that there are duplicated keys configured for my entities.
Entity Framework Core configures one to one relationships by being able to detect the foreign key property, and thereby identify which is the principal and which is the dependent entity in the relationship.
First look at the existing database and check what is the dependant table, assuming it is the Employee, it should have a foriegn key to CompanyVehicle table. (It could be other way around in your case.)
1. Using EF Core convestions.
If Employee is the depentant table, add that exact foriegn key property name (let's assume it's Vehicle_Id) to your Employee entity. Follow 2nd method if you don't want to add a property to the class.
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Vehicle_Id { get; set; } // <-- This right here.
public CompanyVehicle CompanyVehicle { get; set; }
}
Without this property, as I mentioned earlier, child/dependent side could not be determined for the one-to-one relationship. (check what is yours in the db and add that property, otherwise you will get two foreign keys in the Employee table)
And using fluent API, configure the relation like this. (Notice how a and b were used to separate two navigation properties, in your implementation you have used t, for both, and when you say .HasForeignKey<Employee>(t => t.Id), you're setting the foriegn key to primary key Id of Employee table, which could be the reason behind your error).
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>(b => b.Vehicle_Id);
}
2. Not using EF Core conventions.
If you do not like to add a property to the dependant table, use the exsisting foriegn key in the database (let's assume it's Vehicle_Id), fluent API config should look like this.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>("Vehicle_Id");
}
Edit:
The Has/With pattern is used to close the loop and fully define a relationship. In this case, since the relationship to be configured is a one-to-one, the HasOne method is chained with the WithOne method. Then the dependent entity (Employee) is identified by passing it in as a type parameter to the HasForeignKey method, which takes a lambda specifying which property in the dependent type is the foreign key.
So if you want the Employee Id to act as the foriegn key to the CompanyVehicle table, ammend your Fluent API as this, again notice a and b when specifying lambdas.
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>(b => b.Id);
I had the same issue that A. Morel had.
When manually inserting a custom join table for ManyToMany, and a foreign key was 0, I was getting this error.
Fixed by changing the seed value of the parent table to start at 2:
DBCC CHECKIDENT ('program_contact', RESEED, 1);
Because of this issue
DBCC CHECKIDENT Sets Identity to 0
For me this seems to be caused by creating "blank" entities that I don't add to the context. In EF6 these were ignored since they were not added, but in EF Core they seem to be added automatically.
I corrected the issue by reducing the scope of my "writable" context down to just the single line where a change was made, and used a separate "read only" context for everything else.
I could further correct this by not using entity types directly in my view so that I can make blank entries that are not entities.

Why am I getting DbUpdateException: OptimisticConcurrencyException?

I have a Category class:
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
I also have a Subcategory class:
public class Subcategory
{
public int SubcategoryId { get; set; }
public Category Category { get; set; }
public string SubcategoryName { get; set; }
}
And a Flavor class:
public class Flavor
{
public int FlavorId { get; set; }
public Subcategory Subcategory { get; set; }
public string FlavorName { get; set; }
}
Then I also have Filling and Frosting classes just like the Flavor class that also have Category and Subcategory navigation properties.
I have a Product class that has a Flavor navigation property.
An OrderItem class represents each row in an order:
public class OrderItem
{
public int OrderItemId { get; set; }
public string OrderNo { get; set; }
public Product Product { get; set; }
public Frosting Frosting { get; set; }
public Filling Filling { get; set; }
public int Quantity { get; set; }
}
I'm having issues when trying to save an OrderItem object. I keep getting DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. with the Inner Exception being OptimisticConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. I've stepped through my code several times and I can't find anything that modifies or deletes any entities loaded from the database. I've been able to save the OrderItem, but it creates duplicate entries of Product, Flavor, Subcategory and Category items in the DB. I changed the EntityState of the OrderItem to Modified, but that throws the above exception. I thought it might have been the fact that I have Product, Frosting and Filling objects all referencing the same Subcategory and Category objects, so I tried Detaching Frosting and Filling, saving, attaching, changing OrderItem entity state to Modified and saving again, but that also throws the above exception.
The following statement creates duplicates in the database:
db.OrderItems.Add(orderItem);
Adding any of the following statements after the above line all cause db.SaveChanges(); to throw the mentioned exception (both Modified and Detached states):
db.Entry(item).State = EntityState.Modified;
db.Entry(item.Product.Flavor.Subcategory.Category).State = EntityState.Modified;
db.Entry(item.Product.Flavor.Subcategory).State = EntityState.Modified;
db.Entry(item.Product.Flavor).State = EntityState.Modified;
db.Entry(item.Product).State = EntityState.Modified;
Can someone please give me some insight? Are my classes badly designed?
The first thing to check would be how the entity relationships are mapped. Generally the navigation properties should be marked as virtual to ensure EF can proxy them. One other optimization is that if the entities reference SubCategory then since SubCats reference a Category, those entities do not need both. You would only need both if sub categories are optional. Having both won't necessarily cause issues, but it can lead to scenarios where a Frosting's Category does not match the category of the Frosting's SubCategory. (Seen more than enough bugs like this depending on whether the code went frosting.CategoryId vs. frosting.SubCategory.CategoryId) Your Flavor definition seemed to only use SubCategory which is good, just something to be cautious of.
The error detail seems to point at EF knowing about the entities but not being told about their relationships. You'll want to ensure that you have mapping details to tell EF about how Frosting and SubCategory are related. EF can deduce some of these automatically but my preference is always to be explicit. (I hate surprises!)
public class FrostingConfiguration : EntityTypeConfiguration<Frosting>
{
public FlavorConfiguration()
{
ToTable("Flavors");
HasKey(x => x.FlavorId)
.Property(x => x.FlavorId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.SubCategory)
.WithMany()
.Map(x => x.MapKey("SubCategoryId");
}
}
Given your Flavor entity didn't appear to have a property for the SubCategoryId, it helps to tell EF about it. EF may be able to deduce this, but with IDs and the automatic naming conventions it looks for, I don't bother trying to remember what works automagically.
Now if this is EF Core, you can replace the .Map() statement with:
.ForeignKey("SubCategoryId");
which will set up a shadow property for the FK.
If SubCats are optional, then replace HasRequired with HasOptional. The WithMany() just denotes that while a Flavor references a sub category, SubCategory does not maintain a list of flavours.
The next point of caution is passing entities outside of the scope of the DBContext that they were loaded. While EF does support detaching entities from one context and reattaching them to another, I would argue that this practice is almost always far more trouble than it is worth. Mapping entities to POCO ViewModels/DTOs, then loading them on demand again when performing updates is simpler, and less error-prone then attempting to reattach them. Data state may have changed between the time they were initially loaded and when you go to re-attach them, so fail-safe code needs to handle that scenario anyways. It also saves the hassle of messing around with modified state in the entity sets. While it may seem efficient to not load the entities a second time, by adopting view models you can optimize reads far more efficiently by only pulling back and transporting the meaningful data rather than entire entity graphs. (Systems generally read far more than they update) Even for update-heavy operations you can utilize bounded contexts to represent large tables as smaller, simple entities to load and update a few key fields more efficiently.

How do I use a composite key for a one to many relationship in Code First EF

I am using EF Code First.
I need two tables, LedgerCategories and LedgerSubCategories with a one-to-many relationship (Categories -> SubCategories), with the keys in each being codes (strings) - i.e. LedgerCategoryCode and LedgerSubCategoryCode respectively. However, I need to allow the SubCategoryCode values to be the same for different Categories.
E.g. CategoryCode = REHAB, SubCategoryCodes = MATL, CONTR, and FEES; and CategoryCode = MAINT, SubCategoryCodes = MATL, CONTR, and FEES.
I'm thinking I need to use a composite key and include both the CategoryCode and SubCategoryCode fields in the LedgerSubCategories table. Currently I have:
public class LedgerCategory
{
[Key]
public string LedgerCategoryCode { get; set; }
public string Description { get; set; }
public List<LedgerSubCategory> LedgerSubCategories { get; set; }
}
public class LedgerSubCategory
{
[Key, Column(Order = 0)]
public string LedgerCategoryCode { get; set; }
[Key, Column(Order = 1)]
public string LedgerSubCategoryCode { get; set; }
public string Description { get; set; }
}
I am seeding these tables using only instances of the LedgerCategory class, having each contain a List of appropriately instantiated LedgerSubCategory classes. This appears to both set up the DB schema correctly (in my perception), and populate both tables appropriately.
But, when I reinstantiate a simple List of LedgerCategory, i.e.
using (var db = new BusinessLedgerDBContext())
{
var LedgerCategories = db.LedgerCategories.ToList();
}
The LedgerCategory instances don't contain their respective List of associated LedgerSubCategory instances.
I am trying to avoid, what seems like a kludge, to introduce a unique number or Guid ID field in LedgerSubCategories as a PK and just index off the other Code fields. I haven't tried this, but I'm not sure it would cause any different results for reinstantiating the LedgerCategories and getting associated LedgerSubCategories.
Any advice on how to do this appropriately and get proper results is appreciated.
To, I suppose, answer my own question, I have found that overriding OnModelCreating() in the respective DbContext with Fluent API to establish the one to many relationship and foreign key when the Code First framework establishes the desired DB Schema. There appears no other way to do this, such as with Attributes. By many accounts of others, including MSDN, Fluent API appears to be what is needed. However, that has led me to a new issue, or set of issues, which I've posed as a question here.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Configures the one-many relationship between Categories and
// SubCategories, and established the Foreign Key in SubCategories
modelBuilder.Entity<Category>()
.HasMany<SubCategory>(c => c.SubCategories)
.WithRequired(s => s.Category)
.HasForeignKey<string>(s => s.CategoryCode);
}

DbSet.Attach() only updates single table but not referencing ones

I have two tables: Word and Adjective, both with some properties. Primary key of both tables is ID, Adjective.ID also references Word.ID as foreign key so there is a 1-1 relationship.
I also have a repository for any kind of table with an Update function.
public void Update(T entity) {
var entry = DatabaseContext.Entry(entity);
DatabaseSet.Attach(entity);
entry.State = EntityState.Modified;
}
I take a value from the database, convert it into a ViewModel looking like this (of course it's actually a little more complex):
public class WordModel {
public int ID { get; set; }
public string OriginalWord { get; set; }
}
public class AdjectiveModel : WordModel {
public string Translation { get; set; }
}
Then I alter the values of properties Word and Translation, convert and write it back. After conversion I have an object like this:
Word = {
ID = 1
OriginalWord = y
Adjective = {
ID = 1
Translation = z
}
}
Upon updating however, only one table gets updated.
Database.Words.Update(Word) only updates the OriginalWord value in the Word table,
Database.Adjectives.Update(Word.Adjective) only updates the Translation value in the Adjective table.
When running the updates for both tables sequentially I get an InvalidOperationException: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Creating a new database entry works perfectly.
I cannot believe I have to update both tables on their own and then save the context for each. I have created the database Repository via a Tutorial that obviously didn't explain well enough what's going on with the DbSet and the DbContext, which leaves me a little helpless here.
Sadly I have no link (it is quite a while ago I created the database project)
So, what am I doing wrong here?
You entity Word contains an entity Adjective, it is then the root of the object graph. Now generally here's what you should keep in mind in the following situations :
All objects in the graph are new (new word and new adjective)
use myDbContext.Words.Add(myNewWordObjectGraph); to have the correct state you want.
Only root is new (new word and a pre-existing non modified adjective)
use myDbContext.Entry(myNewWord).state = EntityState.Added; to have the correct state you want.
Root is modified and some nodes are modified (word and adjective both exist in the DB and both have been modified)
use myDbContext.Entry(myWord).State = EntityState.Modified; and myDbContext.Entry(myAdjective).State = EntityState.Modified; to have the correct state you want. i.e. call myDbContext.Entry(myObject).State = EntityState.Modified; for each modified object in the graph whether it's the root or some other node.
Root is unchanged and/or Modified and some nodes are added, others are also unchanged and/or modified
use myDbContext.MyRootObjectDbSet.Add(myRootObject); ; this will mark all the objects in the graph as EntityState.Added including the unchanged and/or modified objects. so the next call should be for each unchanged and/or modified object in order to correct its state : myDbContext.Entry(myObject).State = ThisObjectSCorrectState;.
I Hope that helps
EDIT
Calling DbSet.Attach(...) just adds the object to the objects tracked by EF. If you modify an object before calling DbSet.Attach(...), the modifications won't be persisted to DB when you call SaveChages(), so attaching an object as is before modification, calling DbSet.Attach(...) and then modifying the object is the way to make EF aware of the modifications.
Based on the way your update method's defined I would assume your repository looks something like this maybe?
//Not threadsafe as it contains a transient object 'DbContext'.
public class Repository<T> : IRespository<T> where T : class
{
private readonly MyDbContext context;
public Repository(MtDbContext context)
{
this.context = context
}
//...
public void Update(T entity) {... }
public void Commit() { context.SaveChanges(); }
}
I would suggest changing the update method to the following :
public void Update(T entity)
{
context.Entry(entity).State = EntityState.Modified;
}
And this update method would be called for each object you updated in the graph using the same instance of the repository enclosing the DbContext.

How to delete child entities before parent with Entity Framework CF?

I am trying to use EF code-first to delete a db record (deleteMe) and it's children (deleteMe.Prices).
foreach (var deleteMe in deleteThese)
{
// Delete validation
if(CanDeleteItem(deleteMe.ItemId))
{
db.Entry(deleteMe).State = EntityState.Deleted;
foreach (var item in deleteMe.Prices)
{
db.Entry(item).State = EntityState.Deleted; // cascade delete
}
}
}
db.SaveChanges();
However, Entity Framework seems to be unable to track the fact that the child records should be deleted before the parent. I get the error:
The DELETE statement conflicted with the REFERENCE constraint "ItemPrice_Item".
The conflict occurred in database "DEVDB", table "dbo.ItemPrices",
column 'Item_ItemId'.
The statement has been terminated.
How would I execute this delete in EF?
I ended up finding a quick line that'd do it for me:
foreach (var deleteMe in deleteThese)
{
// Delete validation
if(CanDeleteItem(deleteMe.ItemId))
{
///
deleteMe.Prices.ToList().ForEach(p => db.ItemPrices.Remove(p));
///
db.Entry(deleteMe).State = EntityState.Deleted;
}
}
db.SaveChanges();
EF6
context.Children.RemoveRange(parent.Children)
Cascade delete in EF is dependent on cascade delete configured in relation in the database so if you don't have cascade delete configured in the database you must first load all item prices to your application and mark them as deleted.
Well the most easiest solution would be to iterate through prices first and call save changes, then set the entry to delete for deleteMe and call save changes again, but have you checked out this: Entity framework code first delete with cascade? It seems to be what you want.
Curious though also why you just aren't removing the entities from the context to delete but instead setting the entry state?
Another option is to set cascade delete http://blogs.msdn.com/b/alexj/archive/2009/08/19/tip-33-how-cascade-delete-really-works-in-ef.aspx
Do something like this (not tested but hopefully you get the jist):
using (TransactionScope scope = new TransactionScope())
{
foreach (var deleteMe in deleteThese)
{
// Delete validation
if(CanDeleteItem(deleteMe.ItemId))
{
foreach (var item in deleteMe.Prices)
{
db.Entry(item).State = EntityState.Deleted; // cascade delete
}
db.SaveChanges();
db.Entry(deleteMe).State = EntityState.Deleted;
}
}
db.SaveChanges();
scope.Complete();
}
Additionally you could call:
db.Prices.Remove(item);
and
db.DeleteMes.Remove(deleteMe);
instead of setting the entry state. Not sure if there is a difference behind the scenes between the two though.
Cascade delete in Entity framework is tricky thing, as you need to be sure about deletion entity object graph.It is better to always write a integration test for these cascade deletes.
If you try to delete parent entity in EF, it will try to execute delete statements for any child entities in current dbcontext. As a result, it will not initialize any child entities which have not been loaded. This will lead to RDBMS runtime error which violate the foreign key constraint. To be in safe side ensure all dependent entities loaded to current dbcontext before deleting.
The following works quite efficiently.
For each relational table in your database add the following (At your context file).
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder
.Entity<TableA>()
.HasMany(x => x.TableB)
.WithRequired(x => x.TableA)
.WillCascadeOnDelete();
modelBuilder
.Entity<TableC>()
.HasMany(x => x.TableD)
.WithRequired(x => x.TableC)
.WillCascadeOnDelete();
modelBuilder
.Entity<TableE>()
.HasMany(x => x.TableF)
.WithRequired(x => x.TableE)
.WillCascadeOnDelete(); }
Then in your code, don't forget to load these tables, before you delete
context.TableA.Load();
context.TableB.Load();
context.TableC.Load();
context.TableD.Load();
context.TableE.Load();
context.TableF.Load();
var tableAEntity= TableA.Where(x => x.Condition == [yourcondition].FirstOrDefault();
context.TableA.Remove(tableAEntity);
context.SaveChanges();
This will delete the entity (record) from the main entry table and all the connected table records (related through FK) quite fast and efficiently (Even if the relationship cascades deeply at multiple levels).
If your object is self-referencing, you can delete both many-to-many and one-to-many children using the method below. Just remember to call db.SaveChanges() afterwards :)
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Object obj = this.db.Objects.Find(id);
this.DeleteObjectAndChildren(obj);
this.db.Objects.Remove(obj);
this.db.SaveChanges();
return this.Json(new { success = true });
}
/// <summary>
/// This deletes an object and all children, but does not commit changes to the db.
/// - MH # 2016/08/15 14:42
/// </summary>
/// <param name="parent">
/// The object.
/// </param>
private void DeleteObjectAndChildren(Object parent)
{
// Deletes One-to-Many Children
if (parent.Things != null && parent.Things.Count > 0)
{
this.db.Things.RemoveRange(parent.Things);
}
// Deletes Self Referenced Children
if (parent.Children != null && parent.Children.Count > 0)
{
foreach (var child in parent.Children)
{
this.DeleteObjectAndChildren(child);
}
this.db.Objects.RemoveRange(parent.Children);
}
}
I had a similar issue and for me, it looked like I hadn't correctly established the relationship between Parent and Child in their respective classes.
My fix was to add the attributes specified below to the Child class, for the property that represented its Parent's Id
public class Child
{
[Key, Column(Order = 1)]
public string Id { get; set; }
[Key, ForeignKey("Parent"), Column(Order = 2)] // adding this line fixed things for me
public string ParentId {get; set;}
}
public class Parent
{
[Key, Column(Order = 1)]
public string Id { get; set; }
...
public virtual ICollection<Child> Children{ get; set; }
}
_context.Remove(parent);
_context.RemoveRange(_context.Childrens
.Where(p => parent.Childrens
.Select(c => c.Id).Contains(p.Id)));

Resources