使用Oracle的NHibernate多查询/期货

时间:2012-04-06 16:44:50

标签: .net oracle nhibernate orm fluent-nhibernate

我正在尝试在NHibernate 3.2和Oracle 11gR2中使用期货。虽然我不确定,但这似乎并不支持。我在NHibernate Jira上发现了this issue,这使得看起来甲骨文可以实现未来。有谁知道如何让期货与Oracle合作?究竟是什么原因导致Oracle不受支持?

更新

根据此处的评论,我尝试使用HQL multiquery。我在执行_nhSession.CreateMultiQuery();时遇到异常以下是例外情况:

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

我还能尝试什么?我使用了错误的驱动程序吗?

3 个答案:

答案 0 :(得分:8)

我想分享一下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);
        }
    }
}

答案 1 :(得分:5)

三年前,我在Oracle&#34;中发布了对NHibernate多查询/期货的回答。问题与解决方案如何使未来的查询与Oracle一起工作。 就像将两个派生类EnhancedOracleDataClientDriver和EnhancedOracleResultSetsCommand添加到项目中一样简单,并将NHibernate配置为使用EnhancedOracleDataClientDriver类作为数据库驱动程序。

我最近检查了这个问题https://nhibernate.jira.com/browse/NH-2170,并发现开箱即用的NHibernate仍然不支持Oracle的期货。另外,如果我可以分享任何来源和/或方法来推导这个&#34;增强型&#34;我在StackOverflow上从Ruben那里得到了一个问题。实施方法。此外,有些人测试了这个&#34;增强型&#34;接近并且对于没有像SQL Server未来那样注意到性能的事实感到失望。

所以我决定花一些时间重新审视这个问题,试图分析和优化&#34;增强型&#34;方法

以下是我对探查者的调查结果:

  1. 在Oracle.ManagedDataAccess提供程序中,执行 按名称的参数绑定比位置参数慢 捆绑。我测试了一个多标准,总共500个命名 参数,分析器在执行之前向我展示了 命令Oracle提供商花了将近1秒钟进行转换 命名参数为位置参数。我相信即使是经常性的 具有500个命名参数的(非未来)查询将会遇到 类似的性能处罚。所以,一个瓶颈就是 &#34; command.BindByName = true;&#34;。
  2. 将多个查询合并为一个批次时, 应该使用SqlStringBuilder.Add(...)(不是 SqlString.Append(...))。这与组合字符串相同: StringBuilder的执行方式比String更好。
  3. 因此,在仔细分析构建SQL命令并将其组合成批处理的NHibernate源代码之后,我推出了#34;增强型&#34;的版本#2;做法。我希望NHibernate核心团队会注意到这一点,并考虑将Oracle的期货添加到我最喜欢的ORM中。

    顺便说一下,&#34;增强型&#34;方法取决于Oracle refcursors(批处理中每个查询的一个输出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);
            }
        }
    }
    

答案 2 :(得分:0)

我无法对上面的答案发表评论:)所以我对上述实施的反馈意见如下:对我有用,但性能没有提升。我的测试用例在远程Oracle机器上的小表上执行100次简单选择,并使用ToFuture&lt;&gt;与ToList&lt;&gt;提供类似的响应时间。与远程机器上的MS SQL相比,ToFuture&lt;&gt;提供的响应时间比ToList&lt;&gt;的响应时间短两倍。