【问题标题】:Changes tracking for detached entities or session per client?每个客户端的分离实体或会话的更改跟踪?
【发布时间】:2014-11-11 09:45:34
【问题描述】:

我开发了一个具有持久客户端连接(非基于请求)的服务器。当我在内存中跟踪每个连接的客户端状态时,如果我每次需要访问此类客户端数据时都加载实体会很奇怪。

所以我有我的分离实体,当我需要执行任何更改时,我不会直接应用它们,而是将这些更改和分离实体作为请求传递给 GameDb 类。它对此实体执行更改,然后从数据库加载相同的实体以再次对会话拥有的实体执行相同的更改,以便 NH 可以跟踪这些更改。

我可以使用 Merge 但它要慢得多,因为 NH 应该加载所有实体数据(包括可能未修改的惰性集合)来检查每个属性的更改。就我而言,性能至关重要。

一个例子:

    public void GameDb.UpdateTradeOperation(UserOperation operation, int incomeQuantity, decimal price)
    {
        if (operation == null) throw new ArgumentNullException("operation");
        if (operation.Id == 0) throw new ArgumentException("operation is not persisted");
        _operationLogic.UpdateTradeOperation(operation, incomeQuantity, price);

        try
        {
            _factory.Execute(
                s =>
                {
                    var op = s.Get<UserOperation>(operation.Id);
                    _operationLogic.UpdateTradeOperation(op, incomeQuantity, price);

                    if (op.User.BalanceFrozen != operation.User.BalanceFrozen)
                        throw new Exception("Inconsistent balance");
                }); // commits transaction if no exceptions thrown
        }
        catch (Exception e)
        {
            throw new UserStateCorruptedException(operation.User, null, e);
        }
    }

这种方法带来了一些过于复杂的问题,因为我需要两次应用每个更改并检查结果状态是否相等。如果我可以使用 NH Session 来监控实体更改,那就更容易了。但不建议长时间保持 NH 会话打开,我可能会有数千个这样长时间打开的会话。

它还迫使我拆分我的实体和通用逻辑。问题是 GameDb 类不知道它是从哪个上下文调用的,并且不能为其操作请求任何附加数据(例如当前价格或客户端套接字非活动计时器或许多其他东西),或者它可能需要有条件地(由其决定) 向客户端发送一些数据。当然,我可以将一堆委托传递给 GameDb 方法,但在我看来这不是一个好的解决方案。

我可以使用 Session.Lock 来附加我未更改的分离实体,这样我就不需要执行两次更改了吗?我应该使用什么 LockMode?​​p>

我可以在这里使用更好的方法吗?如果我为每个客户端保留一个打开的会话但快速提交或回滚事务,它仍然会打开很多连接吗?事务完成后会话会保持实体状态吗?

长期存在的每个客户端会话可能会遇到什么样的并发问题:

  1. 如果我只从自己的线程光纤(或锁)操作每个用户实体?
  2. 如果我从“错误”会话(来自该会话的线程)请求另一个只读用户配置文件?

【问题讨论】:

    标签: c# nhibernate


    【解决方案1】:

    我认为您需要做的是使用二级缓存,并为每个连接的客户端存储实体 ID,而不是将实体存储在内存中。

    当客户端连接时,您可以使用您存储的 Id 获取实体,这甚至不会在后续请求中访问数据库,因为它将从二级缓存中获取实体,您无需担心更改跟踪。

    http://ayende.com/blog/3976/nhibernate-2nd-level-cache

    【讨论】:

    • 这是一个实时服务器。它可能会非常频繁地使用实体状态,可能每秒十次。例如,它可以在 3D 空间中“移动”该客户端,并且移动速度是存储在该客户端实体内的常数。你说 - 复制一次该值?我有很多!它们现在完美地存储在客户端实体中。 Hibernate 缓存快,但 Dictionary 快,但内存中的对象快得多。
    • 如果您使用二级缓存,您可以通过添加更多服务器来扩展您的解决方案,但是如果二级缓存不够好,我会说您可能必须创建一个队列来存储操作并且每隔一段时间(比如 60 秒)它会合并更改并一次写入。缺点是在最坏的情况下你会在一分钟后知道是否有错误。
    • 最好只更新您更改的实体以及更改的时间。请参阅我自己的答案。
    【解决方案2】:

    我尝试使用 Session.Lock (LockMode.None) 将分离的实体重新附加到新会话,它可以工作。它向会话中添加一个干净且未更改的对象。我可以修改它,它将在下一次事务提交时存储到数据库中。

    这比合并更好,因为 Nhibernate 不需要查看所有属性来找出更改的内容。

    如果我更改了至少一个属性,它会更新整个对象(我的意思是所有属性,但没有集合和实体链接,如果它们没有被触及)。我在实体映射中设置了 DynamicUpdate = true,现在它只更新更改的属性。

    如果我在当前会话之外更改已分离实体的 any 属性,则对 Session.Lock 的下一次调用将引发异常(特别是如果我更改集合内容,则异常状态为“重新关联的对象具有脏集合”)。我在会话之外进行这些更改,因为我不需要保存它们(一些带有引用的东西)。

    很奇怪,但是当我两次调用Lock时它就完美了!

    try
    {
        s.Lock(DbEntity, LockMode.None); // throws
    }
    catch
    {
        s.Lock(DbEntity, LockMode.None); // everything ok
    }
    

    也适用于集合:在找到上述解决方案之前,我将它们转换为 IPersistentCollection 并使用了 ClearDirty()。

    并发呢?我的代码不确定每个线程 Fiber 是否仅更新其用户,并且除了该 Fiber 之外没有人对实体具有写访问权限。

    所以模式是:

    1. 我打开一个会话,获取一个实体并将其存储在内存中的某个位置。
    2. 当我需要读取它的属性时 - 我可以随时快速完成。
    3. 当我想修改它时,我打开一个新会话并对其执行 Lock()。应用更改后,我提交事务并关闭会话。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-23
      • 2012-02-06
      相关资源
      最近更新 更多