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.