如何与NHibernate添加NOLOCK?(How do add NOLOCK with nHib

2019-06-18 15:09发布

如何使用NHibernate当您添加NOLOCK? (条件查询)

Answer 1:

SetLockMode(LockMode.None)connection.isolation ReadUncomitted一个不追加NOLOCK您的查询。

Ayende进入在他的博客正确答案 :

如果您使用<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子句。



Answer 2:

我将解释如何做到这一点,这样你可以添加NOLOCK(或任何其他查询提示),而仍然使用的ICriteria或HQL,而不必坚持你查询的知识转化为映射或会话出厂配置。

我写这NHibernate的为2.1。 有许多与它的主要注意事项,主要是由于NHibernate的错误时,“use_sql_comments”开启(见下文)。 我不知道如果这些错误已被固定在NH 3,但尝试一下。 UPDATE:错误没有被固定为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错误:

首先,有这种漏洞与NHibernate,你将需要修复。 (您可以通过直接修复NHibernate的来源,或者修复这个bug 做我所做的和创建自己的方言修复问题)。

其次,有这似乎当你在第一页之后做分页查询,任何页面上出现另一个bug,并且您使用的预测。 通过NHibernate的生成的SQL是围绕“OVER”条款完全错误的。 在这个阶段,我不知道如何解决这个错误,但我的工作就可以了。 更新:我已经详细地描述如何修复这个bug 在这里 。 像其他错误,这个也可以通过两种修复NHibernate的源代码或通过创建自己的方言类固定。



Answer 3:

如果你想在你的很多的查询使用它,你可以将其设置为通过配置属性默认connection.isolation

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

退房此属性的文档 。



Answer 4:

这不添加NOLOCK您的查询,我所知道的,但它应该提供相同的功能 - 这是执行脏读只在事务内。

Session.BeginTransaction(IsolationLevel.ReadUncommitted);

我使用了SQL Profiler来看看上面的命令会做,但它并没有改变任何有关查询或添加NOLOCK他们(NHibernate的使用sp_executesql的大多数我的查询)。 我用它跑无论如何,这似乎所有的死锁都不见了。 我们的软件已经3天,现在这种方式运行无死锁。 这种变化之前,我通常会在15分钟内重现死锁。 我不是100%确信这个固定的,但值得测试的另一个星期后,我就知道了。

这已经工作了其他人也: http://quomon.com/NHibernate-deadlock-problem-q43633.aspx



Answer 5:

您可以通过使用拦截器解决它。

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;
        }
}


Answer 6:

你可以试试这个:

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);
    }
}


Answer 7:

我已采取@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;
    }

这样一来就会无锁添加到所有的表和内部的查询连接。

这对所有的你使用的工作模式的单位是好的。



文章来源: How do add NOLOCK with nHibernate?