我们有一个微风客户端解决方案,在该解决方案中,我们向父实体显示其子实体的列表。我们对一些子实体进行硬删除。现在,当用户进行删除时,没有问题,但当其他人这样做时,似乎没有办法使已经加载在缓存中的子项无效。我们对父级进行了一个新的查询,并扩展到子级,但breeze会附加它已经听说过的所有其他子级,即使数据库没有返回它们。
我的问题是:在从数据库中加载结果之前,breeze难道不应该意识到我们正在通过expand加载,从而从缓存中完全删除所有子项吗?如果不是这样的话,我们还能如何做到这一点?
谢谢
删除对于每一项数据管理工作来说都是一个可怕的复杂性。无论你是否使用微风,这都是真的。这只会让人心痛不已。这就是为什么我建议软删除而不是硬删除。
但你不在乎我怎么想。。。所以我会继续。
让我直说吧要正确实现缓存清理方案,没有简单的方法。我将描述我们如何做到这一点(我相信忽略了一些细节),你会明白为什么这很困难,而且在反常的情况下,毫无结果。
当然,最有效的暴力方法是在查询之前先清除缓存。如果你这样做的话,你可能还没有缓存,但我想我会提到它。
"分离"实体问题
在我继续之前,请记住我刚才提到的技术,如果您的UI(或其他任何东西)包含对要删除的实体的引用,那么所有可能的解决方案都是无用的。
哦,你会把它们从缓存中删除的。但是,现在持有对它们的引用的对象将继续具有对处于"分离"状态的实体对象的引用——重影。确保这种情况不会发生是你的责任;Breeze不可能知道,如果它知道的话,它也无能为力。
第二次尝试
第二种不那么生硬的方法(周建议)是
- 首先将查询应用于缓存
- 对每个结果进行迭代
- 沿着"展开"路径分离每个子实体
- 拆离该顶级实体
现在,当查询成功时,它就有了填充缓存的清晰道路。
以下是一个与TodoLists及其TodoItems的查询相关的代码的简单示例:
var query = breeze.EntityQuery.from('TodoLists').expand('TodoItems');
var inCache = manager.executeQueryLocally(query);
inCache.slice().forEach(function(e) {
inCache = inCache.concat(e.TodoItems);
});
inCache.slice().forEach(function(e) {
manager.detachEntity(e);
});
这种方法至少有四个问题:
每个被查询的实体都是一个重影。如果您的UI显示任何查询的实体,它将显示重影。即使实体在服务器上根本没有被触摸(99%的时间),情况也是如此。太糟糕了。你必须重新绘制整个页面。
你也许能做到。但在许多方面,这种技术几乎和第一种一样不切实际。这意味着在任何地方进行任何查询后,任何视图都可能处于无效状态。
分离实体会产生副作用。依赖于您分离的实体的所有其他实体都会立即(a)更改和(b)孤立。正如下文"孤儿"部分所解释的那样,要从中恢复并不容易。
此技术将清除正在查询的实体中所有挂起的更改。我们很快就会看到如何处理这个问题。
如果查询由于某种原因失败(连接丢失?),则没有任何内容可显示。除非你记得你删除了什么。。。在这种情况下,如果查询失败,可以将这些实体放回缓存中。
为什么要提到一种实用价值有限的技术?因为这是接近#3的一步,可以实现
尝试#3-这可能真的有效
我将要描述的方法通常被称为"标记和扫描"。
如前所述,在本地运行查询并计算实体的
inCache
列表。这一次,不要从缓存中删除这些实体。查询成功后,我们将删除保留在此列表中的实体。。。但现在还没有。如果查询的
MergeOption
是"PreserveChanges"(默认情况下是这样),请从inCache
列表中(而不是从管理器的缓存中!)删除所有具有挂起更改的实体。我们这样做是因为无论实体在服务器上的状态如何,这些实体都必须留在缓存中。这就是"保留更改"的含义。我们本可以在第二种方法中这样做,以避免删除具有未保存更改的实体。
订阅
EntityManager.entityChanged
事件。在处理程序中,从inCache
列表中删除"已更改的实体",因为该实体由查询返回并合并到缓存中,这一事实告诉它仍然存在于服务器上。这里有一些代码:var handlerId = manager.entityChanged.subscribe(trackQueryResults); function trackQueryResults(changeArgs) { var action = changeArgs.entityAction; if (action === breeze.EntityAction.AttachOnQuery || action === breeze.EntityAction.MergeOnQuery) { var ix = inCache.indexOf(changeArgs.entity); if (ix > -1) { inCache.splice(ix, 1); } } }
如果查询失败,请忘记所有
如果查询成功
取消订阅:
manager.entityChanged.unsubscribe(handlerId);
使用孤立检测处理程序订阅
var handlerId = manager.entityChanged.subscribe(orphanDetector); function orphanDetector(changeArgs) { var action = changeArgs.entityAction; if (action === breeze.EntityAction.PropertyChange) { var orphan = changeArgs.entity; // do something about this orphan } }
分离保留在
inCache
列表中的每个实体。inCache.slice().forEach(function(e) { manager.detachEntity(e); });
取消订阅孤立检测处理程序
孤立探测器
分离实体可能会产生副作用。假设我们有一个Products
,并且每个乘积都有一个Color
。其他一些用户讨厌"红色"。她删除了一些红色的产品,其余的改为"蓝色"。然后她删除"红色"Color
。
您对此一无所知,并无辜地重新查询Colors
。"红色"已经消失,您的清理过程会将其从缓存中分离出来。缓存中的每个Product
都立即被修改。Breeze不知道新的Color
应该是什么,所以它将每个以前的"红色"产品的FKProduct.colorId
设置为零。
没有id为0的Color
,因此所有这些产品都处于无效状态(违反引用完整性约束)。它们没有Color
父级。他们是孤儿。
两个问题:你怎么知道这件事发生在你身上,你该怎么办?
检测当您分离"红色"颜色时,微风会更新受影响的产品。
您可以侦听分离过程中引发的PropertyChanged
事件。这就是我在代码示例中所做的。理论上(我认为"事实上"),在分离过程中唯一可能触发PropertyChanged
事件的是"孤立"副作用。
你是做什么的
- 是否使孤立项处于无效、已修改的状态
- 对于已删除的"红色",是否恢复到同样无效的前
colorId
- 刷新孤立项以获得其新的颜色状态(或者发现它已被删除)
没有好的答案。在前两个选项中,你有自己的选择。我可能会选择第二个,因为它看起来破坏性最小。这将使产品处于"未更改"状态,指向不存在的Color
。
当您查询最新产品时,其中一个产品引用了缓存中没有的新
Color
("香蕉"),情况不会更糟。
"刷新"选项在技术上似乎是最好的。它很笨重。它可以很容易地级联成一长串异步查询,可能需要很长时间才能完成。
我们无法把握完美的解决方案。
鬼魂呢
哦,对了。。。您的UI可能仍然显示您分离的(较少的)实体,因为您认为它们已在服务器上删除。你必须从UI中删除这些"幽灵"。
我相信你会想办法把它们去掉的。但你必须首先了解它们是什么。
您可以迭代显示的每个实体,看看它是否处于"已分离"状态。恶心!
更好的是,我认为如果清理机制发布了一个(自定义?)事件,其中包含您在清理过程中分离的实体列表。。。并且该列表是CCD_ 22。然后,您的订阅者知道哪些实体必须从显示中删除。。。并且可以适当地进行响应。
哇!我肯定忘了什么。但现在你明白了问题的严重性。
服务器通知怎么样
这确实有可能。如果你可以安排服务器在任何实体被删除时通知客户端,那么这些信息可以在你的UI中共享,你也可以采取措施消除这些累赘。
这是一个有效的点,但目前我们从未因为查询而从本地缓存中删除实体。但是这是一个合理的请求,所以请将此添加到微风用户语音中。https://breezejs.uservoice.com/forums/173093-breeze-feature-suggestions
同时,您总是可以创建一个方法,在执行查询之前从缓存中删除相关实体,并让查询(带展开)将它们添加回来。