Three years ago, I sent an answer to the question "NHibernate multi query / futures with Oracle" with a decision on how to make future queries with Oracle. It was as simple as adding two derived classes of EnhancedOracleDataClientDriver and EnhancedOracleResultSetsCommand to the project and setting up NHibernate to use the EnhancedOracleDataClientDriver class as the database driver.
I recently checked this question https://nhibernate.jira.com/browse/NH-2170 and found out that the finished NHibernate still does not support Oracle futures. Also, I had a question from Ruben on StackOverflow if I can share any sources and / or methodology for getting this “advanced” implementation approach. In addition, some people tested this “advanced” approach and were disappointed with the fact that performance improvements were not seen as with SQL Server futures.
So I decided to spend some time and rethink this problem, trying to profile and optimize the Enhanced approach.
Here are my findings with the profiler:
- In the Oracle.ManagedDataAccess provider, the implementation of parameter binding by name is slower than parameter positioning binding. I tested several criteria with a total of 500 parameters, and the profiler showed me that before the execution, the Oracle team spent almost 1 second converting the named parameters into positional ones. I believe that even regular (not future) requests with the same 500 named parameters are similar to penalties. Thus, one bottleneck is "command.BindByName = true;".
- When combining several requests into one batch, SqlStringBuilder.Add (...) should be used (not SqlString.Append (...)). This is the same as concatenating strings: StringBuilder performs a better path than String.
So, having carefully analyzed the source code of NHibernate, in which SQL commands are built and bundled, I came out with version 2 of the "Advanced" approach. I hope the main NHibernate team will notice this and consider adding Oracle futures to my favorite ORM.
By the way, the Enhanced approach depends on Oracle recursors (one returned refcursor for each request in the batch version), and there is an Oracle limit on the maximum cursors per session that we should be aware of (by default there are a maximum of 300 cursors for Oracle XE).
Using. Add two EnhancedOracleManagedDataClientDriver and EnhancedOracleManagedResultSetsCommand classes 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) {
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)