SQLServerのストアドプロシージャについて

524 views
Skip to first unread message

志水正幸

unread,
Dec 3, 2019, 11:22:42 PM12/3/19
to DBFluteユーザの集い
志水です。
いつもお世話になっております。

ストアドプロシージャでOUTPUTを宣言して実行すると以下のようなエラーとなるのですが
OUTPUT指定はどのようにすれば良いのでしょうか?
※PmbにはOUTPUT指定したパラメータのプロパティ(セッター・ゲッター)はGenerateされています。

※SQLServerのOUTPUTは下記のようにコマンド指定するみたいなのですが・・・
cmd.Parameters["@Name"].Direction = System.Data.ParameterDirection.Output;



/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_

[Exception]
Seasar.Framework.Exceptions.SQLRuntimeException
[ESSR0071]SQLで例外が発生しました。理由はSystem.Data.SqlClient.SqlException (0x80131904): プロシージャまたは関数 'SP_MKINFO_CS_TOTAL' にはパラメーター '@RES' が必要ですが、指定されませんでした。


[NextException]
System.Data.SqlClient.SqlException
プロシージャまたは関数 'SP_MKINFO_CS_TOTAL' にはパラメーター '@RES' が必要ですが、指定されませんでした。
  ErrorCode = -2146232060
  HelpLink  = 

[Behavior]
TUrikakeHeadBhv.OutsideSql().Call()

[OutsideSqlPath]
SP_MKINFO_CS_TOTAL

[ParameterBean]
SGMDB.DBFlute.ExDao.PmBean.SpMkinfoCsTotalPmb
SpMkinfoCsTotalPmb:{, 2019/12/04, 2019/11/21, 2019/12/20, 2019/12/01, 2019/12/31, }

[Statement]
System.Data.SqlClient.SqlCommand



kubo

unread,
Dec 3, 2019, 11:38:50 PM12/3/19
to DBFluteユーザの集い
jfluteです

志水さん、こんにちは

とりあえず、Java版だとSQLServerのoutputパラメーターは動作していますね。

(BsSpInOutParameterPmb, dbflute-test-dbms-sqlserverより)
public static final String returnValue_PROCEDURE_PARAMETER = "return, -1";
public static final String VInVarchar_PROCEDURE_PARAMETER = "in, 0";
public static final String VOutVarchar_PROCEDURE_PARAMETER = "inout, 1";
public static final String VInoutVarchar_PROCEDURE_PARAMETER = "inout, 2";

outputって付けると、OUTたけじゃなく実質INOUTになるようですね。
内部的にはINOUTパラメーターとして扱われています。


SpMkinfoCsTotalPmbクラスのコードにおいて、INパラメーターとOUTパラメーターで何か違いあります?

また、コードを読まないとわからないと思うので、スタックトレースも貼り付けて頂けると嬉しいです。

志水正幸

unread,
Dec 4, 2019, 12:56:03 AM12/4/19
to DBFluteユーザの集い
志水です。
jfluteさん、ありがとうございます。

以下、宜しくお願い致します。


>SpMkinfoCsTotalPmbクラスのコードにおいてINパラメーターとOUTパラメーターで何か違いあります?
>また、コードを読まないとわからないと思うので、スタックトレースも貼り付けて頂けると嬉しいです。


◆「SpMkinfoCsTotalPmb」です。「Res」がOUTPUTですが見たところINもOUTも区別ないみたいです。
    public class SpMkinfoCsTotalPmb : ProcedurePmb
    {
        public static readonly string res_PROCEDURE_PARAMETER;
        public static readonly string returnValue_PROCEDURE_PARAMETER;
        public static readonly string vardate_PROCEDURE_PARAMETER;
        public static readonly string varmonthFromUriage_PROCEDURE_PARAMETER;
        public static readonly string varmonthFrom_PROCEDURE_PARAMETER;
        public static readonly string varmonthToUriage_PROCEDURE_PARAMETER;
        public static readonly string varmonthTo_PROCEDURE_PARAMETER;
        protected string _res;
        protected int? _returnValue;
        protected string _vardate;
        protected string _varmonthFrom;
        protected string _varmonthFromUriage;
        protected string _varmonthTo;
        protected string _varmonthToUriage;

        public SpMkinfoCsTotalPmb();

        public virtual string ProcedureName { get; }
        public string Res { get; set; }
        public int? ReturnValue { get; set; }
        public string Vardate { get; set; }
        public string VarmonthFrom { get; set; }
        public string VarmonthFromUriage { get; set; }
        public string VarmonthTo { get; set; }
        public string VarmonthToUriage { get; set; }

        public override string ToString();
        protected string ConvertEmptyToNullIfString(string value);
        protected string FilterRemoveEmptyString(string value);
        protected string FormatByteArray(byte[] bytes);
    }


