更改跟踪每个客户端的分离实体或会话?

时间:2014-09-17 11:20:31

标签: c# nhibernate

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

所以我有我的分离实体,当我需要执行任何更改时,我不会直接应用它们,而是将这些更改和分离的实体作为请求传递给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?

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

对于长期存在的每个客户端会话,我可以遇到什么样的并发问题:

  1. 如果我仅使用自己的线程光纤(或锁定)操作每个用户实体?
  2. 如果我从&#34;错误&#34;请求其他用户个人资料。会话(来自该会议的主题)?

2 个答案:

答案 0 :(得分:0)

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

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

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

答案 1 :(得分:0)

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

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

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

如果我在当前会话之外更改了dettached实体的任何属性,则对Session.Lock的下一次调用会抛出异常(特别是如果我更改了集合内容,则异常状态为&#34; reassociated对象有脏集合&#34;)。我在会话之外做了那些更改,因为我不需要保存它们(一些带引用的东西)。

非常奇怪,但是当我两次拨打Lock时,它的效果非常好!

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

同样对于集合:在我找到上面的解决方案之前,我将它们转换为IPersistentCollection并使用了ClearDirty()。

并发性怎么样?我的代码不确定每个线程光纤只更新其用户,除此光纤外的任何人都有对该实体的写访问权。

所以模式是:

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