Entity Framework Core DbContext.RemoveRange和类型约束

6

这段代码会抛出异常

系统无法找到实体类型“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);
}

然而,如果您添加类型约束where T: class,就不会抛出异常。为什么会这样呢?我原本以为C#类型约束不会对运行时行为产生影响。两个版本都可以成功编译。

请查看Poke的答案此处 - NaDeR Star
1个回答

9
这里涉及到的不是运行时行为,而是编译时方法重载解析和协变性问题:
context.RemoveRange(existing);

RemoveRange方法有两个重载:

RemoveRange(IEnumerable<object> entities)

并且

RemoveRange(params object[] entities)

拥有类约束使得C#编译器可以选择重载方法 IEnumerable<object>,因为 ICollection<T>IEnumerable<T>,而对于引用类型 TIEnumerable<T> 是协变的,因此是 IEnumerable<object>
如果没有类约束,则唯一可用的选项是带有 params object[] 参数的方法。这里就出现了 params object[] 构造的一个缺点/副作用/陷阱 - 除了 object[] 类型之外的每个参数 arg 都会被隐式地视为 object 并传递为 new object[] { arg }
因此,在前一种情况下,实际调用是:
context.RemoveRange((IEnumerable<object>)existing);

在后一种情况下,它是这样的。
context.RemoveRange(new object[] { existing });

换句话说,该列表被作为对象传递,这导致了所提出的运行时异常。
对于DbContext类的所有其他Range方法 - AddRangeUpdateRangeAttachRange也是同样的情况。

这可能是第二个问题,但从API设计的角度来看,为什么存在第二个重载? - kayjtea
1
@kayjtea 很好的问题,针对 EF Core API 设计者 :) 它允许您使用类似 db.AddRange(new Entrity { }, new Entity { … }, ...) 的东西,而不需要显式地使用 new [] - 基本上是 params 修饰符存在的原因。 - Ivan Stoev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接