使用带有输出参数的实体框架调用 Oracle 存储过程?

Posted

技术标签:

【中文标题】使用带有输出参数的实体框架调用 Oracle 存储过程?【英文标题】:Calling Oracle stored procedure using Entity Framework with output parameter? 【发布时间】:2016-12-26 12:53:00 【问题描述】:

我有一个简单的 Oracle 存储过程,它传入三个参数,并有一个输出参数:

CREATE OR REPLACE PROCEDURE RA.RA_REGISTERASSET
(
    INPROJECTNAME IN VARCHAR2
    ,INCOUNTRYCODE IN VARCHAR2
    ,INLOCATION IN VARCHAR2
    ,OUTASSETREGISTERED OUT VARCHAR2
)
AS
BEGIN
  SELECT 
      INPROJECTNAME || ', ' || INLOCATION || ', ' || INCOUNTRYCODE
  INTO
      OUTASSETREGISTERED
  FROM
      DUAL;     
END RA_REGISTERASSET;

我正在尝试使用 Entity Framework 6.1 来取回 OutAssetRegistered 值,但是,在调用 SqlQuery 后我得到一个空值,无一例外:

public class CmdRegisterAssetDto

        public string inProjectName  get; set; 
        public string inCountryCode  get; set; 
        public string inLocation  get; set; 
        public string OutAssetRegistered  get; set; 

//--------------------------------------------- ---------------

string projectName = "EXCO";
string location = "ANYWHERE";
string countryCode = "XX";

using (var ctx = new RAContext())

    var projectNameParam = new OracleParameter("inProjectName", OracleDbType.Varchar2, projectName, ParameterDirection.Input);
    var countryCodeParam = new OracleParameter("inCountryCode", OracleDbType.Varchar2, countryCode, ParameterDirection.Input);
    var locationParam = new OracleParameter("inLocation", OracleDbType.Varchar2, location, ParameterDirection.Input);
    var assetRegisteredParam = new OracleParameter("OutAssetRegistered", OracleDbType.Varchar2, ParameterDirection.Output);

    var sql = "BEGIN RA.RA_RegisterAsset(:inProjectName, :inCountryCode, :inLocation, :OutAssetRegistered); END;";
    var query = ctx.Database.SqlQuery<CmdRegisterAssetDto>(sql, projectNameParam, countryCodeParam, locationParam, assetRegisteredParam
    );

    assetRegistered = (string)assetRegisteredParam.Value;

我一直在努力让这个工作无济于事,检查了不同的博客,所有其他的 crud 操作都有效,谁能帮助并指导我哪里出错了?

【问题讨论】:

我有类似的要求,你能解决吗? 我遇到了同样的问题,如果您找到了解决方案,请告诉我。 【参考方案1】:

在这种情况下,你不应该打电话:

var query = ctx.Database.SqlQuery<CmdRegisterAssetDto>(sql, projectNameParam, countryCodeParam, locationParam, assetRegisteredParam);

而是调用:

var result = ctx.Database.ExecuteSqlCommand(sql, projectNameParam, countryCodeParam, locationParam, assetRegisteredParam);

请注意,唯一有效的区别是 SqlQuery&lt;CmdRegisterAssetDto&gt; 被替换为 ExecuteSqlCommand。这也意味着 DTO 是不必要的。否则,您的代码看起来应该可以工作。这是您的完整原始代码以及我提到的更改:

string projectName = "EXCO";
string location = "ANYWHERE";
string countryCode = "XX";

using (var ctx = new RAContext())

    var projectNameParam = new OracleParameter("inProjectName", OracleDbType.Varchar2, projectName, ParameterDirection.Input);
    var countryCodeParam = new OracleParameter("inCountryCode", OracleDbType.Varchar2, countryCode, ParameterDirection.Input);
    var locationParam = new OracleParameter("inLocation", OracleDbType.Varchar2, location, ParameterDirection.Input);
    var assetRegisteredParam = new OracleParameter("OutAssetRegistered", OracleDbType.Varchar2, ParameterDirection.Output);

    var sql = "BEGIN RA.RA_RegisterAsset(:inProjectName, :inCountryCode, :inLocation, :OutAssetRegistered); END;";
    var result = ctx.Database.ExecuteSqlCommand(sql, projectNameParam, countryCodeParam, locationParam, assetRegisteredParam);

    assetRegistered = (string)assetRegisteredParam.Value;