◆スタックトレース
[Exception]
Seasar.Framework.Exceptions.SQLRuntimeException
[ESSR0071]SQLで例外が発生しました。理由はSystem.Data.SqlClient.SqlException (0x80131904): プロシージャまたは関数 'SP_MKINFO_CS_TOTAL' にはパラメーター '@Res' が必要ですが、指定されませんでした。
   場所 System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   場所 System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   場所 System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   場所 System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   場所 System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted)
   場所 System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
   場所 System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry)
   場所 System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry)
   場所 System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   場所 Seasar.Framework.Util.CommandUtil.ExecuteNonQuery(IDataSource dataSource, IDbCommand cmd) 場所 C:\ProjectX\Source\s2container.net-1.4.0-RC3\source\Seasar\Seasar.Framework.Util\CommandUtil.cs:行 66
ClientConnectionId:4adca8b4-b072-4308-980f-00c8ab646689
Error Number: 201、State: 4、Class: 16







2019年12月4日水曜日 13時38分50秒 UTC+9 jflute:

kubo

unread,
Dec 4, 2019, 1:03:56 AM12/4/19
to DBFluteユーザの集い
jfluteです

ありがとうございます。

> ◆「SpMkinfoCsTotalPmb」です。「Res」がOUTPUTですが見たところINもOUTも区別ないみたいです。

コード上で区別がないと、フレームワークで区別付かないはずなので、
自動生成側でどうにか区別付けなきゃいけないという感じですが...

一方で、どう区別させればいいのか、そもそもフレームワーク側の仕様を把握しないとですね。

> ◆スタックトレース

もう少し、先までないでしょうか?
System.Data.SqlClient.SqlCommand.ExecuteNonQuery()で止まってしまっていますが、
恐らくS2Dao.NETのクラスを経由すると思うので、そこのコードを読んで、
上記のフレームワーク側の仕様を把握されたほうが良いかなと。
(どの程度S2Dao.NETで、どの程度DBFlute.NETが拡張しているのか?など)

志水正幸

unread,
Dec 4, 2019, 4:50:51 AM12/4/19
to DBFluteユーザの集い
志水です。


すいません、スタックトレース ですがデバッグで止めてみても
これ以上のものはでていませんでした。

「@Res」のパラメータの内容を確認したところDirectionには
InputOUTPUTとなっており、他の入力パラメータはINPUTのみでした。



以上、宜しくお願い致します。
無題.png

kubo

unread,
Dec 4, 2019, 6:01:02 AM12/4/19
to DBFluteユーザの集い
jfluteです

> すいません、スタックトレース ですがデバッグで止めてみても
> これ以上のものはでていませんでした。

あら、C#ってそういうものでしたっけ?
(それとも、どこかで例外チェーンが切れちゃってたり!?)


ひとまず、InternalProcedureCommand.cs, InternalProcedureHandler.cs を追ってみると良いです。
(これらはDBFluteの自動生成クラスで、もしこっちで処理されているのであれば分析しやすいです)

HandleOutParameters()というメソッドもあるようなので、
そこでどう挙動しているか?ってところがポイントになりそうですね。

kubo

unread,
Dec 4, 2019, 7:16:29 AM12/4/19
to DBFluteユーザの集い
jfluteです

自動生成されたPmbクラスですが...

