6

NHibernate 3.2 と Oracle 11gR2 で先物を使用しようとしています。よくわかりませんが、これはサポートされていないようです。NHibernate Jira でこの問題が見つかりました。これは、Oracle で未来が可能であるように思わせます。Future を Oracle と連携させる方法を知っている人はいますか? Oracle がサポートされていない理由は何ですか?

アップデート

ここのコメントに基づいて、HQL マルチクエリを使用してみました。実行中に例外が発生しまし_nhSession.CreateMultiQuery();た。例外は次のとおりです。

The driver NHibernate.Driver.OracleDataClientDriver does not support multiple queries.

他に何を試すことができますか?間違ったドライバーを使用していませんか?

4

3 に答える 3

8

NHibernate Future クエリを Oracle で動作させる方法を共有したいと思います。以下の 2 つのクラス EnhancedOracleDataClientDriver および EnhancedOracleResultSetsCommand をプロジェクトに追加し、NHibernate を構成して、EnhancedOracleDataClientDriver クラスをデータベース ドライバーとして使用するだけです。このアプローチが他の人にも有効かどうか、フィードバックをいただければ幸いです。上記のクラスのソースコードは次のとおりです。

EnhancedOracleDataClientDriver.cs

using NHibernate.Engine;

namespace NHibernate.Driver
{
    public class EnhancedOracleDataClientDriver : OracleDataClientDriver
    {
        public override bool SupportsMultipleQueries
        {
            get
            {
                return true;
            }
        }

        public override IResultSetsCommand GetResultSetsCommand(ISessionImplementor session)
        {
            return new EnhancedOracleResultSetsCommand(session);
        }
    }
}

EnhancedOracleResultSetsCommand.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using NHibernate.Engine;
using NHibernate.SqlCommand;
using NHibernate.SqlTypes;
using NHibernate.Util;

namespace NHibernate.Driver
{
    public class EnhancedOracleResultSetsCommand : BasicResultSetsCommand
    {
        private const string driverAssemblyName = "Oracle.DataAccess";

        private SqlString sqlString = new SqlString();
        private int cursorCount = 0;
        private readonly PropertyInfo oracleDbType;
        private readonly object oracleDbTypeRefCursor;

        public EnhancedOracleResultSetsCommand(ISessionImplementor session)
            : base(session)
        {
            System.Type parameterType = ReflectHelper.TypeFromAssembly("Oracle.DataAccess.Client.OracleParameter", driverAssemblyName, false);
            oracleDbType = parameterType.GetProperty("OracleDbType");

            System.Type oracleDbTypeEnum = ReflectHelper.TypeFromAssembly("Oracle.DataAccess.Client.OracleDbType", driverAssemblyName, false);
            oracleDbTypeRefCursor = System.Enum.Parse(oracleDbTypeEnum, "RefCursor");
        }

        public override void Append(ISqlCommand command)
        {
            Commands.Add(command);
            sqlString = sqlString.Append("\nOPEN :cursor")
                .Append(Convert.ToString(cursorCount++))
                .Append("\nFOR\n")
                .Append(command.Query).Append("\n;\n");
        }

        public override SqlString Sql
        {
            get { return sqlString; }
        }

        public override IDataReader GetReader(int? commandTimeout)
        {
            var batcher = Session.Batcher;
            SqlType[] sqlTypes = Commands.SelectMany(c => c.ParameterTypes).ToArray();
            ForEachSqlCommand((sqlLoaderCommand, offset) => sqlLoaderCommand.ResetParametersIndexesForTheCommand(offset));

            sqlString = sqlString.Insert(0, "\nBEGIN\n").Append("\nEND;\n");

            var command = batcher.PrepareQueryCommand(CommandType.Text, sqlString, sqlTypes);
            if (commandTimeout.HasValue) {
                command.CommandTimeout = commandTimeout.Value;
            }

            BindParameters(command);

            for (int cursorIndex = 0; cursorIndex < cursorCount; cursorIndex++) {
                IDbDataParameter outCursor = command.CreateParameter();
                oracleDbType.SetValue(outCursor, oracleDbTypeRefCursor, null);
                outCursor.ParameterName = ":cursor" + Convert.ToString(cursorIndex);
                outCursor.Direction = ParameterDirection.Output;
                command.Parameters.Add(outCursor);
            }

            return new BatcherDataReaderWrapper(batcher, command);
        }
    }
}
于 2013-01-05T19:42:00.007 に答える
5

3 年前、私は「NHibernate multi query / futures with Oracle」という質問への回答を投稿し、将来のクエリを Oracle で動作させる方法を説明しました。2 つの派生クラス EnhancedOracleDataClientDriver と EnhancedOracleResultSetsCommand をプロジェクトに追加し、NHibernate を構成して EnhancedOracleDataClientDriver クラスをデータベース ドライバーとして使用するだけでした。

最近、この問題https://nhibernate.jira.com/browse/NH-2170を確認したところ、すぐに使用できる NHibernate が Oracle との先物をまだサポートしていないことがわかりました。さらに、この「強化された」実装アプローチの導出に関する情報源や方法論を共有できるかどうか、StackOverflow で Ruben から質問を受けました。さらに、一部の人々はこの「強化された」アプローチをテストしましたが、SQL Server の将来のようにパフォーマンスの向上が見られなかったという事実に失望しました。

そこで、時間をかけてこの問題を再検討し、「拡張」アプローチのプロファイリングと最適化を試みることにしました。

プロファイラーでの私の調査結果は次のとおりです。

  1. Oracle.ManagedDataAccess プロバイダでは、名前によるパラメータ バインディングの実装は、位置パラメータ バインディングよりも遅くなります。全体で 500 個の名前付きパラメーターを使用して複数の基準をテストしたところ、プロファイラーは、コマンドを実行する前に、Oracle プロバイダーが名前付きパラメーターを位置パラメーターに変換するためだけに 1 秒近く費やしたことを示しました。500 個の名前付きパラメーターを使用する通常の (将来ではない) クエリでさえ、同様のパフォーマンス ペナルティを経験すると思います。したがって、1 つのボトルネックは「command.BindByName = true;」です。
  2. 複数のクエリを 1 つのバッチに結合する場合は、SqlStringBuilder.Add(...) を使用する必要があります (SqlString.Append(...) ではありません)。これは、文字列を結合する場合と同じです。StringBuilder は、String よりもはるかに優れたパフォーマンスを発揮します。

そのため、SQL コマンドが構築されてバッチに結合される NHibernate ソース コードを注意深く分析した後、「強化された」アプローチのバージョン #2 にたどり着きました。NHibernate のコア チームがこれに気付き、私のお気に入りの ORM に Oracle の先物を追加することを検討してくれることを願っています。

ちなみに、「拡張」アプローチは Oracle refcursors (バッチ内のクエリごとに 1 つの出力 refcursor) に依存し、注意しなければならないセッションごとの最大カーソル数の Oracle 制限があります (Oracle XE のデフォルトは最大 300 カーソルです)。 )。

使用法。以下の 2 つのクラス EnhancedOracleManagedDataClientDriver および EnhancedOracleManagedResultSetsCommand をプロジェクトに追加し、NHibernate を構成して、EnhancedOracleManagedDataClientDriver クラスをデータベース ドライバーとして使用します。

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);
        }
    }
}
于 2016-02-04T19:49:06.380 に答える