如何使用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;
}
这样一来就会无锁添加到所有的表和内部的查询连接。
这对所有的你使用的工作模式的单位是好的。