Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework Core DbContext.RemoveRange and type constraints

This code throws an exception

System.InvalidOperationException: The entity type 'List<..>' was not found. Ensure that the entity type has been added to the model.

private static void Update<T>(DbContext context, ICollection<T> existing, ICollection<T> updated)  // where T: class
{
      context.RemoveRange(existing); 
      updated.ToList().ForEach(existing.Add);
}

However, if you add the type constraint where T: class no exception is thrown. Why is this? I was under the impression C# type constraints didn't affect run time behavior like this. Both versions compile fine.

like image 518
kayjtea Avatar asked Feb 12 '19 20:02

kayjtea


1 Answers

It's not the runtime behavior, but the compile time method overload resolution and covariance here:

context.RemoveRange(existing);

RemoveRange method has two overloads:

RemoveRange(IEnumerable<object> entities)

and

RemoveRange(params object[] entities)

Having class constraint allows C# compiler to pick the overload with IEnumerable<object> - because ICollection<T> is IEnumerable<T>, and IEnumerable<T> for reference type T is covariant, hence is IEnumerable<object>.

Without class constraint, the only available options is the method with params object[] argument. And here comes one of the drawbacks / side effects / traps of params object[] construct - every single argument arg with type other than object[] is treated as object and passed implicitly as new object[] { arg }.

So, in the former case the actual call is

context.RemoveRange((IEnumerable<object>)existing);

while in the later case it is

context.RemoveRange(new object[] { existing });

In other words, the list is passed as object, which leads to the runtime exception in question.

The same applies to all other Range methods of the DbContext class - AddRange, UpdateRange and AttachRange.

like image 56
Ivan Stoev Avatar answered Oct 17 '22 08:10

Ivan Stoev