I am trying to use futures in NHibernate 3.2 and Oracle 11gR2. This doesn't seem to be supported although I'm not sure. I found this issue on NHibernate Jira that makes it seem like futures are possible with Oracle. Does anyone know how to get futures to work with Oracle? What exactly is the reason that Oracle isn't supported?
Based on comments here, I tried using HQL multiquery. I got an exception while performing _nhSession.CreateMultiQuery();
Here's the exception:
The driver NHibernate.Driver.OracleDataClientDriver does not support multiple queries.
What else can I try? Am I using the wrong driver?
Three years ago I posted an answer to the "NHibernate multi query / futures with Oracle" question with a solution how to make future queries work with Oracle. It was as simple as adding two derived classes EnhancedOracleDataClientDriver and EnhancedOracleResultSetsCommand to a project, and configuring NHibernate to use the EnhancedOracleDataClientDriver class as the database driver.
I recently checked this issue https://nhibernate.jira.com/browse/NH-2170, and figured out that out-of-box NHibernate still does not support futures with Oracle. Plus, I got a question from Ruben on StackOverflow if I can share any sources and/or methodology about deriving this "Enhanced" implementation approach. Plus, some people tested this "Enhanced" approach and were disappointed with the fact that no performance boost noticed as with SQL Server futures.
So I decided to spend some time and revisit this issue trying to profile and optimize the "Enhanced" approach.
Here are my findings with the profiler:
So, after carefully analysing NHibernate source code where SQL commands are constructed and combined into batches, I came out with the version #2 of the "Enhanced" approach. I hope that NHibernate core team will notice this and consider adding futures with Oracle to my favourite ORM.
By the way, the "Enhanced" approach depends on Oracle refcursors (one output refcursor for each query in a batch), and there is Oracle limitation of max cursors per session that we must be aware of (default on Oracle XE is max 300 cursors).
Usage. Add the below two classes EnhancedOracleManagedDataClientDriver and EnhancedOracleManagedResultSetsCommand to your project and configure NHibernate to use the EnhancedOracleManagedDataClientDriver class as the database driver.
EnhancedOracleManagedDataClientDriver.cs
using System;
using System.Data;
using System.Reflection;
using NHibernate.Engine;
using NHibernate.SqlTypes;
using NHibernate.Util;
namespace NHibernate.Driver
{
public class EnhancedOracleManagedDataClientDriver : OracleManagedDataClientDriver
{
private readonly PropertyInfo _oracleCommandBindByName;
private readonly PropertyInfo _oracleDbType;
private readonly object _oracleDbTypeRefCursor;
public EnhancedOracleManagedDataClientDriver()
{
_oracleCommandBindByName = ReflectHelper.TypeFromAssembly(
"Oracle.ManagedDataAccess.Client.OracleCommand", "Oracle.ManagedDataAccess", true).GetProperty("BindByName");
_oracleDbType = ReflectHelper.TypeFromAssembly(
"Oracle.ManagedDataAccess.Client.OracleParameter", "Oracle.ManagedDataAccess", true).GetProperty("OracleDbType");
var enumType = ReflectHelper.TypeFromAssembly(
"Oracle.ManagedDataAccess.Client.OracleDbType", "Oracle.ManagedDataAccess", true);
_oracleDbTypeRefCursor = Enum.Parse(enumType, "RefCursor");
}
public override bool SupportsMultipleQueries => true;
public override IResultSetsCommand GetResultSetsCommand(ISessionImplementor session)
{
return new EnhancedOracleManagedResultSetsCommand(session);
}
protected override void InitializeParameter(IDbDataParameter dbParam, string name, SqlType sqlType)
{
// this "exotic" parameter type will actually mean output refcursor
if (sqlType.DbType == DbType.VarNumeric)
{
dbParam.ParameterName = FormatNameForParameter(name);
dbParam.Direction = ParameterDirection.Output;
_oracleDbType.SetValue(dbParam, _oracleDbTypeRefCursor, null);
}
else
base.InitializeParameter(dbParam, name, sqlType);
}
protected override void OnBeforePrepare(IDbCommand command)
{
base.OnBeforePrepare(command);
if (command.CommandText.StartsWith("\nBEGIN -- multi query\n"))
{
// for better performance, in multi-queries,
// we switch to parameter binding by position (not by name)
this._oracleCommandBindByName.SetValue(command, false, null);
command.CommandText = command.CommandText.Replace(":p", ":");
}
}
}
}
EnhancedOracleManagedResultSetsCommand.cs
using System.Data;
using System.Linq;
using NHibernate.Engine;
using NHibernate.Impl;
using NHibernate.Loader.Custom;
using NHibernate.Loader.Custom.Sql;
using NHibernate.SqlCommand;
using NHibernate.SqlTypes;
using NHibernate.Type;
namespace NHibernate.Driver
{
public class EnhancedOracleManagedResultSetsCommand : BasicResultSetsCommand
{
private readonly SqlStringBuilder _sqlStringBuilder = new SqlStringBuilder();
private SqlString _sqlString = new SqlString();
private QueryParameters _prefixQueryParameters;
private CustomLoader _prefixLoader;
public EnhancedOracleManagedResultSetsCommand(ISessionImplementor session)
: base(session) {}
public override SqlString Sql => _sqlString;
public override void Append(ISqlCommand command)
{
if (_prefixLoader == null)
{
var prefixQuery = (SqlQueryImpl)((ISession)Session)
// this SQL query fragment will prepend every SELECT query in multiquery/multicriteria
.CreateSQLQuery("\nOPEN :crsr \nFOR\n")
// this "exotic" parameter type will actually mean output refcursor
.SetParameter("crsr", 0, new DecimalType(new SqlType(DbType.VarNumeric)));
_prefixQueryParameters = prefixQuery.GetQueryParameters();
var querySpecification = prefixQuery.GenerateQuerySpecification(_prefixQueryParameters.NamedParameters);
_prefixLoader = new CustomLoader(new SQLCustomQuery(querySpecification.SqlQueryReturns, querySpecification.QueryString,
querySpecification.QuerySpaces, Session.Factory), Session.Factory);
}
var prefixCommand = _prefixLoader.CreateSqlCommand(_prefixQueryParameters, Session);
Commands.Add(prefixCommand);
Commands.Add(command);
_sqlStringBuilder.Add(prefixCommand.Query);
_sqlStringBuilder.Add(command.Query).Add("\n;\n\n");
}
public override IDataReader GetReader(int? commandTimeout)
{
var batcher = Session.Batcher;
var sqlTypes = Commands.SelectMany(c => c.ParameterTypes).ToArray();
ForEachSqlCommand((sqlLoaderCommand, offset) => sqlLoaderCommand.ResetParametersIndexesForTheCommand(offset));
_sqlStringBuilder.Insert(0, "\nBEGIN -- multi query\n").Add("\nEND;\n");
_sqlString = _sqlStringBuilder.ToSqlString();
var command = batcher.PrepareQueryCommand(CommandType.Text, _sqlString, sqlTypes);
if (commandTimeout.HasValue)
command.CommandTimeout = commandTimeout.Value;
BindParameters(command);
return new BatcherDataReaderWrapper(batcher, command);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With