How do add NOLOCK with nHibernate?

后端 未结 7 1195
再見小時候
再見小時候 2020-11-29 03:43

How do you add NOLOCK when using nhibernate? (criteria query)

相关标签:
7条回答
  • 2020-11-29 04:03

    If you are going to use it in a lot of your queries, you can set it as default via the configuration property connection.isolation.

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

    Check out the documentation on this property.

    0 讨论(0)
  • 2020-11-29 04:06

    SetLockMode(LockMode.None) or connection.isolation ReadUncomitted does NOT append a NOLOCK to your queries.

    Ayende goes into the correct answer on his blog:

    If you're using <sql-query> you can do the following:

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

    Note the WTIH(nolock) appended to the FROM clause.

    0 讨论(0)
  • 2020-11-29 04:10

    You can try this:

    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);
        }
    }
    
    0 讨论(0)
  • 2020-11-29 04:13

    I have taken @cbp answer and changed it a bit:

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

    this way it will add no lock to all tables and inner joins in the query.

    this is good for all of you using the unit of work pattern.

    0 讨论(0)
  • 2020-11-29 04:17

    You can solve it by using Interceptor.

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

    Here is the implementation for the NoLockInterceptor class. Basically the NoLockInterceptor class will insert "WITH (NOLOCK)" hint after each table names in the select query, generated by 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 讨论(0)
  • 2020-11-29 04:19

    I'll explain how to do this so that you can add NOLOCK (or any other query hints), whilst still using ICriteria or HQL, and without having to stick knowledge of your queries into the mappings or session factory configuration.

    I wrote this for NHibernate 2.1. There are a number of major caveats with it, mostly due to bugs in NHibernate when "use_sql_comments" is turned on (see below). I'm not sure if these bugs have been fixed in NH 3, but try it out. UPDATE: Bugs have not been fixed as of NH 3.3. The technique and workarounds I describe here still work.

    Firstly, create an interceptor, like this:

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

    Then create some nice extension methods somewhere:

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

    Next, tell NHibernate to use your interceptor:

    config.SetInterceptor(new QueryHintInterceptor());
    

    Finally, enable the use_sql_comments property in your NHibernate configuration.

    And you're done! Now you can add nolock hints like this:

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

    I based this work around the technique described here: http://www.codewrecks.com/blog/index.php/2011/07/23/use-sql-server-query-hints-with-nhibernate-hql-and-icriteria/

    NHibernate Showstopping Bugs:

    Firstly, there is this bug with NHibernate that you will need to fix. (You can either fix this bug by repairing the NHibernate source directly, or by doing what I did and creating your own Dialect which repairs the issue).

    Secondly, there is another bug which seems to occur when you do a paged query, on any page after the first page, and you are using projections. The sql generated by NHibernate is completely wrong around the "OVER" clause. At this stage I don't know how to fix this bug but I'm working on it. UPDATE: I have detailed how to fix this bug here. Like the other bug, this one can also be fixed either by repairing the NHibernate source code or by creating your own Dialect class.

    0 讨论(0)
提交回复
热议问题