如何用nHibernate添加NOLOCK?

时间:2009-08-19 21:33:26

标签: nhibernate fluent-nhibernate criteria

使用nhibernate时如何添加NOLOCK? (标准查询)

7 个答案:

答案 0 :(得分:20)

SetLockMode(LockMode.None)connection.isolation ReadUncomitted不会在您的查询中附加NOLOCK

Ayende进入correct answer on his blog

如果您使用的是<sql-query>,则可以执行以下操作:

<sql-query name="PeopleByName">
    <return alias="person"
                    class="Person"/>
    SELECT {person.*}
    FROM People {person} WITH(nolock)
    WHERE {person}.Name LIKE :name
</sql-query>

请注意WTIH(nolock)子句附加的FROM

答案 1 :(得分:16)

我将解释如何执行此操作,以便您可以添加NOLOCK(或任何其他查询提示),同时仍然使用ICriteria或HQL,而无需将查询的知识粘贴到映射或会话工厂配置中。< / p>

我为NHibernate 2.1编写了这个。有一些主要的警告,主要是由于NHibernate中的“use_sql_comments”打开时的错误(见下文)。我不确定这些错误是否已在NH 3中修复,但尝试一下。 更新:自NH 3.3起,错误尚未得到修复。我在这里描述的技术和解决方法仍然有用。

首先,创建一个拦截器,如下所示:

[Serializable]
public class QueryHintInterceptor : EmptyInterceptor
{
    internal const string QUERY_HINT_NOLOCK_COMMENT = "queryhint-nolock: ";

    /// <summary>
    /// Gets a comment to add to a sql query to tell this interceptor to add 'OPTION (TABLE HINT(table_alias, INDEX = index_name))' to the query.
    /// </summary>
    internal static string GetQueryHintNoLock(string tableName)
    {
        return QUERY_HINT_NOLOCK_COMMENT + tableName;
    }

    public override SqlString OnPrepareStatement(SqlString sql)
    {
        if (sql.ToString().Contains(QUERY_HINT_NOLOCK_COMMENT))
        {
            sql = ApplyQueryHintNoLock(sql, sql.ToString());
        }

        return base.OnPrepareStatement(sql);
    }

    private static SqlString ApplyQueryHintNoLock(SqlString sql, string sqlString)
    {
        var indexOfTableName = sqlString.IndexOf(QUERY_HINT_NOLOCK_COMMENT) + QUERY_HINT_NOLOCK_COMMENT.Length;

        if (indexOfTableName < 0)
            throw new InvalidOperationException(
                "Query hint comment should contain name of table, like this: '/* queryhint-nolock: tableName */'");

        var indexOfTableNameEnd = sqlString.IndexOf(" ", indexOfTableName + 1);

        if (indexOfTableNameEnd < 0)
            throw new InvalidOperationException(
                "Query hint comment should contain name of table, like this: '/* queryhint-nlock: tableName */'");

        var tableName = sqlString.Substring(indexOfTableName, indexOfTableNameEnd - indexOfTableName).Trim();

        var regex = new Regex(@"{0}\s(\w+)".F(tableName));

        var aliasMatches = regex.Matches(sqlString, indexOfTableNameEnd);

        if (aliasMatches.Count == 0)
            throw new InvalidOperationException("Could not find aliases for table with name: " + tableName);

        var q = 0;
        foreach (Match aliasMatch in aliasMatches)
        {
            var alias = aliasMatch.Groups[1].Value;
            var aliasIndex = aliasMatch.Groups[1].Index + q + alias.Length;

            sql = sql.Insert(aliasIndex, " WITH (NOLOCK)");
            q += " WITH (NOLOCK)".Length;
        }
        return sql;
    }

    private static SqlString InsertOption(SqlString sql, string option)
    {
        // The original code used just "sql.Length". I found that the end of the sql string actually contains new lines and a semi colon.
        // Might need to change in future versions of NHibernate.
        var regex = new Regex(@"[^\;\s]", RegexOptions.RightToLeft);
        var insertAt = regex.Match(sql.ToString()).Index + 1;
        return sql.Insert(insertAt, option);
    }
}

然后在某处创建一些不错的扩展方法:

public static class NHibernateQueryExtensions
{
    public static IQuery QueryHintNoLock(this IQuery query, string tableName)
    {
        return query.SetComment(QueryHintInterceptor.GetQueryHintNoLock(tableName));
    }

    public static ICriteria QueryHintNoLock(this ICriteria query, string tableName)
    {
        return query.SetComment(QueryHintInterceptor.GetQueryHintNoLock(tableName));
    }
}

接下来,告诉NHibernate使用你的拦截器:

config.SetInterceptor(new QueryHintInterceptor());

最后,在NHibernate配置中启用 use_sql_comments 属性。

你已经完成了!现在你可以添加如下的nolock提示:

var criteria = Session.CreateCriteria<Foo>()
    .QueryHintNoLock("tableFoo")
    .List<Foo>();

我的工作基于此处描述的技术:http://www.codewrecks.com/blog/index.php/2011/07/23/use-sql-server-query-hints-with-nhibernate-hql-and-icriteria/

NHibernate Showstopping Bugs:

首先,您需要修复this bug NHibernate。 (您可以通过直接修复NHibernate源来修复此错误,或者by doing what I did并创建自己的Dialect修复此问题。)

