微风:当其他人删除了子实体时,它们仍然会在重新加载父实体后出现



我们有一个微风客户端解决方案,在该解决方案中,我们向父实体显示其子实体的列表。我们对一些子实体进行硬删除。现在,当用户进行删除时,没有问题,但当其他人这样做时,似乎没有办法使已经加载在缓存中的子项无效。我们对父级进行了一个新的查询,并扩展到子级,但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);
});

这种方法至少有四个问题:

  1. 每个被查询的实体都是一个重影。如果您的UI显示任何查询的实体,它将显示重影。即使实体在服务器上根本没有被触摸(99%的时间),情况也是如此。太糟糕了。你必须重新绘制整个页面。

    你也许能做到。但在许多方面,这种技术几乎和第一种一样不切实际。这意味着在任何地方进行任何查询后,任何视图都可能处于无效状态。

  2. 分离实体会产生副作用。依赖于您分离的实体的所有其他实体都会立即(a)更改和(b)孤立。正如下文"孤儿"部分所解释的那样,要从中恢复并不容易。

  3. 此技术将清除正在查询的实体中所有挂起的更改。我们很快就会看到如何处理这个问题。

  4. 如果查询由于某种原因失败(连接丢失?),则没有任何内容可显示。除非你记得你删除了什么。。。在这种情况下,如果查询失败,可以将这些实体放回缓存中。

为什么要提到一种实用价值有限的技术?因为这是接近#3的一步,可以实现

尝试#3-这可能真的有效

我将要描述的方法通常被称为"标记和扫描"。

  1. 如前所述,在本地运行查询并计算实体的inCache列表。这一次,不要从缓存中删除这些实体。查询成功后,我们将删除保留在此列表中的实体。。。但现在还没有。

  2. 如果查询的MergeOption是"PreserveChanges"(默认情况下是这样),请从inCache列表中(而不是从管理器的缓存中!)删除所有具有挂起更改的实体。我们这样做是因为无论实体在服务器上的状态如何,这些实体都必须留在缓存中。这就是"保留更改"的含义。

    我们本可以在第二种方法中这样做,以避免删除具有未保存更改的实体。

  3. 订阅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);
    }
    }
    }
    
  4. 如果查询失败,请忘记所有

  5. 如果查询成功

    1. 取消订阅:manager.entityChanged.unsubscribe(handlerId);

    2. 使用孤立检测处理程序订阅

      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
      }
      }
      
    3. 分离保留在inCache列表中的每个实体。

      inCache.slice().forEach(function(e) {
      manager.detachEntity(e);
      });
      
    4. 取消订阅孤立检测处理程序

孤立探测器

分离实体可能会产生副作用。假设我们有一个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

同时,您总是可以创建一个方法,在执行查询之前从缓存中删除相关实体,并让查询(带展开)将它们添加回来。

最新更新