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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With