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

M-A*_*nes 3 breeze

我们有一个微风客户端解决方案,在其中我们向父实体显示其子项列表.我们对一些子实体进行了严格删除.现在当用户是执行删除的用户时,没有问题,但是当其他人这样做时,似乎无法使已经加载到缓存中的子项无效.我们与父进行了一个新的查询并扩展到子进程,但是breeze附加了它已经听过的所有其他子进程,即使数据库没有返回它们.

我的问题:不应该轻易意识到我们正在通过扩展加载,从而在从db中加载结果之前从缓存中完全删除所有子项?如果不是这样,我们还能怎样做到这一点?

谢谢

War*_*ard 7

是的,这是一个非常好的观点.

删除只是每个数据管理工作的一个可怕的复杂因素.无论你是否使用Breeze,都是如此.它只会导致心痛上下.这就是为什么我建议使用软删除而不是硬删除.

但你不在乎我的想法...所以我会继续.

让我直截了当.没有简单的方法可以正确实现缓存清理方案.我将描述我们如何做到这一点(我肯定会忽略一些细节),你会明白为什么它很难,而且在不正常的情况下,没有结果.

当然,最有效的蛮力方法是在查询之前清除缓存.如果你这样做,你可能也没有缓存,但我想我会提到它.

"独立"实体问题

在我继续之前,请记住我刚刚提到的技术,如果你的UI(或其他任何东西)持有你想要删除的实体的引用,那么所有可能的解决方案都是无用的.

哦,你会从缓存中删除它们.但无论是什么,持有对它们的引用现在将继续引用处于"分离"状态的实体对象 - 鬼.确保不会发生这是你的责任; 如果它确实知道,微风就无法知道也无法做任何事情.

第二次尝试

第二种不那么直率的方法(由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);
});
Run Code Online (Sandbox Code Playgroud)

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

  1. 每个被查询的实体都是幽灵.如果您的UI显示任何查询的实体,它将显示鬼.即使在服务器上没有触及实体(99%的情况下)也是如此.太糟糕了.您必须重新绘制整个页面.

    你或许可以做到这一点.但在许多方面,这种技术几乎与第一种技术一样不切实际.这意味着任何查询发生在任何地方后,视图都处于潜在的无效状态.

  2. 分离实体有副作用.依赖于您分离的实体的所有其他实体会立即(a)更改并且(b)孤立.正如下文"孤儿"部分所述,这并不容易从中恢复.

  3. 此技术会清除您要查询的实体之间的所有挂起更改.我们很快就会看到如何处理.

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

为什么要提一种可能具有有限实用价值的技术?因为这是迈向#3的一步,可以奏效

尝试#3 - 这可能实际上有效

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

  1. 在本地运行查询并计算inCache刚刚描述的实体列表.这个时候,就不会从缓存中删除这些实体.我们将在查询成功后删除保留在此列表中的实体...但不仅仅是.

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

    我们可以在第二种方法中完成此操作,以避免删除未保存更改的实体.

  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);
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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
          }
       }
      
      Run Code Online (Sandbox Code Playgroud)
    3. 分离inCache列表中剩余的每个实体.

      inCache.slice().forEach(function(e) {
          manager.detachEntity(e);
      });
      
      Run Code Online (Sandbox Code Playgroud)
    4. 取消订阅孤儿检测处理程序

孤儿探测器?

分离实体可能会产生副作用.假设我们拥有Products并且每个产品都有Color.其他一些用户讨厌"红色".她删除了一些红色产品,并将其余部分改为"蓝色".然后她删除了"红色" Color.

你对此一无所知,无辜地重新查询Colors."红色"颜色消失,您的清理过程将其从缓存中分离.立即Product修改每个缓存中的每个.Breeze不知道新的Color应该是什么,因此它Product.colorId为每个以前的"红色"产品设置FK 为零.

Colorid = 0 没有,因此所有这些产品都处于无效状态(违反参照完整性约束).他们没有Color父母.他们是孤儿.

两个问题:你怎么知道这发生在你身上,你做了什么?

当您分离"红色"颜色时,检测 Breeze会更新受影响的产品.

您可以听取PropertyChanged分离过程中发生的事件.这就是我在代码示例中所做的.理论上(我认为"实际上"),PropertyChanged在分离过程中唯一可以触发事件的是"孤儿"副作用.

你是做什么?

  • 让孤儿处于无效的修改状态?
  • colorId对于删除的"红色"颜色,还原为同样无效的前者?
  • 刷新孤儿以获得其新的颜色状态(或发现它已被删除)?

没有好的答案.你可以选择前两种选择.我可能会选择第二个,因为它似乎最不具破坏性.这会使产品处于"未更改"状态,指向不存在Color.

当你查询最新的产品并且其中一个引用Color你在缓存中没有的新的("香蕉")时,它并没有差多少.

"刷新"选项在技术上似乎是最好的.这很笨重.它很容易级联成一长串异步查询,可能需要很长时间才能完成.

完美的解决方案逃避了我们的掌握.

鬼魂怎么样?

哦,对了......您的UI仍然可以显示您分离的(更少)实体,因为您认为它们已在服务器上删除.你必须从UI中删除这些"幽灵".

我相信你可以弄明白如何删除它们.但你必须先了解它们是什么.

您可以迭代您正在显示的每个实体,并查看它是否处于"已分离"状态.YUCK!

更好的我认为,如果清理机制发布了一个(自定义?)事件,其中包含您在清理期间分离的实体列表......并且该列表是inCache.然后,您的订户知道哪些实体必须从显示屏中删除......并且可以做出适当的响应.

呼!我确定我忘记了什么.但现在您了解问题的维度.

那么服务器通知呢?

这有可能.如果您可以安排服务器在删除任何实体时通知客户端,则可以在您的UI中共享该信息,您可以采取措施删除枯木.