[英]How can I resolve a “Do not call overridable methods in constructors” warning for virtual Entity Framework objects?
我正在使用实体框架并想对属性使用延迟加载,所以我将属性设置为virtual
。
一个例子:
public class Child
{
public string Name { get; set; }
}
public class Parent
{
public Parent()
{
Child = new Child();
}
public virtual Child Child { get; set; }
}
当我这样做时,我收到一个CA2214警告:
Severity Code Description Project File Line Suppression State
Warning CA2214 'Parent.Parent()' contains a call chain that results in a call to a virtual method defined by the class. Review the following call stack for unintended consequences:
Parent..ctor()
Parent.set_Child(Child):Void Project C:\Parent.cs 18 Active
我想删除此警告,但如果我将Parent
标记为已sealed
,则会收到预期的错误:
Severity Code Description Project File Line Suppression State
Error CS0549 'Parent.Child.get' is a new virtual member in sealed class 'Parent' Project C:\Parent.cs 24 N/A
那么如何解决这个警告(不忽略它)并仍然使用virtual
?
在属性上使用初始化器而不是构造器。
public class Parent
{
public virtual Child Child { get; set; } = new Child();
}
编辑:关于上述和那个
...有时我需要在构造函数中为
Child
设置属性...
简单的规则是“你可能不应该”。 实体的作用是代表该实体的数据 state,仅此而已。 初始化实体“图”不应在顶级实体的构造函数中完成,而应使用工厂模式。 例如,我使用带有 EF 的存储库模式,我不仅管理 getter,还充当提供 Create 方法的工厂,以及处理软删除场景的 Delete。 这有助于确保始终在“最低限度完整”且有效的 state 中创建具有依赖关系的实体。
甚至我要说的上面的例子也是一个坏例子。 即使它没有触发编译器警告,父构造点的实体也不是完整且有效的 state。 如果我要做类似的事情:
使用 (var context = new MyContext()) { var parent = new Parent(); parent.Name = "我自己"; context.SaveChanges(); }
如果父级自动初始化子级,则 SaveChanges 将要保存该新子级,并且没有任何东西可以确保设置子级上的所有必填字段,则 SaveChanges 调用将失败。 孩子的 state 不够完整。
我主张自动初始化的唯一地方是 collections:
public class Parent
{
public virtual ICollection<Child> Children { get; internal set; } = new List<Child>();
}
以上仍然是“完整的”,因为如果我在不添加任何子项的情况下填充新父项,则空集合不会尝试为子项保存任何内容。 这也很方便,因此当我创建新的父级时,如果我没有子级,我可以选择立即开始添加/关联子级,而不会触发 null 引用异常。
使用工厂方法初始化 object 图形有助于确保实体始终在最低限度完整的 state 中创建,这意味着它们可以立即保存而不会出错。 正如我上面提到的,我通常使用我的存储库作为实体工厂,因为它已经通过工作单元与 DbContext 连接起来,以根据需要解决依赖关系。
例如,如果我可以创建一个 Order 实体,该实体由订单号、客户参考以及保存有效订单所需的产品的一个或多个订单行组成,我的 OrderRepository 可能有一个类似这样的 CreateOrder 方法:
public Order CreateOrder(int customerId, IEnumerable<OrderedProductViewModel> orderedProducts)
{
if (!orderedProducts.Where(x => x.Quantity > 0).Any())
throw new ArgumentException("No products selected.");
var customer = Context.Customers.Single(x => x.CustomerId == customerId);
var products = Context.Products.Where(x => orderedProducts.Where(o => o.Quantity > 0).Select(o => o.ProductId).Contains(x.ProductId)).ToList();
if (products.Count() < orderedProducts.Count())
throw new ArgumentException("Invalid products included in order.");
var order = new Order
{
Customer = customer,
OrderLines = orderedProducts.Select(x => new OrderLine
{
Product = products.Single(p => p.ProductId == x.ProductId),
Quantity = x.Quantity
}
}
Context.Orders.Add(order);
return order;
}
这是我可能使用的工厂方法的上下文示例,以及一些基本验证。 OrderedProductViewModel 有效地表示 ProductId 和 Quantity 的元组。 它与客户 ID 一起代表我允许保存的订单的最小 state。 在订单被认为足够完整可以发货之前,可能会在此方法之外设置其他可选详细信息,但工厂会确保它足够完整以进行保存。
我可以调用如下代码:
using (var contextScope = ContextScopeFactory.Create())
{
var order = OrderRepository.Create(selectedCustomerId, selectedProducts);
contextScope.SaveChanges();
}
那会很高兴。 或者我可以在调用 SaveChanges 之前继续在订单上设置可用信息。 我的 OrderRepository 将没有任何业务逻辑,因为该业务逻辑可能依赖于客户端的配置,并且存储库没有业务知道或关心它; 但它可能依赖于 IOrderValidator 之类的东西来传递新提议的订单,业务逻辑可以通过该订单来断言订单足够有效以进行保存。 存储库/工厂断言它足够完整,但它可以绑定到业务逻辑中的验证器以断言它足够有效。 (即最小/最大订单大小/价值等)
这与 DDD 相结合,我将所有设置器都设置为internal
并在我的实体上使用操作方法有助于控制确保我的实体始终保持在完整的 state 中。 我猜这是你试图通过使用构造函数来确保的东西,所以我想我会分享上面的例子,以提供一些可能的想法和替代方案来实现这一点。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.