Как добавить NOLOCK с помощью nHibernate? - PullRequest
22 голосов
/ 20 августа 2009

Как добавить NOLOCK при использовании nhibernate? (критерий запроса)

Ответы [ 7 ]

20 голосов
/ 18 ноября 2010

SetLockMode(LockMode.None) или connection.isolation ReadUncomitted НЕ добавляет NOLOCK к вашим запросам.

Айенде приводит правильный ответ в своем блоге :

Если вы используете <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.

16 голосов
/ 12 декабря 2011

Я объясню, как это сделать, чтобы вы могли добавить NOLOCK (или любые другие подсказки запроса), все еще используя ICriteria или HQL, и не добавляя знания о ваших запросах в конфигурацию сопоставлений или фабрики сеансов.

Я написал это для 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());

Наконец, включите свойство use_sql_comments в конфигурации NHibernate.

И все готово! Теперь вы можете добавить подсказки 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:

Во-первых, есть эта ошибка с NHibernate, которую вам нужно будет исправить. (Вы можете исправить эту ошибку, исправив источник NHibernate напрямую, или , выполнив то, что я сделал и создав собственный диалект, который исправит проблему).

Во-вторых, есть другая ошибка, которая возникает, когда вы выполняете постраничный запрос на любой странице после первой страницы и используете проекции. SQL, сгенерированный NHibernate, полностью неверен в предложении «OVER». На данном этапе я не знаю, как исправить эту ошибку, но я работаю над этим. ОБНОВЛЕНИЕ: Я подробно описал, как исправить эту ошибку здесь . Как и в случае с другой ошибкой, эту ошибку также можно исправить, исправив исходный код NHibernate или создав собственный класс Dialect.

8 голосов
/ 24 ноября 2009

Если вы собираетесь использовать его во многих своих запросах, вы можете установить его по умолчанию через свойство конфигурации connection.isolation.

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

Проверьте документацию по этому свойству .

6 голосов
/ 19 января 2013

Это не добавляет NOLOCK к вашим запросам, что я могу сказать, но оно должно обеспечивать такую ​​же функциональность - то есть выполнять грязное чтение только внутри транзакции.

Session.BeginTransaction(IsolationLevel.ReadUncommitted);

Я использовал Sql Profiler, чтобы посмотреть, что будет делать приведенная выше команда, но она ничего не изменила в запросе и не добавила к ним NOLOCK (nhibernate использует sp_executesql для большинства моих запросов). Я все равно побежал с ним, и, похоже, все тупики ушли. Наше программное обеспечение работает уже 3 дня без блокировок. До этого изменения я обычно мог воспроизвести тупики в течение 15 минут. Я не уверен на 100%, что это исправили, но после еще нескольких недель испытаний я узнаю больше.

Это сработало и для других: http://quomon.com/NHibernate-deadlock-problem-q43633.aspx

1 голос
/ 15 сентября 2016

Вы можете решить это с помощью перехватчика.

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;
        }
}
0 голосов
/ 02 декабря 2018

Я взял ответ @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;
    }

таким образом он не добавит блокировку ко всем таблицам и внутренним соединениям в запросе.

Это хорошо для всех вас, использующих единицу работы.

0 голосов
/ 15 октября 2018

Вы можете попробовать это:

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