实体框架核心DbContext.RemoveRange和类型约束

kay*_*tea 4 c# generics entity-framework-core

此代码抛出异常

System.InvalidOperationException:找不到实体类型"List <..>".确保已将实体类型添加到模型中.

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

但是,如果添加类型约束,则where T: class不会引发异常.为什么是这样?我的印象是C#类型约束没有影响这样的运行时行为.两个版本编译都很好.

Iva*_*oev 7

这不是运行时行为,而是编译时方法重载分辨率和协方差:

context.RemoveRange(existing);
Run Code Online (Sandbox Code Playgroud)

RemoveRange 方法有两个重载:

RemoveRange(IEnumerable<object> entities)
Run Code Online (Sandbox Code Playgroud)

RemoveRange(params object[] entities)
Run Code Online (Sandbox Code Playgroud)

具有类约束允许C#编译器选择重载IEnumerable<object>- 因为ICollection<T>IEnumerable<T>,并且IEnumerable<T>对于引用类型T是协变的,因此是IEnumerable<object>.

没有类约束,唯一可用的选项是带params object[]参数的方法.这里有一个params object[]构造的缺点/副作用/陷阱- 每个arg类型不同的参数object[]被视为object并隐式传递为new object[] { arg }.

所以,在前一种情况下,实际的呼叫是

context.RemoveRange((IEnumerable<object>)existing);
Run Code Online (Sandbox Code Playgroud)

而在后一种情况下它是

context.RemoveRange(new object[] { existing });
Run Code Online (Sandbox Code Playgroud)

换句话说,列表作为对象传递,这导致了所讨论的运行时异常.

这同样适用于所有其他Range的方法DbContext类- AddRange,UpdateRangeAttachRange.