Как добавить NOLOCK с nHibernate?

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

Ответ 1

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.

Ответ 2

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

Я написал это для 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 Showstopping Bugs:

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

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

Ответ 3

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

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

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

Ответ 4

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

Session.BeginTransaction(IsolationLevel.ReadUncommitted);

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

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

Ответ 5

Вы можете решить эту проблему с помощью 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;
        }
}

Ответ 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);
    }
}

Ответ 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;
    }

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

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