> public class SpMkinfoCsTotalPmb : ProcedurePmb
> {
> public static readonly string res_PROCEDURE_PARAMETER;
> public static readonly string returnValue_PROCEDURE_PARAMETER;
> public static readonly string vardate_PROCEDURE_PARAMETER;
> public static readonly string varmonthFromUriage_PROCEDURE_PARAMETER;
> public static readonly string varmonthFrom_PROCEDURE_PARAMETER;
> public static readonly string varmonthToUriage_PROCEDURE_PARAMETER;
> public static readonly string varmonthTo_PROCEDURE_PARAMETER;

_PROCEDURE_PARAMETER 系の定数の値って、何も設定されていないですか?

BsParameterBean.vmnet を見ると、何かしら値は入るような気がするのですが。
具体的には、"カラム名, inとかoutとか" で初期化されるはずなのですが。

// -------------------------------------------------
// Procedure Parameter
// -------------------
#foreach ($propertyName in $database.getPmbMetaDataPropertySet($pmbClassName))
#set ($annotationPropName = $database.initUncap(${propertyName}))
#set ($propertyType =
$database.getPmbMetaDataPropertyType($pmbClassName, $propertyName))
#set ($columnName =
$database.getPmbMetaDataPropertyColumnName($pmbClassName,
$propertyName))
#if ($database.isPmbMetaDataPropertyOptionProcedureParameterIn($pmbClassName,
$propertyName))
public static readonly String
${annotationPropName}_PROCEDURE_PARAMETER = "${columnName}, in";
#elseif ($database.isPmbMetaDataPropertyOptionProcedureParameterOut($pmbClassName,
$propertyName))
public static readonly String
${annotationPropName}_PROCEDURE_PARAMETER = "${columnName}, out";
#elseif ($database.isPmbMetaDataPropertyOptionProcedureParameterInOut($pmbClassName,
$propertyName))
public static readonly String
${annotationPropName}_PROCEDURE_PARAMETER = "${columnName}, inout";
#elseif ($database.isPmbMetaDataPropertyOptionProcedureParameterReturn($pmbClassName,
$propertyName))
public static readonly String
${annotationPropName}_PROCEDURE_PARAMETER = "${columnName}, return";
#end
#end
#end

志水正幸

unread,
Dec 4, 2019, 9:09:34 PM12/4/19
to DBFluteユーザの集い
志水です。

すいません。
今の問題となってるプロジェクトは本体プロジェクトを共通プロジェクト参照
していてDLL参照しているところから見てしまっていたので
定義情報しかとれてませんでした。
本体のぞいたらちゃんと設定ありました。。





using System;
using System.Collections.Generic;
using System.Text;

using SGMDB.DBFlute.AllCommon;
using SGMDB.DBFlute.AllCommon.CBean.OutsideSql;
using SGMDB.DBFlute.AllCommon.CBean.COption;

namespace SGMDB.DBFlute.ExDao.PmBean {

    /// <summary>
    /// The parametaer-bean of SpMkinfoCsTotalPmb.
    /// Author: DBFlute(AutoGenerator)
    /// </summary>
    [System.Serializable]
    public partial class SpMkinfoCsTotalPmb : ProcedurePmb {

        // ===============================================================================
        //                                                                      Definition
        //                                                                      ==========
        // -------------------------------------------------
        //                               Procedure Parameter
        //                               -------------------
        public static readonly String returnValue_PROCEDURE_PARAMETER = "RETURN_VALUE, return";
        public static readonly String vardate_PROCEDURE_PARAMETER = "VARDATE, in";
        public static readonly String varmonthFrom_PROCEDURE_PARAMETER = "VARMONTH_FROM, in";
        public static readonly String varmonthTo_PROCEDURE_PARAMETER = "VARMONTH_TO, in";
        public static readonly String varmonthFromUriage_PROCEDURE_PARAMETER = "VARMONTH_FROM_URIAGE, in";
        public static readonly String varmonthToUriage_PROCEDURE_PARAMETER = "VARMONTH_TO_URIAGE, in";
        public static readonly String res_PROCEDURE_PARAMETER = "RES, inout";

