New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removing two or more entity with byte[] PK will throw exception #12749

Closed
AtsushiSuzuki opened this Issue Jul 21, 2018 · 11 comments

Comments

Projects
None yet
8 participants
@AtsushiSuzuki
Copy link

AtsushiSuzuki commented Jul 21, 2018

When entity model have byte[] PK, removing two or more entity from context and SaveChanges will throw exception.

Exception message: System.InvalidOperationException : Failed to compare two elements in the array.
---- System.ArgumentException : At least one object must implement IComparable.

Stack trace:
at System.Collections.Generic.ArraySortHelper`1.Sort(T[] keys, Int32 index, Int32 length, IComparer`1 comparer)
   at System.Collections.Generic.List`1.Sort(Int32 index, Int32 count, IComparer`1 comparer)
   at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.BatchCommands(IReadOnlyList`1 entries)+MoveNext()
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(DbContext _, ValueTuple`2 parameters, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IReadOnlyList`1 entriesToSave, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at efcore_test2.UnitTest1.Test1() in C:\Users\suzuki\Documents\Projects\efcore_test2\UnitTest1.cs:line 44
--- End of stack trace from previous location where exception was thrown ---
----- Inner Stack Trace -----
   at System.Collections.Comparer.Compare(Object a, Object b)
   at Microsoft.EntityFrameworkCore.Update.Internal.ModificationCommandComparer.Compare(ModificationCommand x, ModificationCommand y)
   at System.Collections.Generic.ArraySortHelper`1.SwapIfGreater(T[] keys, Comparison`1 comparer, Int32 a, Int32 b)
   at System.Collections.Generic.ArraySortHelper`1.IntrospectiveSort(T[] keys, Int32 left, Int32 length, Comparison`1 comparer)
   at System.Collections.Generic.ArraySortHelper`1.Sort(T[] keys, Int32 index, Int32 length, IComparer`1 comparer)

Steps to reproduce

models:

    public class Blog
    {
        [Key]
        public byte[] Name { get; set; }
    }

operation:

                // ctx.Blogs have two or more entities
                foreach (var blog in ctx.Blogs)
                {
                    ctx.Remove(blog);
                }
                await ctx.SaveChangesAsync(); // This will fail with ASP.NET Core 2.1

full example: https://gitlab.com/suzuki_ds/efcore_test2

Expected behavior

delete two row from DB.

Actual behavior

await ctx.SaveChangesAsync() will throw exception.

Further technical details

EF Core version: Microsoft.AspNetCore.App 2.1.2
Database Provider: Microsoft.EntityFrameworkCore.Sqlite 2.1.1
Operating system: Windows 10 Pro 1803
IDE: Visual Studio 2017 15.7.5

@maumar maumar self-assigned this Jul 23, 2018

@maumar maumar removed their assignment Jul 24, 2018

@ajcvickers ajcvickers added the type-bug label Jul 27, 2018

@ajcvickers ajcvickers added this to the 2.2.0 milestone Jul 27, 2018

@Perlkonig

This comment has been minimized.

Copy link

Perlkonig commented Jul 29, 2018

Just ran into this myself. Glad to see it's being addressed. It's a bit of a project killer 😄

Is there a workaround in the meantime?

@AtsushiSuzuki

This comment has been minimized.

Copy link
Author

AtsushiSuzuki commented Jul 30, 2018

My (crude) workaround:

using (var tx = await ctx.Database.BeginTransactionAsync())
{
    foreach (var blog in ctx.Blogs.ToArray())
    {
        ctx.Remove(blog);
        await ctx.SaveChangesAsync();
    }
    tx.Commit();
}
@Perlkonig

This comment has been minimized.

Copy link

Perlkonig commented Jul 30, 2018

Thanks. In my case, though, the problem arises when multiple child records are being deleted when deleting a single parent record—all of which have byte[] primary keys. Too many child tables for me to manually code ☹️ This seems like a pretty critical bug. I hope it gets fixed sooner rather than later.

@Perlkonig

This comment has been minimized.

Copy link

Perlkonig commented Aug 5, 2018

So apparently one can create a custom IComparer function, but is it possible to hook that into EFCore?

I guess another approach is to add a converter to transform them into strings at the EFCore level. I'd prefer to avoid the extra cycles, but if that's the only way to move forward with my project, I guess that's what I'll have to do.

@Perlkonig

This comment has been minimized.

Copy link

Perlkonig commented Aug 5, 2018

I just found SetValueComparer. Sorry for the chatter.

@AtsushiSuzuki

This comment has been minimized.

Copy link
Author

AtsushiSuzuki commented Aug 5, 2018

I found better workaround.

  • define my own IComparer<ModificationCommand> that can handle byte[], by extending ModificationCommandComparer
  • replace ICompare<ModificationCommand> with defined implementation.

https://gitlab.com/suzuki_ds/efcore_test2/commit/a275603bcc7f9ef7c3795ef4ba51b82da8aaf091

I think I should handle null case, but it worked with my test case.

@Perlkonig

This comment has been minimized.

Copy link

Perlkonig commented Aug 5, 2018

THANK YOU! My custom comparer didn't fix the problem, but your solution did. Now I can move forward!

@danytoz

This comment has been minimized.

Copy link

danytoz commented Nov 8, 2018

I found better workaround.

  • define my own IComparer<ModificationCommand> that can handle byte[], by extending ModificationCommandComparer
  • replace ICompare<ModificationCommand> with defined implementation.

https://gitlab.com/suzuki_ds/efcore_test2/commit/a275603bcc7f9ef7c3795ef4ba51b82da8aaf091

I think I should handle null case, but it worked with my test case.

I didnt have an issue with something as complex as bytearray but it was using your comparer allowed me to find the culprit class where I needed to implement the comparer.

Thanks

@beeradmoore

This comment has been minimized.

Copy link

beeradmoore commented Feb 11, 2019

I'm running into this issue as well. Not with deleting objects but with updating existing ones which happen to also have byte[] as PK. Does anyone have a copy at the above gitlab commits so I can have a gander?

I can't see how to subclass or add to the config the subclass of the ModificationCommandComparer. Googling it I just find a bunch of people going "Ah yeah, turns out C# can't compare byte[]. Changed it to an int/string and now it works!", but changing it isn't an option at this point.

@Perlkonig

This comment has been minimized.

Copy link

Perlkonig commented Feb 11, 2019

@beeradmoore

This comment has been minimized.

Copy link

beeradmoore commented Feb 11, 2019

Thanks @Perlkonig, that helped a heap!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment