question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Client cascade delete for owned entities

See original GitHub issue

I am trying to delete entities just by giving the PK however it fails if the entity contains Complex Type fields

Example

    public class BowtieCauseDetails
    {
        [Key]
        public int NodeId { get; set; }

        public BowtieCauseTimeFields Current { get; set; }

        public BowtieCauseTimeFields Proposed { get; set; }
    }

    public class BowtieCauseTimeFields
    {
        public BowtieNodeInOutFrequencyFields Outgoing { get; set; }
    }

    public class BowtieNodeInOutFrequencyFields
    {
        public decimal Frequency { get; set; }
    }

Example

            var ctx = new MyDbContext();

            var toBeDeleted = ctx
                .BowtieCauseDetails
                .AsNoTracking()
                .Select(e => new BowtieCauseDetails
                {
                    NodeId = e.NodeId // PK only
                })
                .ToList();

            ctx.BowtieCauseDetails.RemoveRange(toBeDeleted);

            ctx.SaveChanges();

It throws the following exception

Unhandled Exception: System.InvalidOperationException: The entity of ‘BowtieCauseDetails’ is sharing the table ‘BowtieCauseDetails’ with ‘BowtieCauseDetails.Current#BowtieCauseTimeFields’, but there is no entity of this type with the same key value that has been marked as ‘Deleted’. Consider using ‘DbContextOptionsBuilder.EnableSensitiveDataLogging’ to see the key values.

If I provide empty place holder for those fields, it still fails but with a different message

Example

            var ctx = new MyDbContext();

            var toBeDeleted = ctx
                .BowtieCauseDetails
                .AsNoTracking()
                .Select(e => new BowtieCauseDetails
                {
                    NodeId = e.NodeId,
                    Current = new BowtieCauseTimeFields
                    {
                        Outgoing = new BowtieNodeInOutFrequencyFields
                        {
                        }
                    },
                    Proposed = new BowtieCauseTimeFields
                    {
                        Outgoing = new BowtieNodeInOutFrequencyFields
                        {
                        }
                    }
                })
                .ToList();

            ctx.BowtieCauseDetails.RemoveRange(toBeDeleted);

            ctx.SaveChanges();

Unhandled Exception: System.InvalidOperationException: The property ‘BowtieCauseTimeFieldsBowtieCauseDetailsNodeId’ on entity type ‘BowtieCauseDetails.Current#BowtieCauseTimeFields.Outgoing#BowtieNodeInOutFrequencyFields’ is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke ‘SaveChanges’ then associate the dependent with the new principal.

However if I get at least one field from the DB then it works

Example

            var ctx = new MyDbContext();

            var toBeDeleted = ctx
                .BowtieCauseDetails
                .AsNoTracking()
                .Select(e => new BowtieCauseDetails
                {
                    NodeId = e.NodeId,
                    Current = new BowtieCauseTimeFields
                    {
                        Outgoing = new BowtieNodeInOutFrequencyFields
                        {
                            Frequency = e.Current.Outgoing.Frequency
                        }
                    },
                    Proposed = new BowtieCauseTimeFields
                    {
                        Outgoing = new BowtieNodeInOutFrequencyFields
                        {
                            Frequency = e.Current.Outgoing.Frequency
                        }
                    }
                })
                .ToList();

            ctx.BowtieCauseDetails.RemoveRange(toBeDeleted);

            ctx.SaveChanges();

I think it should work by giving the PK only.

Here is the full sample for easier replication

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace EfTests
{
    class Program
    {
        static int Main(string[] args)
        {
            var ctx = new MyDbContext();

            var toBeDeleted = ctx
                .BowtieCauseDetails
                .AsNoTracking()
                .Select(e => new BowtieCauseDetails
                {
                    NodeId = e.NodeId,
                    //Current = new BowtieCauseTimeFields
                    //{
                    //    Outgoing = new BowtieNodeInOutFrequencyFields
                    //    {
                    //        Frequency = e.Current.Outgoing.Frequency
                    //    }
                    //},
                    //Proposed = new BowtieCauseTimeFields
                    //{
                    //    Outgoing = new BowtieNodeInOutFrequencyFields
                    //    {
                    //        Frequency = e.Current.Outgoing.Frequency
                    //    }
                    //}
                })
                .ToList();

            ctx.BowtieCauseDetails.RemoveRange(toBeDeleted);

            ctx.SaveChanges();

            return 0;
        }
    }

    public class BowtieCauseDetails
    {
        [Key]
        public int NodeId { get; set; }

        public BowtieCauseTimeFields Current { get; set; }

        public BowtieCauseTimeFields Proposed { get; set; }
    }

    public class BowtieCauseTimeFields
    {
        public BowtieNodeInOutFrequencyFields Outgoing { get; set; }
    }

    public class BowtieNodeInOutFrequencyFields
    {
        public decimal Frequency { get; set; }
    }

    public class MyDbContext : DbContext
    {
        public DbSet<BowtieCauseDetails> BowtieCauseDetails { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("Server=localhost,1433;Database=ipl_tux;Integrated Security=True;");            
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<BowtieCauseDetails>()
                .OwnsOne(c => c.Current)
                .OwnsOne(c => c.Outgoing);

            modelBuilder.Entity<BowtieCauseDetails>()
                .OwnsOne(c => c.Proposed)
                .OwnsOne(c => c.Outgoing);

            base.OnModelCreating(modelBuilder);
        }
    }
}

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:2
  • Comments:13 (9 by maintainers)

github_iconTop GitHub Comments

3reactions
diegoernestofariascommented, Oct 25, 2018

You can fix it with a simple model configuration. Add this to your DbContext class:

protected override void OnModelCreating(ModelBuilder modelBuilder) {
        foreach (var relationship in modelBuilder.Model.GetEntityTypes().Where(e => e.IsOwned()).SelectMany(e => e.GetForeignKeys())) {
                relationship.DeleteBehavior = DeleteBehavior.Cascade;
        }
}

Now, all the owned types (complex objects) will be deleted automatically when you delete the owner entity.

2reactions
smitpatelcommented, Oct 30, 2017

@ralmsdeveloper - This is different from #10180 because here there is no need of unique indexes.

The issue here is, when you are trying to delete an entity it would remove the row from database. But since the table is being shared, there would be other entities using the same row so unless all of the entities sharing the row are deleted, deleting the row could be incorrect and cause data corruption. Hence before sending command EF Core tried to verify if all the entities in that row is marked as deleted and in the absence threw above exception.

Though in this particular case, owned entities are sharing the table. If owner is deleted then owned entities should also lose its existence since it is tied with owner. If the entities are loaded in different state then its an error. But if it not then owner is deleted, owned children could be deleted too.

Another use of table sharing is identifying 1-to-1 relationship. In that case, we need to think about the effects. For the case of principal getting deleted with cascade delete is enabled, we can delete but not for all cases.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Cascade Delete - EF Core
Cascading deletes are needed when a dependent/child entity can no longer be associated with its current principal/parent.
Read more >
Cascade Delete in Entity Framework 6
Cascade delete automatically deletes dependent records or sets null to ForeignKey columns when the parent record is deleted in the database. Cascade delete...
Read more >
Cascade Delete in Entity Framework Core
Entity Framework Core Cascade Delete is one of the Referential actions. These actions specify what to do with the related rows when we...
Read more >
Entity Framework (Core) - cascading delete
It seems the EF has the ability to cascade delete - if it is enabled, and if the dependent objects are loaded in...
Read more >
EF Core Advanced Topics - Cascade Delete
Cascade delete allows the deletion of a row to trigger the deletion of related rows automatically. EF Core covers a closely related concept...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found