我一直在寻找一种在BeforeSaveEntity方法中组织验证规则的好方法,我在文件中找到了这条评论:项目中的TodoContextProvider.cs:BreezeMvcSPATemplate:
// A second DbContext for db access during custom save validation.
// "this.Context" is reserved for Breeze save only!
为什么不能使用this.Context?
答案 0 :(得分:10)
很好的问题。答案并不明显,简单介绍并不容易。我会试试。
EFContextProvider
从客户端获取保存数据,并(最终)将这些数据转换为EFContextProvider.Context
内的实体。保存获得批准后,EFContextProvider
会在此EF SaveChanges
上调用Context
方法,并将其所有内容保存为单个交易。
有两个潜在的问题。
永远无法完全信任客户数据。如果您的业务规则限制了授权用户可以查看或更改的内容,则必须将客户端派生的实体与数据库中的相应实体进行比较。
EF Context
不能包含“同一实体”的两个副本。它不能容纳具有相同密钥的两个实体。因此,您无法使用EFContextProvider.Context
来从数据库中获取干净副本并保留带有更改的副本。
你需要第二个Context
来获得干净的副本,你必须编写逻辑来比较EFContextProvider.Context
中要保存的实体的关键值和值第二个Context
中的干净实体。
许多验证不需要将值与干净实体进行比较。
例如,开箱即用的 System.ComponentModel.DataAnnotations
属性(例如Required
和MaxLength
)是简单的数据验证,用于确定实体是否存在是自我一致。要么有价值要么没有价值。该值小于最大长度或不是。对于此类测试,您不需要比较实体。
您可以编写自己的自定义System.ComponentModel.DataAnnotations
属性,以比较单个实体中的数据值。您可能会有一条规则,指出order.InvoiceDate
必须在order.ShipDate
之前或之前。这也是一个自我一致性测试,你也不需要那个比较实体。
如果这些是您关心的唯一验证类型 - 并且您正在使用EF DbContext
- 您可以让EF在 保存处理期间为您运行它们。您不需要第二个Context
。
但跨实体验证是另一个故事。在跨实体验证中,实体“A”仅在实体“B”(可能是“C”,“D”,“E”,......)的某些条件为真时才有效。例如,您可能要求订单商品具有已在数据库中的父订单。
在您验证订单商品时,父订单很可能不在EFContextProvider.Context
。
“没问题,”你说。 “我只会使用someItem.Order
导航到父级。”
不,你不能。首先,它不起作用,因为EFContextProvider.Context
禁用了延迟加载。 EFContextProvider
禁用延迟加载主要是为了在序列化期间中断循环引用,但也防止服务器上的性能查杀"n+1" bugs。
您可以通过随意加载任何实体或相关实体来解决这个问题。但是,您遇到了第二个问题:您为验证加载的实体可能与您尝试在此批次中保存的另一个实体发生冲突。
EFContextProvider
不会立即填充其Context
。它开始逐个验证实体,然后将它们添加到Context
。
继续我们的示例,假设我们在验证期间加载了someItem
的父订单。该订单现在位于EFContextProvider.Context
。
保存过程继续到下一个实体并且......惊讶,惊讶......下一个实体碰巧是完全相同的父订单。 EFContextProvider
尝试将此副本附加到已有副本的Context
(我们刚刚加载的副本)......它不能。
存在冲突。这两个订单中的哪一个属于EFContextProvider
?我们刚刚加载的干净副本用于验证目的......或者是来自客户端并且需要修改的保存副本才能保存?
也许你认为你知道答案。也许我同意。但事实是,EFContextProvider
会引发异常,因为Context
中已有一个包含该密钥的订单。
如果您的所有验证都是自我一致性检查,那么EFContextProvider.Context
就是您所需要的。您不必创建第二个Context
但如果您有涉及其他实体的数据安全问题和/或业务逻辑,则需要第二个Context
...并且您需要足够的EF技能才能使用Context
。
不是Breeze或实体框架的限制。无论您选择何种技术,非平凡的业务逻辑都需要相当的服务器端复杂性。这就是野兽的本性。