为了证明我的理论,我复制了您遇到的空行为,然后进行了一次更改。它挂了一会儿(可能是为了让 EF 启动),但此后每次都快速执行。在每种情况下,我都在 out 参数中找到了一个等待值。

如果有人遇到了麻烦,有一个简单的变体可以为您处理脚本细节:

string projectName = "EXCO";
string location = "ANYWHERE";
string countryCode = "XX";

using (var ctx = new RAContext())
using (var cmd = ctx.Database.Connection.CreateCommand())

    cmd.CommandType = CommandType.StoredProcedure;
    cmd.CommandText = "RA.RA_REGISTERASSET";

    var projectNameParam = new OracleParameter("inProjectName", OracleDbType.Varchar2, projectName, ParameterDirection.Input);
    var countryCodeParam = new OracleParameter("inCountryCode", OracleDbType.Varchar2, countryCode, ParameterDirection.Input);
    var locationParam = new OracleParameter("inLocation", OracleDbType.Varchar2, location, ParameterDirection.Input);
    var assetRegisteredParam = new OracleParameter("OutAssetRegistered", OracleDbType.Varchar2, ParameterDirection.Output);
    cmd.Parameters.AddRange(new[]  projectNameParam, countryCodeParam, locationParam, assetRegisteredParam );

    cmd.Connection.Open();
    var result = cmd.ExecuteNonQuery();
    cmd.Connection.Close();

    assetRegistered = (string)assetRegisteredParam.Value;

作为事后的想法,如果您在之后立即调用查询(即query.FirstOrDefault()),从技术上讲,您可以使用原始解决方案。查询的返回值将始终为 null,但您的 out 参数至少会被填充。这是因为 EF 查询使用延迟执行。

【讨论】:

【参考方案2】:

如果您需要传入和/或传出日期:

        public DateTime GetDate(string dateIn)
        
            Regex regex = new Regex(@"(\d4-\d2-\d2T\d2:\d2:\d2Z)");

            if (!regex.IsMatch(dateIn))
            
                throw new ArgumentException($"dateIn must be given in the YYYY-MM-DD\"T\"hh24:mi:ss\"Z\" format. " +
                    $"Value given: dateIn");
            

            OracleParameter dateInParam = new OracleParameter("DATE_IN_", dateIn);
            createdParam.OracleDbType = OracleDbType.TimeStampTZ;
            OracleParameter dateOutParam= new OracleParameter("DATE_OUT_", OracleDbType.Date, System.Data.ParameterDirection.Output);

            var sql = "BEGIN " +
                $"Get_Date(:DATE_IN_, TO_TIMESTAMP_TZ('dateIn','YYYY-MM-DD\"T\"hh24:mi:ss\"Z\"'), :DATE_OUT_ ); " +
                "END;";

#pragma warning disable EF1000 // Possible SQL injection vulnerability.
            context.Database.ExecuteSqlCommand(sql, dateInParam, dateOutParam);
#pragma warning restore EF1000 // Possible SQL injection vulnerability.

            return (DateTime)(OracleDate)dateOutParam.Value;
        

【讨论】:

以上是关于使用带有输出参数的实体框架调用 Oracle 存储过程?的主要内容,如果未能解决你的问题,请参考以下文章

oracle中怎么执行带有输出参数的存储过程,在程序中我知道怎么调用,

从 Java 调用带有数组输出参数的 Oracle 存储过程

实体框架和 Oracle 客户端 - 存储过程问题

如何使用实体框架在 oracle 包中调用存储过程?

使用实体框架获取存储过程输出参数抛出映射错误:数据读取器与指定的不兼容

Hibernate调用带有输入参数,输出参数为cursor的存储过程