【问题标题】:NHibernate multi query / futures with Oracle使用 Oracle 的 NHibernate 多查询/期货
【发布时间】:2012-04-06 16:44:49
【问题描述】:

我正在尝试在 NHibernate 3.2 和 Oracle 11gR2 中使用期货。尽管我不确定,但这似乎不受支持。我在 NHibernate Jira 上找到了this issue,这使得 Oracle 的未来似乎成为可能。有谁知道如何让期货与 Oracle 合作?不支持 Oracle 的具体原因是什么?

更新

基于这里的 cmets,我尝试使用 HQL 多查询。我在执行_nhSession.CreateMultiQuery(); 时遇到了异常,这是异常:

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

我还能尝试什么?我是不是用错驱动了?

【问题讨论】:

  • MultiQuery / MultiCriteria 能帮到您吗?自 NH 3.0 起,Oracle 似乎实际上支持这一点
  • Future 对我来说是最好的。否则我需要做相当多的重构。
  • 用 MultiQuery/Criteria 尝试一个用例,看看它是否受支持,因为期货在内部使用相同的技术 (AFAIK)。
  • 我刚试了一下,遇到了不支持的异常。我已经用新信息更新了我原来的问题。

标签: .net oracle nhibernate orm fluent-nhibernate


【解决方案1】:

我想分享一下我是如何让 NHibernate Future 查询与 Oracle 一起工作的。您只需将以下两个类 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);
        }
    }
}

【讨论】:

  • 有效!我已将此驱动程序添加到我的 DataLayer .dll 程序集中并重新配置应用程序以使用它。
  • 当我将我的 nhibernate 配置为使用您的驱动程序时出现错误。它在 OracleDataClientDriver 的构造函数中抛出 NULL 引用异常。您能否就如何配置 nhibernate 驱动程序提出建议?目前我这样做是这样的: return Fluently.Configure().Database(OracleClientConfiguration.Oracle10.ConnectionString( c => c.Is(_connectionString)).Driver) .ExposeConfiguration(x => x.SetProperty(" adonet.batch_size", "1")).Mappings(mappings);我正在使用 Oracle 11g
  • 谢谢!我不得不在这里寻找有关如何在配置文件stackoverflow.com/questions/5165625/… 中指定类名和程序集名称的提示
  • 您好,Arturas,您能否分享一下您的来源和/或派生此实施方法的方法?
  • 这是否适用于 Futures (QueryOver),还是必须使用 MultiQuery (HQL)?
【解决方案2】:

三年前,我发布了“NHibernate multi query / futures with Oracle”问题的答案,其中包含如何使未来的查询与 Oracle 一起使用的解决方案。 就像在项目中添加两个派生类 EnhancedOracleDataClientDriver 和 EnhancedOracleResultSetsCommand 一样简单,并将 NHibernate 配置为使用 EnhancedOracleDataClientDriver 类作为数据库驱动程序。

我最近检查了这个问题https://nhibernate.jira.com/browse/NH-2170,并发现开箱即用的 NHibernate 仍然不支持 Oracle 的期货。另外,我在 StackOverflow 上收到了 Ruben 的一个问题,我是否可以分享任何有关推导这种“增强”实现方法的资源和/或方法。此外,有些人测试了这种“增强”方法,但对 SQL Server 期货没有发现性能提升感到失望。

所以我决定花一些时间重新审视这个问题,尝试分析和优化“增强”方法。

以下是我使用探查器的发现:

  1. 在 Oracle.ManagedDataAccess 提供程序中,执行 按名称绑定参数比位置参数慢 捆绑。我测试了一个多标准,总共有 500 个名为 参数,探查器向我展示了在执行 命令 Oracle 提供程序仅用于转换就花费了近 1 秒 命名参数转换为位置参数。我相信即使是常规的 (非未来)具有 500 个命名参数的查询会遇到 类似的性能惩罚。所以,一个瓶颈是 “command.BindByName = true;”。
  2. 将多个查询组合成一个批次时, 应该使用 SqlStringBuilder.Add(...) (不是 SqlString.Append(...))。这与组合字符串相同: StringBuilder 的性能比 String 好。

因此,在仔细分析了构建 SQL 命令并将其组合成批处理的 NHibernate 源代码之后,我提出了“增强”方法的版本 #2。我希望 NHibernate 核心团队会注意到这一点,并考虑将 Oracle Futures 添加到我最喜欢的 ORM 中。

顺便说一下,“增强”方法依赖于 Oracle refcursor(一个批处理中的每个查询的一个输出 refcursor),并且我们必须注意每个会话的最大游标数的 Oracle 限制(Oracle XE 上的默认设置是最多 300 个游标)。

用法。将以下两个类 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);
        }
    }
}

【讨论】:

  • 这个解决方案效果很好。只需要首先找出我必须将新驱动程序类型的AssemblyQualifiedName 传递给connection.driver_class 属性。
  • Thomas 说了什么,或者使用 FluentNHConfig + .Driver&lt;EnhancedOracleManagedDataClientDriver&gt;()
  • “我希望 NHibernate 核心团队会注意到这一点,并考虑将 Oracle 的期货添加到我最喜欢的 ORM 中。”:实现这一点的最佳方式是在 github.com/nhibernate/nhibernate-core 上提交 PR。
【解决方案3】:

我无法评论上面的答案:) 所以我对上述实现的反馈在这里:对我来说很好,但性能没有提升。我的测试用例在远程 Oracle 机器上的小表上执行 100 次简单的选择,并且 ToFuture 与 ToList 的使用提供了相似的响应时间。与远程机器上的 MS SQL 相比,ToFuture 提供的响应时间大约是 ToList 的两倍。

【讨论】:

  • 写完反馈后,我尝试运行超过 100 个查询,但我达到了 300 个的限制,对于更多查询,它会引发一般 ADO 异常。我猜这与最大打开游标有关。
猜你喜欢
  • 2011-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多