Efcore: Removing two or more entity with byte[] PK will throw exception

Created on 21 Jul 2018  路  12Comments  路  Source: dotnet/efcore

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:
```c#
public class Blog
{
[Key]
public byte[] Name { get; set; }
}


operation:
```c#
                // 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

closed-fixed customer-reported type-bug

Most helpful comment

All 12 comments

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?

My (crude) workaround:

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

Thanks. In my case, though, the problem arises when multiple child records are being deleted when deleting a single parent record鈥攁ll 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.

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.

I just found SetValueComparer. Sorry for the chatter.

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.

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

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

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.

Thanks @Perlkonig, that helped a heap!

I am encountering this issue with an System.Net.IPAddress in my primary key using the Postgres inet type with NPGSQL. Maybe because IPAddress uses a ushort[] internally?

Was this page helpful?
0 / 5 - 0 ratings