diff --git a/src/NHibernate/Async/Engine/Query/HQLQueryPlan.cs b/src/NHibernate/Async/Engine/Query/HQLQueryPlan.cs
index a5ced8d10d7..e259ad48e46 100644
--- a/src/NHibernate/Async/Engine/Query/HQLQueryPlan.cs
+++ b/src/NHibernate/Async/Engine/Query/HQLQueryPlan.cs
@@ -50,6 +50,7 @@ public async Task PerformListAsync(QueryParameters queryParameters, ISessionImpl
RowSelection selection = new RowSelection();
selection.FetchSize = queryParameters.RowSelection.FetchSize;
selection.Timeout = queryParameters.RowSelection.Timeout;
+ selection.Hint = queryParameters.RowSelection.Hint;
queryParametersToUse = queryParameters.CreateCopyUsing(selection);
}
else
diff --git a/src/NHibernate/Dialect/Dialect.cs b/src/NHibernate/Dialect/Dialect.cs
index 9a4db11880e..ec89f5183b2 100644
--- a/src/NHibernate/Dialect/Dialect.cs
+++ b/src/NHibernate/Dialect/Dialect.cs
@@ -2408,6 +2408,11 @@ public virtual bool SupportsSubSelects
///
public virtual bool SupportsDateTimeScale => false;
+ ///
+ /// Does this dialect support T-SQL query hints
+ ///
+ public virtual bool SupportsQueryHints => false;
+
#endregion
///
@@ -2675,5 +2680,20 @@ public virtual ISQLExceptionConverter BuildSQLExceptionConverter()
// may override to return whatever is most appropriate for that vendor.
return new SQLStateConverter(ViolatedConstraintNameExtracter);
}
+
+ ///
+ /// If the dialect supports T-SQL query hints, this method returns the corresponding SQL.
+ ///
+ /// The input query string to transform
+ /// The query hint string
+ /// Modified query string with the given T-SQL query hint.
+ ///
+ /// This exception is thrown if the dialect does state it supports query hints, but the current dialect
+ /// has no implementation for it.
+ ///
+ public virtual SqlString GetQueryHintString(SqlString queryString, string hint)
+ {
+ throw new NotSupportedException("Dialect does not have support for query hints.");
+ }
}
}
diff --git a/src/NHibernate/Dialect/MsSql2008Dialect.cs b/src/NHibernate/Dialect/MsSql2008Dialect.cs
index d0ef5580389..1e93677dd9c 100644
--- a/src/NHibernate/Dialect/MsSql2008Dialect.cs
+++ b/src/NHibernate/Dialect/MsSql2008Dialect.cs
@@ -1,7 +1,9 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Data;
using NHibernate.Dialect.Function;
using NHibernate.Driver;
+using NHibernate.SqlCommand;
using NHibernate.SqlTypes;
using NHibernate.Util;
using Environment = NHibernate.Cfg.Environment;
@@ -107,5 +109,17 @@ public override SqlType OverrideSqlType(SqlType type)
///
public override bool SupportsDateTimeScale => !KeepDateTime;
+
+ ///
+ public override bool SupportsQueryHints => true;
+
+ ///
+ public override SqlString GetQueryHintString(SqlString queryString, string hint)
+ {
+ if (String.IsNullOrEmpty(hint))
+ return queryString;
+
+ return queryString.Append($" OPTION({hint})");
+ }
}
}
diff --git a/src/NHibernate/Engine/RowSelection.cs b/src/NHibernate/Engine/RowSelection.cs
index bef50f3bc52..b9286abfbac 100644
--- a/src/NHibernate/Engine/RowSelection.cs
+++ b/src/NHibernate/Engine/RowSelection.cs
@@ -18,6 +18,7 @@ public sealed class RowSelection
private int maxRows = NoValue;
private int timeout = NoValue;
private int fetchSize = NoValue;
+ private string hint = null;
///
/// Gets or Sets the Index of the First Row to Select
@@ -62,5 +63,11 @@ public bool DefinesLimits
{
get { return maxRows != NoValue || firstRow > 0; }
}
+
+ public string Hint
+ {
+ get { return hint; }
+ set { hint = value; }
+ }
}
}
diff --git a/src/NHibernate/IQuery.cs b/src/NHibernate/IQuery.cs
index 4a8d062415a..1ac6a933487 100644
--- a/src/NHibernate/IQuery.cs
+++ b/src/NHibernate/IQuery.cs
@@ -646,6 +646,11 @@ public partial interface IQuery
///
IQuery SetResultTransformer(IResultTransformer resultTransformer);
+ ///
+ /// Provide a query hint. Needs support of the underlying dialect.
+ ///
+ IQuery SetHint(string hint);
+
///
/// Get a enumerable that when enumerated will execute
/// a batch of queries in a single database roundtrip
diff --git a/src/NHibernate/Impl/AbstractDetachedQuery.cs b/src/NHibernate/Impl/AbstractDetachedQuery.cs
index b41e705ee6a..37d251245ed 100644
--- a/src/NHibernate/Impl/AbstractDetachedQuery.cs
+++ b/src/NHibernate/Impl/AbstractDetachedQuery.cs
@@ -443,7 +443,8 @@ protected void SetQueryProperties(IQuery q)
.SetReadOnly(readOnly)
.SetTimeout(selection.Timeout)
.SetFlushMode(flushMode)
- .SetFetchSize(selection.FetchSize);
+ .SetFetchSize(selection.FetchSize)
+ .SetHint(selection.Hint);
if (!string.IsNullOrEmpty(comment))
q.SetComment(comment);
if (!string.IsNullOrEmpty(cacheRegion))
diff --git a/src/NHibernate/Impl/AbstractQueryImpl.cs b/src/NHibernate/Impl/AbstractQueryImpl.cs
index ba46b665466..6d6d45fe5b4 100644
--- a/src/NHibernate/Impl/AbstractQueryImpl.cs
+++ b/src/NHibernate/Impl/AbstractQueryImpl.cs
@@ -903,6 +903,12 @@ public IQuery SetFlushMode(FlushMode flushMode)
return this;
}
+ public IQuery SetHint(string hint)
+ {
+ selection.Hint = hint;
+ return this;
+ }
+
public IQuery SetCollectionKey(object collectionKey)
{
this.collectionKey = collectionKey;
diff --git a/src/NHibernate/Linq/IQueryableOptions.cs b/src/NHibernate/Linq/IQueryableOptions.cs
index 049a52d2aa0..6e117139b61 100644
--- a/src/NHibernate/Linq/IQueryableOptions.cs
+++ b/src/NHibernate/Linq/IQueryableOptions.cs
@@ -38,5 +38,12 @@ public interface IQueryableOptions
/// The timeout in seconds.
/// (for method chaining).
IQueryableOptions SetTimeout(int timeout);
+
+ ///
+ /// Set a T-SQL Query hint as an Option to the query
+ ///
+ /// The t-sql query hint
+ /// (for method chaining).
+ IQueryableOptions SetHint(string hint);
}
}
diff --git a/src/NHibernate/Linq/NhQueryableOptions.cs b/src/NHibernate/Linq/NhQueryableOptions.cs
index b6a8a77de7b..050d4c69e25 100644
--- a/src/NHibernate/Linq/NhQueryableOptions.cs
+++ b/src/NHibernate/Linq/NhQueryableOptions.cs
@@ -15,6 +15,7 @@ public class NhQueryableOptions
protected bool? ReadOnly { get; private set; }
protected string Comment { get; private set; }
protected FlushMode? FlushMode { get; private set; }
+ protected string Hint { get; private set; }
#pragma warning disable 618
///
@@ -28,6 +29,9 @@ public class NhQueryableOptions
///
IQueryableOptions IQueryableOptions.SetTimeout(int timeout) => SetTimeout(timeout);
+
+ ///
+ IQueryableOptions IQueryableOptions.SetHint(string hint) => SetHint(hint);
#pragma warning restore 618
///
@@ -125,6 +129,20 @@ public NhQueryableOptions SetFlushMode(FlushMode flushMode)
return this;
}
+ ///
+ /// Set a T-SQL query hint which is appended to the query.
+ ///
+ ///
+ /// The underlying dialect needs support for query hints.
+ ///
+ /// The query hint which should be appended to the query.
+ /// (for method chaining).
+ public NhQueryableOptions SetHint(string hint)
+ {
+ Hint = hint;
+ return this;
+ }
+
protected internal NhQueryableOptions Clone()
{
return new NhQueryableOptions
@@ -135,7 +153,8 @@ protected internal NhQueryableOptions Clone()
Timeout = Timeout,
ReadOnly = ReadOnly,
Comment = Comment,
- FlushMode = FlushMode
+ FlushMode = FlushMode,
+ Hint = Hint
};
}
@@ -161,6 +180,9 @@ protected internal void Apply(IQuery query)
if (FlushMode.HasValue)
query.SetFlushMode(FlushMode.Value);
+
+ if (!string.IsNullOrEmpty(Hint))
+ query.SetHint(Hint);
}
}
}
diff --git a/src/NHibernate/Loader/Hql/QueryLoader.cs b/src/NHibernate/Loader/Hql/QueryLoader.cs
index 5fdda0cde17..ee0f2ae0aea 100644
--- a/src/NHibernate/Loader/Hql/QueryLoader.cs
+++ b/src/NHibernate/Loader/Hql/QueryLoader.cs
@@ -104,6 +104,14 @@ protected override SqlString ApplyLocks(SqlString sql, IDictionary
+ /// Append OPTION(...hints...) clause, if necessary. This
+ /// empty superclass implementation merely returns its first
+ /// argument.
+ ///
+ protected virtual SqlString ApplyHint(SqlString sql, string hints, Dialect.Dialect dialect)
+ {
+ return sql;
+ }
+
///
/// Does this query return objects that might be already cached by
/// the session, whose lock mode may need upgrading.