如何使用nHibernate添加NOLOCK?

如何使用nHibernate添加NOLOCK?,nhibernate,fluent-nhibernate,criteria,Nhibernate,Fluent Nhibernate,Criteria,使用nhibernate时如何添加NOLOCK?(条件查询)如果要在许多查询中使用它,可以通过配置属性connection.isolation将其设置为默认值 <property name="connection.isolation">ReadUncommitted</property> ReadUncommitted 请查看。或不要在查询中附加NOLOCK Ayende进入了: 如果您使用的是,则可以执行以下操作: <sql-query name="Peop

使用nhibernate时如何添加NOLOCK?(条件查询)

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

<property name="connection.isolation">ReadUncommitted</property> 
ReadUncommitted
请查看。

或不要在查询中附加
NOLOCK

Ayende进入了:

如果您使用的是
,则可以执行以下操作:

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

选择{person.*}
来自具有(nolock)的人{person}
WHERE{person}.Name类似于:Name

请注意
WTIH(nolock)
附在
FROM
子句之后。

我将解释如何添加nolock(或任何其他查询提示),同时仍然使用ICriteria或HQL,而不必将查询的知识添加到映射或会话工厂配置中

我是为NHibernate2.1写的。它有许多主要的警告,主要是由于打开“use_sql_comments”时NHibernate中的错误(见下文)。我不确定这些错误是否已在NH3中修复,但请尝试一下更新:截至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);
    }
}
var criteria = Session.CreateCriteria<Foo>()
    .QueryHintNoLock("tableFoo")
    .List<Foo>();
接下来,告诉NHibernate使用拦截器:

config.SetInterceptor(new QueryHintInterceptor());
最后,在NHibernate配置中启用use_sql_comments属性

你完了!现在,您可以添加如下nolock提示:

[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);
    }
}
var criteria = Session.CreateCriteria<Foo>()
    .QueryHintNoLock("tableFoo")
    .List<Foo>();
var-criteria=Session.CreateCriteria()
.QueryHintNoLock(“tableFoo”)
.List();
我的这项工作基于这里描述的技术:

NHibernate显示停止错误:

首先,您需要修复NHibernate。 (您可以通过直接修复NHibernate源代码来修复此bug,也可以创建自己的方言来修复此问题)


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

这不会给查询添加NOLOCK,我可以告诉您,但它应该提供相同的功能—只在事务内执行脏读

Session.BeginTransaction(IsolationLevel.ReadUncommitted);
我使用Sql Profiler查看上述命令的作用,但它没有改变查询的任何内容,也没有向查询添加NOLOCK(nhibernate对我的大多数查询使用sp_executesql)。不管怎样,我还是带着它跑了,看来所有的死锁都消失了。我们的软件已经以这种方式运行了3天,没有死锁。在这个改变之前,我通常可以在15分钟内重现死锁。我并不是100%相信这一点,但经过一周的测试,我会知道更多


这也适用于其他人:

您可以使用拦截器解决它

var session = SessionFactory.OpenSession(new NoLockInterceptor());
下面是NoLockInterceptor类的实现。 基本上,NoLockInterceptor类将在由nHibernate生成的select查询中的每个表名之后插入“WITH(NOLOCK)”提示


公共类NoLockInterceptor:EmptyInterceptor
{
PrepareStatement上的公共重写SqlString(SqlString sql)
{
//var log=新的StringBuilder();
//Append(sql.ToString());
//log.AppendLine();
//修改sql以添加提示
if(sql.startswithcase不敏感(“选择”))
{
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)
返回sql;
零件.插入(零件.索引of(fromItem)+3,“带(NOLOCK)”);
for(int i=fromIndex;i
您可以尝试以下方法:

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);
    }
}
公共类NoLockInterceptor:EmptyInterceptor
{
/// 
///准备好了。
/// 
///询问。
PrepareStatement上的公共重写SqlString(SqlString sql)
{
var begin=SqlString.Parse(“查询为(”);
var end=SqlString.Parse(“)使用(nolock)”从查询中选择*);
返回base.OnPrepareStatement(begin+sql+end);
}
}

我接受了@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;
    }
private静态SqlString ApplyQueryHintNoLock(SqlString-sql)
{
var sqlString=sql.ToString();
if(_cache.Get(sqlString)是sqlString cachedSql)
{
//返回cachedSql;
}
var regex1=new Regex(@“FROM\s+[a-zA-Z1-9.]*\s+([a-zA-Z1-9.]*)”,RegexOptions.IgnoreCase);
var regex2=新的正则表达式(@“(?:内部联接|左外部联接)\s+[a-zA-Z1-9.]*\s+([a-zA-Z1-9.]*)ON”,正则表达式