        // ===============================================================================
        //                                                                       Attribute
        //                                                                       =========
        protected int? _returnValue;
        protected String _vardate;
        protected String _varmonthFrom;
        protected String _varmonthTo;
        protected String _varmonthFromUriage;
        protected String _varmonthToUriage;
        protected String _res;
    
        // ===============================================================================
        //                                                        Procedure Implementation
        //                                                        ========================
        public virtual String ProcedureName { get {
            return "SP_MKINFO_CS_TOTAL";
        }}

        // ===============================================================================
        //                                                                   Assist Helper
        //                                                                   =============
        protected String ConvertEmptyToNullIfString(String value) {
            return FilterRemoveEmptyString(value);
        }

        protected String FilterRemoveEmptyString(String value) {
            return ((value != null && !"".Equals(value)) ? value : null);
        }

        protected String FormatByteArray(byte[] bytes) {
            return "byte[" + (bytes != null ? bytes.Length.ToString() : "null") + "]";
        }

        // ===============================================================================
        //                                                                  Basic Override
        //                                                                  ==============
        public override String ToString() {
            StringBuilder sb = new StringBuilder();
            sb.Append("SpMkinfoCsTotalPmb:");
            sb.Append(xbuildColumnString());
            return sb.ToString();
        }
        private String xbuildColumnString() {
            String c = ", ";
            StringBuilder sb = new StringBuilder();
            sb.Append(c).Append(_returnValue);
            sb.Append(c).Append(_vardate);
            sb.Append(c).Append(_varmonthFrom);
            sb.Append(c).Append(_varmonthTo);
            sb.Append(c).Append(_varmonthFromUriage);
            sb.Append(c).Append(_varmonthToUriage);
            sb.Append(c).Append(_res);
            if (sb.Length > 0) { sb.Remove(0, c.Length); }
            sb.Insert(0, "{").Append("}");
            return sb.ToString();
        }

        // ===============================================================================
        //                                                                        Accessor
        //                                                                        ========
        public int? ReturnValue {
            get { return _returnValue; }
            set { _returnValue = value; }
        }

        public String Vardate {
            get { return (String)ConvertEmptyToNullIfString(_vardate); }
            set { _vardate = value; }
        }

        public String VarmonthFrom {
            get { return (String)ConvertEmptyToNullIfString(_varmonthFrom); }
            set { _varmonthFrom = value; }
        }

        public String VarmonthTo {
            get { return (String)ConvertEmptyToNullIfString(_varmonthTo); }
            set { _varmonthTo = value; }
        }

        public String VarmonthFromUriage {
            get { return (String)ConvertEmptyToNullIfString(_varmonthFromUriage); }
            set { _varmonthFromUriage = value; }
        }

        public String VarmonthToUriage {
            get { return (String)ConvertEmptyToNullIfString(_varmonthToUriage); }
            set { _varmonthToUriage = value; }
        }

        public String Res {
            get { return (String)ConvertEmptyToNullIfString(_res); }
            set { _res = value; }
        }

    }
}









2019年12月4日水曜日 21時16分29秒 UTC+9 jflute:

kubo

unread,
Dec 5, 2019, 12:15:20 AM12/5/19
to DBFluteユーザの集い
jfluteです

> public static readonly String varmonthToUriage_PROCEDURE_PARAMETER = "VARMONTH_TO_URIAGE, in";
> public static readonly String res_PROCEDURE_PARAMETER = "RES, inout";

したら、ちゃんと区別付いていますね。(inoutとして認識されています)
自動生成のコードとしては、恐らく期待通りなのかなと。
(実行時のフレームワーク側が、これらを正しく処理しているのであれば)

これらフィールドアノテーションが、
S2DaoMetaDataFactoryImpl.InternalProcedureMetaDataFactory にて解析され、
InternalProcedureHandler で、その情報を元に処理されているのだと思います。

その辺を、詳しく追ってみると良いと思います。
ちゃんとOUTパラメーターとして処理されているのか?
されていたとしてもどこかでギャップがあるとか?
などなど

志水正幸

unread,
Dec 5, 2019, 3:17:38 AM12/5/19
to DBFluteユーザの集い
志水です。

