Nhibernate多个会话之间的并发

时间:2017-04-10 22:45:12

标签: sql-server nhibernate concurrency locking fluent-nhibernate

我有一个由一列预先填充的数字组成的表。我使用Nhibernate的API抓取前10行,其中'Used'标志设置为false。 当多个会话试图从表中获取行时,避免并发问题的最佳方法是什么? 选择行后,我可以将标志列更新为True,以便后续调用不会使用相同的数字。

1 个答案:

答案 0 :(得分:0)

有了这样一般的背景,可以这样做:

// RepeatableRead ensures the read rows does not get concurrently updated by another
// session.
using (var tran = session.BeginTransaction(IsolationLevel.RepeatableRead))
{
    var entities = session.Query<Entity>()
        .Where(e => !e.Used)
        .OrderBy(e => e.Id)
        .Take(10)
        .ToList();
    foreach(var entity in entities)
    {
        e.Used = true;
    }
    // If your session flush mode is not the default one and does not cause
    // commits to flush the session, add a session.Flush(); call before committing.
    tran.Commit();
    return entities;
}

很简单。它可能会因死锁而失败,在这种情况下,您必须丢弃会话,获取新会话,然后重试。

使用乐观更新模式可能是一种替代解决方案,但这也需要一些代码来从失败的尝试中恢复。

使用不会导致死锁风险的无显式锁定解决方案可以做到这一点,但需要更多查询:

const int entitiesToObtain = 10;
// Could initialize here with null instead, but then, will have to check
// for null after the while too.
var obtainedEntities = new List<Entity>();
while (obtainedEntities.Count == 0)
{
    List<Entity> candidates;
    using (var tran = session.BeginTransaction())
    {
        candidatesIds = session.Query<Entity>()
            .Where(e => !e.Used)
            .Select(e => e.Id)
            .OrderBy(id => id)
            .Take(entitiesToObtain)
            .ToArray();
    }
    if (candidatesIds.Count == 0)
        // No available entities.
        break;

    using (var tran = session.BeginTransaction())
    {
        var updatedCount = session.CreateQuery(
            @"update Entity e set e.Used = true
                where e.Used = false
                    and e.Id in (:ids)")
            .SetParameterList("ids", candidatesIds)
            .ExecuteUpdate();
        if (updatedCount == candidatesIds.Length)
        {
            // All good, get them.
            obtainedEntities = session.Query<Entity>()
                .Where(e => candidatesIds.Contains(e.Id))
                .ToList();
            tran.Commit();
        }
        else
        {
            // Some or all of them were no more available, and there
            // are no reliable way to know which ones, so just try again.
            tran.Rollback();
        }
    }
}

根据建议使用NHibernate DML-style operations here。 NHibernate v5.0中提供了strongly typed alternative