其次,当您在第一页之后的任何页面上执行分页查询并且您正在使用投影时,似乎还会出现另一个错误。 NHibernate生成的SQL在“OVER”子句中是完全错误的。在这个阶段我不知道如何修复这个bug,但我正在努力。 更新:我详细介绍了如何修复此错误here。与其他bug一样,也可以通过修复NHibernate源代码或创建自己的Dialect类来修复这个bug。

答案 2 :(得分:8)

如果您要在很多查询中使用它,可以通过配置属性connection.isolation将其设置为默认值。

<property name="connection.isolation">ReadUncommitted</property> 

查看documentation on this property

答案 3 :(得分:6)

这不会将NOLOCK添加到我可以告诉的查询中,但它应该提供相同的功能 - 即仅在事务内执行脏读。

Session.BeginTransaction(IsolationLevel.ReadUncommitted);

我使用Sql Profiler来查看上面的命令会做什么,但是它没有改变查询的任何内容或者向它们添加NOLOCK(nhibernate对我的大多数查询使用sp_executesql)。无论如何我还是跑了,似乎所有的僵局都消失了。我们的软件已经运行了3天,现在没有死锁。在此更改之前,我通常可以在15分钟内重现死锁。我不是百分之百地确信它已经修好了,但经过又一周的测试,我会知道更多。

这也适用于其他人:http://quomon.com/NHibernate-deadlock-problem-q43633.aspx

答案 4 :(得分:1)

您可以使用Interceptor解决它。

var session = SessionFactory.OpenSession(new NoLockInterceptor());

这是NoLockInterceptor类的实现。 基本上,NoLockInterceptor类将在选择查询中的每个表名后面插入“WITH(NOLOCK)”提示,由nHibernate生成。

public class NoLockInterceptor : EmptyInterceptor
{
    public override SqlString OnPrepareStatement(SqlString sql)
        {
            //var log = new StringBuilder();
            //log.Append(sql.ToString());
            //log.AppendLine();

            // Modify the sql to add hints
            if (sql.StartsWithCaseInsensitive("select"))
            {
                var parts = sql.ToString().Split().ToList();
                var fromItem = parts.FirstOrDefault(p => p.Trim().Equals("from", StringComparison.OrdinalIgnoreCase));
                int fromIndex = fromItem != null ? parts.IndexOf(fromItem) : -1;
                var whereItem = parts.FirstOrDefault(p => p.Trim().Equals("where", StringComparison.OrdinalIgnoreCase));
                int whereIndex = whereItem != null ? parts.IndexOf(whereItem) : parts.Count;

                if (fromIndex == -1)
                    return sql;

                parts.Insert(parts.IndexOf(fromItem) + 3, "WITH (NOLOCK)");
                for (int i = fromIndex; i < whereIndex; i++)
                {
                    if (parts[i - 1].Equals(","))
                    {
                        parts.Insert(i + 3, "WITH (NOLOCK)");
                        i += 3;
                    }
                    if (parts[i].Trim().Equals("on", StringComparison.OrdinalIgnoreCase))
                    {
                        parts[i] = "WITH (NOLOCK) on";
                    }
                }
                // MUST use SqlString.Parse() method instead of new SqlString()
                sql = SqlString.Parse(string.Join(" ", parts));
            }

            //log.Append(sql);
            return sql;
        }
}

答案 5 :(得分:0)

您可以尝试以下方法:

public class NoLockInterceptor : EmptyInterceptor
{
    /// <summary>
    /// OnPrepare.
    /// </summary>
    /// <param name="sql">Query.</param>
    public override SqlString OnPrepareStatement(SqlString sql)
    {
        var begin = SqlString.Parse("with query as (");
        var end = SqlString.Parse(") select * from query with ( nolock )");

        return base.OnPrepareStatement(begin + sql + end);
    }
}

答案 6 :(得分:0)

我接受了@cbp的答案,并做了一些修改:

private static SqlString ApplyQueryHintNoLock(SqlString sql)
    {
        var sqlString = sql.ToString();

        if (_cache.Get(sqlString) is SqlString cachedSql)
        {
            //return cachedSql;
        }

        var regex1 = new Regex(@" FROM\s+[a-zA-Z1-9_.]*\s+([a-zA-Z1-9_.]*)", RegexOptions.IgnoreCase);
        var regex2 = new Regex(@"(?: INNER JOIN| LEFT OUTER JOIN)\s+[a-zA-Z1-9_.]*\s+([a-zA-Z1-9_.]*) ON", RegexOptions.IgnoreCase);

        var tableAliasMatches = regex1.Matches(sqlString);
        var joinsAliasMatches = regex2.Matches(sqlString);
        var combined = tableAliasMatches.OfType<Match>()
            .Concat(joinsAliasMatches.OfType<Match>())
            .Where(m => m.Success)
            .OrderBy(m=>m.Index);
        var noLockLength = " WITH (NOLOCK)".Length;
        var q = 0;

        foreach (Match aliasMatch in combined)
        {
            var alias = aliasMatch.Groups[1].Value;
            var aliasIndex = aliasMatch.Groups[1].Index + q + alias.Length;

            sql = sql.Insert(aliasIndex, " WITH (NOLOCK)");
            q += noLockLength;
        }

        _cache.Set(sqlString, sql, DateTimeOffset.Now.AddHours(3));

        return sql;
    }

    internal static string GetQueryHintNoLock()
    {
        return _queryHintNoLockCommentString;
    }

这样,它将不对查询中的所有表和内部联接添加任何锁。

这对使用工作单元模式的所有人都有益。