すいません、毎度のことながら中身が
何をしているかがちょっと理解が及ばないので
流れを追ってみました。

>ちゃんとOUTパラメーターとして処理されているのか?
>されていたとしてもどこかでギャップがあるとか?
OUTパラメーターとしては認識されているようです。



★GetProcedureParameterType
 →            } else if (type.Equals("inout")) {
                ppt.IsInType = true;
                ppt.IsOutType = true;
                ppt.ParameterDirectionType = ParameterDirection.InputOutput;

★InternalProcedureHandler 
※通過順に①から番号を振っていみました。

            try {
                try {
①                    dbCommand = PrepareCallableStatement(conn, this.Sql);
                } catch (Exception e) {
                    HandleDbException(e, dbCommand, false);
                }
                Object returnValue = null;
                if (_procedureMetaData.HasReturnParameterType) {
②                    Type returnType = _procedureMetaData.ReturnParameterType;
③                    String returnParamName = BindReturnValues(dbCommand, "RetValue", GetDbValueType(returnType));
                    try {
 ⓸                       BindParamters(dbCommand, dto);
                    } catch (Exception e) {
                        HandleDbException(e, dbCommand, false);
                    }
 ⑤                   ExecuteNonQuery(dbCommand);
                    IDbDataParameter param = (IDbDataParameter)dbCommand.Parameters[returnParamName];
                    returnValue = param.Value;
                } else {
                    try {
                        BindParamters(dbCommand, dto);
                    } catch (Exception e) {
                        HandleDbException(e, dbCommand, false);
                    }
                    ExecuteNonQuery(dbCommand);
                }
                try {
                    return HandleOutParameters(dbCommand, dto, returnValue);
                } catch (Exception e) {
                    HandleDbException(e, dbCommand, false);
                    return null; // Unreachable!
                }
            } finally {
                try {
⑥                    Close(dbCommand);
                } finally {
⑦                    Close(conn);
                }
            }


⑤続き
        public static int ExecuteNonQuery(IDataSource dataSource, IDbCommand cmd)
        {
            try
            {
①                dataSource.SetTransaction(cmd);
②                return cmd.ExecuteNonQuery();
            }
            catch (Exception ex)
            {
③Abend →→→→            throw new SQLRuntimeException(ex, cmd.CommandText);
            }
        }





2019年12月5日木曜日 14時15分20秒 UTC+9 jflute:

kubo

unread,
Dec 5, 2019, 3:40:59 AM12/5/19
to DBFluteユーザの集い
jfluteです

ありがとうございます。

HandleOutParameters()の中でどう処理されているか見ると良いと思います。

OUTとして認識はされているけれども、ちゃんとADO.NETに設定されているかどうか?ってところで。

志水正幸

unread,
Dec 5, 2019, 4:48:57 AM12/5/19
to DBFluteユーザの集い
志水です。



 >HandleOutParameters()の中でどう処理されているか見ると良いと思います。
えーと、上述の⑤で異常終了しているのでここには来てないですorz




2019年12月5日木曜日 17時40分59秒 UTC+9 jflute:

kubo

unread,
Dec 5, 2019, 7:59:03 AM12/5/19
to DBFluteユーザの集い
jfluteです

> えーと、上述の⑤で異常終了しているのでここには来てないですorz

おっ、なるほど。
HandleOutParameters() は、実行後のOutパラメーターの受け取り処理をしていますね。
したら焦点は、BindParamters() の方ですね。

該当のパラメーターが、ADO.NET的に期待される設定のされ方がされているか確認してみると良いと思います。
(ADO.NET的に期待される設定のされ方がパッとわからないですが...)


protected void BindParamters(IDbCommand command, Object dto) {
int size = _procedureMetaData.ParameterTypeSize;
for (int i = 0; i < size; i++) {
InternalProcedureParameterType ppt =
_procedureMetaData.GetParameterType(i);
if (ppt.IsReturnType) {
continue;
}
String parameterName = ppt.ParameterName;
InternalBindVariableType vt = GetBindVariableType(command);
switch (vt) {
case InternalBindVariableType.QuestionWithParam:
parameterName = "?" + parameterName;
break;
case InternalBindVariableType.ColonWithParam:
if ("OracleCommand".Equals(command.GetType().Name)) {
parameterName = string.Empty + parameterName;
} else {
parameterName = ":" + parameterName;
}
break;
default:
parameterName = "@" + parameterName;
break;
}

DbType dbType = GetDbValueType(ppt.ParameterPropertyType);
IDbDataParameter parameter = command.CreateParameter();
parameter.ParameterName = parameterName;
parameter.Direction = ppt.ParameterDirectionType;
parameter.Value = ppt.GetValue(dto);
parameter.DbType = dbType;

// If this setting is valid on MySQL, the exception occured.
if (!"MySqlCommand".Equals(command.GetType().Name)) {
parameter.Size = 4096;
}

if ("OleDbCommand".Equals(command.GetType().Name) && dbType ==
DbType.String) {
OleDbParameter oleDbParam = parameter as OleDbParameter;
oleDbParam.OleDbType = OleDbType.VarChar;
} else if ("SqlCommand".Equals(command.GetType().Name) &&
dbType == DbType.String) {
SqlParameter sqlDbParam = parameter as SqlParameter;
sqlDbParam.SqlDbType = SqlDbType.VarChar;
}
command.Parameters.Add(parameter);
}
}

protected InternalBindVariableType GetBindVariableType(IDbCommand cmd) {
String name = cmd.GetType().Name;
if ("SqlCommand".Equals(name) || "DB2Command".Equals(name)) {
return InternalBindVariableType.AtmarkWithParam;
} else if ("OracleCommand".Equals(name)) {
return InternalBindVariableType.ColonWithParam;
} else if ("MySqlCommand".Equals(name)) {
return InternalBindVariableType.QuestionWithParam;
} else if ("NpgsqlCommand".Equals(name)) {
return InternalBindVariableType.ColonWithParam;
} else if ("FbCommand".Equals(name)) {
return InternalBindVariableType.Question;
} else {
return InternalBindVariableType.Question;
}
}

public enum InternalBindVariableType {
None,
AtmarkWithParam,
Question,
QuestionWithParam,
ColonWithParam
}

kubo

unread,
Dec 5, 2019, 8:35:40 AM12/5/19
to DBFluteユーザの集い
jfluteです

> [NextException]
> System.Data.SqlClient.SqlException
> プロシージャまたは関数 'SP_MKINFO_CS_TOTAL' にはパラメーター '@RES' が必要ですが、指定されませんでした。
> ErrorCode = -2146232060
> HelpLink =

からすると、BindParamters()メソッドで...
パラメーター "RES" が処理されていない、もしくは、間違っている、
というのをまず疑いたいところですね。

> ※SQLServerのOUTPUTは下記のようにコマンド指定するみたいなのですが・・・
> cmd.Parameters["@Name"].Direction = System.Data.ParameterDirection.Output;

なるほど、細かく実行処理を追って、そのように設定されているか?確認してみる良いでしょう。

kubo

unread,
Dec 5, 2019, 12:25:58 PM12/5/19
to DBFluteユーザの集い
jfluteです

プロシージャを呼ぶってなかなか他ではやってる人も少なく、現象が分析できる人も少ないと思うので、
この機能を使っていくのであれば、志水さんも仕組み部分を把握しておいたほうが良いと思い、
簡単ですがちょっとまとめてみました。(あまり、明日(金曜)はレスできないかもなので)


[ざっくりと流れ]

1. Dao初期化時: ParameterBean(pmb)のフィールドアノテーションを解析 (ProcedureMetaData)
2. Dao実行時: その情報を元に、パラメーターをバインドする (ProcedureHandler)
3. Dao実行時: プロシージャを実行する (ProcedureHandler)


[登場人物]

InternalProcedureMetaData: pmbを解析した結果を保持する人
InternalProcedureMetaDataFactory: pmbを解析する人 (↑MetaDataを生成)
InternalFieldProcedureAnnotationReader: フィールドアノテーションを読む人

InternalProcedureCommand: プロシージャの実行コマンド (Handlerを呼ぶ人)
InternalProcedureHandler: プロシージャの実行を実際にする人 (BindParamters()など)

S2DaoMetaDataExtension: Daoを解析した結果を保持する人、InternalProcedureCommandを保持
S2DaoMetaDataFactoryImpl: Dao解析する人 (↑DaoMetaDataを生成・保持),
InternalProcedureMetaDataFactoryを定義

まあ覚えられるものではないので、またこの辺を見るべきときが来たらこのメモで思い出すことができればと。
プロジェクトのドキュメントのどこかに転記しておくと良いですね。(このメールスレッドへのリンクでもいいですし)

志水正幸

unread,
Dec 9, 2019, 4:15:31 AM12/9/19
to DBFluteユーザの集い
志水です。

申し訳ないです。
レス遅くなりました。
今日、デモサイトオープン日だったので先日から準備忙しくて・・・

たぶんこれが原因かな?という箇所が判明しました。
前レスで言っていたパラメータに着目してみたところ
>> cmd.Parameters["@Name"].Direction = System.Data.ParameterDirection.Output; 

添付の画像のとおり該当箇所を変更して実行したところ
現状でているエラーは解消されて、「@Res」に内容が返却されてきました。

「System.Data.ParameterDirection.InputOutput」を設定している箇所は下記「GetProcedureParameterType」だと思うのですが
このパラメータ設定を行っているソース部分をデバッグで止めようとしても止まらないし
ソースを追っていこうとしても何故か結構な頻度でデバッグモードが落ちてアプリケーションが終了してしまい
これ以上の内容の確認がちょっと難しい状況です。
「   } else if (type.Equals("out")) { 」に判定が入ればいいとはおもんですが・・

「System.Data.ParameterDirection.InputOutput」を「System.Data.ParameterDirection.Output」にするには
どう修正すればいいでしょうか?



        protected InternalProcedureParameterType GetProcedureParameterType(Type pmbType, PropertyInfo property) {
            InternalProcedureParameterInfo info = _annotationReader.GetProcedureParameter(pmbType, property);
            if (info == null) {
                return null;
            }
            String name = info.ParameterName;
            String type = info.ParameterType;
            type = type.ToLower();
            InternalProcedureParameterType ppt = new InternalProcedureParameterType(name, property);
            if (type.Equals("in")) {
                ppt.IsInType = true;
                ppt.ParameterDirectionType = ParameterDirection.Input;
            } else if (type.Equals("out")) {
                ppt.IsOutType = true;
                ppt.ParameterDirectionType = ParameterDirection.Output;
            } else if (type.Equals("inout")) {
                ppt.IsInType = true;
                ppt.IsOutType = true;
                ppt.ParameterDirectionType = ParameterDirection.InputOutput;
            } else if (type.Equals("return")) {
                // *Set false to IsOutType
                // The return is not out-parameter at ADO.NET!
                // though JDBC treats it as out-parameter.
                // ppt.IsOutType = true;
                ppt.IsReturnType = true;
                ppt.ParameterDirectionType = ParameterDirection.ReturnValue;
            } else {
                throw new IllegalStateException("The parameter type is wrong: type=" + type);
            }
            return ppt;
        }













志水正幸

unread,
Dec 9, 2019, 4:16:31 AM12/9/19
to DBFluteユーザの集い


添付貼り忘れていました。。。



2019年12月9日月曜日 18時15分31秒 UTC+9 志水正幸:
パラメータ.png

kubo

unread,
Dec 9, 2019, 12:58:31 PM12/9/19
to DBFluteユーザの集い
jfluteです

おお、だいぶ分析進みましたね。
なるほど、DirectionでOutputを指定すると動作したということですね。


まず前提として...

だいぶ昔ですが、SQLServerのストアドプロシージャを分析したとき、
「SQLServerでは、OUT と INOUT の区別がなく、outputと宣言すれば INOUT になる」
という感じでした。実際に検証するとそのように挙動しました。
JDBCドライバのメタデータも、OUT ではなく INOUT で戻ってきます。

https://github.com/dbflute-test/dbflute-test-dbms-sqlserver
の replace-schema-50-procedure.sql より:

-- #df:begin#
create procedure dbo.SP_IN_OUT_PARAMETER
@v_in_varchar varchar(10)
, @v_out_varchar varchar(10) output
, @v_inout_varchar varchar(10) output
as
set @v_out_varchar = @v_inout_varchar
set @v_inout_varchar = @v_in_varchar
-- #df:end#

ゆえに、DBFluteでストアドプロシージャのpmbを作成すると、
outputパラメーターに対して、INOUT で処理されます。
具体的には、_PROCEDURE_PARAMETER フィールドの値が inout になります。


ですが、今回.NETの方で、INOUT指定だと動かなくて、OUT指定だと動いたということですね。
INOUT指定だとエラーメッセージもこのようになると。
System.Data.SqlClient.SqlException
プロシージャまたは関数 'SP_MKINFO_CS_TOTAL' にはパラメーター '@RES' が必要ですが、指定されませんでした。


根本原因はわかりませんが、回避策としては、単純にDBFlute.NETのテンプレートを書き換えて、
outputパラメーターのときに INOUT になるのを、OUT にするというのが考えられます。
具体的には BsParameterBean.vm の _PROCEDURE_PARAMETER のところの inout を out に変更すると。

ただ、ちょっと一つ試してみたいのは、
プロシージャを呼び出す時に、pmbのRESというパラメーターにダミー値を入れてみたらどうなるでしょうか?
例えば...

pmb.SetRes("abc");

とかでOKです。RESが空の状態ではなく、値が入った状態で実行すると。
「パラメーター '@RES' が必要」というエラーメッセージがちょっと気になって、
もしかしたら「INOUTのINに相当する引数の値が指定されていないからエラー」になってたりしないかなって。
仮にそうだとしても、ちょっとダミー値を入れるってのは微妙なので、
先ほどの回避策に頼ることになるとは思いますが、もしよければ試して頂ければと。

志水正幸

unread,
Dec 9, 2019, 10:03:07 PM12/9/19
to DBFluteユーザの集い
志水です。

 >プロシージャを呼び出す時に、pmbのRESというパラメーターにダミー値を入れてみたらどうなるでしょうか? 
おおぅーー!!
jfluteさんナイスです。。
ダミー値を入れて実行したところ動作しました。
初期値NULL値だとダメってことなんですね。
思わずOUTPUTちゃうんとちゃうんかーーー?!おぉぅワレー!!って心の中で叫びました(笑)

無事解決です。
修正なしで良かったです。。。
今回も色々とお騒がせいたしました。
対応本当にありがとうございました。



kubo

unread,
Dec 10, 2019, 7:28:40 AM12/10/19
to DBFluteユーザの集い
jfluteです

おお、確認ありがとうございます。

> ダミー値を入れて実行したところ動作しました。
> 初期値NULL値だとダメってことなんですね。

なんとぅ。Java版(JDBC)だと、ダミー値セットしなくても正常に動作するんですけどね。
ADO.NETだと「outputって、INOUTだからINの値もちゃんと入れるでしょ?」的なんでしょうか...
(もしくは、その辺Java版と少し実装が違ってるところもあるかも!?)

いずれにせよ、とりあえずテンプレートいじらずに動いたということで良かったです。
ただ、そのダミー値の隣には、「なんでダミー値?」のコメントを書いておいてくださいませ。
(このMLでのスレッドのリンクを貼っておくでもいいかも)

志水正幸

unread,
Dec 10, 2019, 8:47:36 AM12/10/19
to DBFluteユーザの集い
志水です。

>なんとぅ。Java版(JDBC)だと、ダミー値セットしなくても正常に動作するんですけどね。
あぁ、Javaやさしい(´;ω;`)
.NET厳しい(´;ω;`)

>ただ、そのダミー値の隣には、「なんでダミー値?」のコメントを書いておいてくださいませ。
そですね。書いておきますー。。。

(人''▽`)ありがとうございましたーー




Reply all
Reply to author
Forward
0 new messages