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.

If an entity is part of a hierarchy then I am unable to remove it by its PK only

See original GitHub issue

I am trying to remove all items of a hierarchy by deleting all base items by their PK however however when I try to remove any other entity which has a FK pointing to a derived type then I get an internal casting exception.

My sample entities

    public class Actor
    {
        public int Id { get; set; }

        public string Name { get; set; }
    }

    public class User : Actor
    {
        public UserDetails Details { get; set; }
    }

    public class Group : Actor
    {
    }

    public class UserDetails
    {
        public int ActorId { get; set; }

        public User Actor { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public string Email { get; set; }
    }

The deletion

            var ctx = new MyDbContext();

            var toBeDeleted = ctx
                .Actors
                .AsNoTracking()
                .Select(e => new Actor
                {
                    Id = e.Id
                })
                .ToList();

            ctx.Actors.RemoveRange(toBeDeleted);

            var toBeDeletedDetails = ctx
                .UserDetails
                .AsNoTracking()
                .Select(e => new UserDetails
                {
                    ActorId = e.ActorId
                })
                .ToList();

            ctx.UserDetails.RemoveRange(toBeDeletedDetails);

            ctx.SaveChanges();

The exception

Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'EfTests3.Actor' to type 'EfTests3.User'.
   at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrPropertySetter`2.SetClrValue(Object instance, Object value)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.WritePropertyValue(IPropertyBase propertyBase, Object value)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetProperty(IPropertyBase propertyBase, Object value, Boolean setModified)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.set_Item(IPropertyBase propertyBase, Object value)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.SetNavigation(InternalEntityEntry entry, INavigation navigation, Object value)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.InitialFixup(InternalEntityEntry entry, ISet`1 handledForeignKeys, Boolean fromQuery)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.StateChanged(InternalEntityEntry entry, EntityState oldState, Boolean fromQuery)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.StateChanged(InternalEntityEntry entry, EntityState oldState, Boolean fromQuery)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph(EntityEntryGraphNode node, Func`2 handleNode)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState entityState, Boolean forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.DbContext.SetEntityState(InternalEntityEntry entry, EntityState entityState)
   at Microsoft.EntityFrameworkCore.DbContext.RemoveRange(IEnumerable`1 entities)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.RemoveRange(IEnumerable`1 entities)
   at EfTests3.Program.Main1(String[] args) in D:\code\tuxedo\Tests\EfBugTest3.cs:line 41
   at EfTests3.Program.Main(String[] args) in D:\code\tuxedo\Tests\EfBugTest3.cs:line 10

Workaround 1 is to swap the deletion around then it works however this is just small piece of a big puzzle involving maps, reflection, dynamics and etc so the order cannot be guaranteed for every base class and derived class

            var ctx = new MyDbContext();

            // entites depending upon a derived class first
            var toBeDeletedDetails = ctx
                .UserDetails
                .AsNoTracking()
                .Select(e => new UserDetails
                {
                    ActorId = e.ActorId
                })
                .ToList();

            ctx.UserDetails.RemoveRange(toBeDeletedDetails);

            // base entity
            var toBeDeleted = ctx
                .Actors
                .AsNoTracking()
                .Select(e => new Actor
                {
                    Id = e.Id
                })
                .ToList();

            ctx.Actors.RemoveRange(toBeDeleted);

Workaround 2 is to cast everything into derived entities first however some scenarios contains lots of derived entities so it is just a workaround if there isn’t a fix

            var ctx = new MyDbContext();

            var toBeDeleted = ctx
                .Users
                .AsNoTracking()
                .Select(e => new User { Id = e.Id })
                .Cast<Actor>()
                .Union(ctx
                    .Groups
                    .AsNoTracking()
                    .Select(e => new Group { Id = e.Id })
                )
                .ToList();

            ctx.Actors.RemoveRange(toBeDeleted);

            var toBeDeletedDetails = ctx
                .UserDetails
                .AsNoTracking()
                .Select(e => new UserDetails
                {
                    ActorId = e.ActorId
                })
                .ToList();

            ctx.UserDetails.RemoveRange(toBeDeletedDetails);

            ctx.SaveChanges();

Full code

namespace EfTests3
{
    class Program
    {
        static int Main(string[] args)
        {
            //Main1(args);
            Main2(args);
            //Main3(args);

            return 0;
        }

        // This one breaks
        static int Main1(string[] args)
        {
            var ctx = new MyDbContext();

            var toBeDeleted = ctx
                .Actors
                .AsNoTracking()
                .Select(e => new Actor
                {
                    Id = e.Id
                })
                .ToList();

            ctx.Actors.RemoveRange(toBeDeleted);

            var toBeDeletedDetails = ctx
                .UserDetails
                .AsNoTracking()
                .Select(e => new UserDetails
                {
                    ActorId = e.ActorId
                })
                .ToList();

            ctx.UserDetails.RemoveRange(toBeDeletedDetails);

            ctx.SaveChanges();

            return 0;
        }

        // Swapping the deletion around works
        static int Main2(string[] args)
        {
            var ctx = new MyDbContext();

            // entites depending upon a derived class first
            var toBeDeletedDetails = ctx
                .UserDetails
                .AsNoTracking()
                .Select(e => new UserDetails
                {
                    ActorId = e.ActorId
                })
                .ToList();

            ctx.UserDetails.RemoveRange(toBeDeletedDetails);

            // base entity
            var toBeDeleted = ctx
                .Actors
                .AsNoTracking()
                .Select(e => new Actor
                {
                    Id = e.Id
                })
                .ToList();

            ctx.Actors.RemoveRange(toBeDeleted);

            ctx.SaveChanges();

            return 0;
        }

        // Forcing casting also works
        static int Main3(string[] args)
        {
            var ctx = new MyDbContext();

            var toBeDeleted = ctx
                .Users
                .AsNoTracking()
                .Select(e => new User { Id = e.Id })
                .Cast<Actor>()
                .Union(ctx
                    .Groups
                    .AsNoTracking()
                    .Select(e => new Group { Id = e.Id })
                )
                .ToList();

            ctx.Actors.RemoveRange(toBeDeleted);

            var toBeDeletedDetails = ctx
                .UserDetails
                .AsNoTracking()
                .Select(e => new UserDetails
                {
                    ActorId = e.ActorId
                })
                .ToList();

            ctx.UserDetails.RemoveRange(toBeDeletedDetails);

            ctx.SaveChanges();

            return 0;
        }
    }


    public class Actor
    {
        public int Id { get; set; }

        public string Name { get; set; }
    }

    public class User : Actor
    {
        public UserDetails Details { get; set; }
    }

    public class Group : Actor
    {
    }

    public class UserDetails
    {
        public int ActorId { get; set; }

        public User Actor { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public string Email { get; set; }
    }

    public class MyDbContext : DbContext
    {
        public DbSet<Actor> Actors { get; set; }
        public DbSet<User> Users { get; set; }
        public DbSet<Group> Groups { get; set; }
        public DbSet<UserDetails> UserDetails { 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<UserDetails>()
                .HasKey(e => e.ActorId);

            base.OnModelCreating(modelBuilder);
        }
    }
}

To be sure to be sure, this issue isn’t a duplicate of neither #10179 nor #10180 however they were all found during my attempt to dynamically purge an entire hierarchy of objects.

EF Core version: 2.0

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
AndriySvyrydcommented, May 7, 2018

@silarmani In general we don’t want to encourage users to supply the wrong type as there might be more places that won’t be able to handle it either now or in the future. Also we cannot make this work in all scenarios even now. A more robust way of accomplishing this would be with Batch CUD https://github.com/aspnet/EntityFrameworkCore/issues/795

0reactions
silarmanicommented, May 7, 2018

Hi guys, just wondering why was this issue closed?

Read more comments on GitHub >

github_iconTop Results From Across the Web

c# - The entity type 'IdentityUserLogin<string>' requires a ...
Closed 4 years ago. i am using dotnet core 1.1 on linux, and i am having issues when i want to split up...
Read more >
Question - Deleting a whole hierarchy of entities?
Is there a way to delete an entity, and all its children (likely converted from GameObjects)? Here's the code running the job (roughly)....
Read more >
How do you completely empty the hierarchy table?
Ok so in the table i have a lot of "Entity" values. How do i remove those? I just want that table to...
Read more >
Deleting Hierarchies
You are allowed to delete a hierarchy that has relationship types associated with it. There will be a warning with the list of...
Read more >
Client cascade delete for owned entities · Issue #10179
I am trying to delete entities just by giving the PK however it fails if the entity contains Complex Type fields Example public...
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