我们有一个微风客户端解决方案,我们在其中向父实体显示其子女列表。我们对一些子实体进行了严格删除。现在当用户是执行删除的用户时,没有问题,但是当其他人这样做时,似乎无法使已经加载到缓存中的子项无效。我们与父母进行了一个新的查询并扩展到了孩子们,但是微风会附上他们已经听过的所有其他孩子,即使数据库没有返回它们。
我的问题:不应该轻易意识到我们正在通过扩展加载,从而在从db重新加载结果之前完全从缓存中删除所有子节点?如果不是这样,我们还能怎样做到这一点?
谢谢
答案 0 :(得分:7)
删除只是每个数据管理工作的一个可怕的复杂功能。无论你是否使用Breeze,都是如此。它只会导致心痛上下。这就是为什么我建议使用软删除而不是硬删除。
但你不在乎我的想法...所以我会继续。
让我直截了当。 没有简单的方法可以正确实施缓存清理方案。我将描述我们如何做到这一点(有些细节被忽略了,我确定),你会明白为什么它很难,而且在不正常的情况下,没有结果。
当然,最有效的蛮力方法是在查询之前清除缓存。如果你这样做,你可能也没有缓存,但我想我会提到它。
在继续之前,请记住我刚刚提到的技术,如果你的UI(或其他任何东西)持有你想要移除的实体的引用,那么所有可能的解决方案都是无用的。
哦,你可以将它们从缓存中移除。但是现在持有对它们的引用的任何内容都将继续引用一个实体对象,该对象位于" Detached"国家 - 鬼。确保不会发生是你的责任;如果Breeze知道的话,Breeze就不会知道也无法做任何事情。
第二种不那么直率的方法(由Jay建议)是
现在,当查询成功时,您可以有一条明确的道路来填充缓存。
以下是与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的一步,可以起作用
我即将描述的方法通常被称为" Mark and Sweep"。
在本地运行查询并按照上述方法计算inCache
实体列表。这次,不从缓存中删除这些实体。我们将在查询成功后删除此列表中保留的实体 ...但不仅仅是。
如果查询的MergeOption
是" PreserveChanges" (默认情况下),删除具有挂起更改的inCache
列表中的每个实体(不是来自管理器的缓存!)。我们这样做是因为无论服务器上的实体状态如何,这些实体都必须保留在缓存中。那是" PreserveChanges"装置
我们可以在第二种方法中做到这一点,以避免删除未保存更改的实体。
订阅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
。其他一些用户讨厌" red"。她删除了一些红色产品,并将其余部分改为"蓝色"。然后她删除了" red" Color
。
你对此一无所知,无辜地重新查询Colors
。 " red"颜色消失了,你的清理过程将其从缓存中分离出来。立即修改缓存中的每个Product
。 Breeze不知道新的Color
应该是什么,所以它将FK Product.colorId
设置为零,以便每个以前的" red"产品
没有Color
id = 0,因此所有这些产品都处于无效状态(违反参照完整性约束)。他们没有Color
父母。他们是孤儿。
两个问题:你怎么知道这发生在你身上,你做了什么?
<强>检测强> 当您分离&#34; red&#34;时,Breeze会更新受影响的产品。颜色。
您可以侦听分离过程中发生的PropertyChanged
事件。这就是我在代码示例中所做的。理论上(我认为&#34;事实上&#34;),在分离过程中唯一能够触发PropertyChanged
事件的是&#34;孤儿&#34;副作用。
你做什么?
colorId
颜色?没有好的答案。你可以选择前两种选择。我可能会选择第二个,因为它似乎最不具破坏性。这将使产品保持在“不变”状态。 state,指向不存在的Color
。
当您查询最新产品并且其中一个产品引用了您不具备的新
Color
(&#34;香蕉&#34;)时,情况并没有变得更糟在缓存中。
&#34;刷新&#34;选项似乎在技术上是最好的。这很笨重。它很容易级联成长链异步查询,可能需要很长时间才能完成。
完美的解决方案让我们无法理解。
哦,对了......你的UI仍然可以显示你分离的(更少)实体,因为你认为它们已经在服务器上被删除了。你必须删除这些&#34;鬼魂&#34;来自用户界面。
我确定你可以弄明白如何删除它们。但你必须先了解它们是什么。
您可以迭代您正在显示的每个实体,看看它是否在&#34; Detached&#34;州。 YUCK!
更好的我认为,如果清理机制发布了一个(自定义?)事件,其中包含您在清理期间分离的实体列表......该列表是inCache
。然后,您的订户知道哪些实体必须从显示屏中删除......并且可以做出适当的响应。
呼!我确定我已经忘记了什么。但现在您了解问题的各个方面。
这有可能。如果您可以安排服务器在删除任何实体时通知客户端,则可以在您的UI中共享该信息,您可以采取措施删除枯木。
答案 1 :(得分:2)
这是一个有效的观点但是现在我们不会因为查询而从本地缓存中删除实体。但是..这是一个合理的请求,所以请将此添加到微风用户语音。 https://breezejs.uservoice.com/forums/173093-breeze-feature-suggestions
与此同时,您始终可以创建一个方法,在查询执行之前从缓存中删除相关实体,并让查询(使用展开)将其添加回来。