使用带输出参数的 Entity Framework 调用 Oracle 存储过程?
Calling Oracle stored procedure using Entity Framework with output parameter?
我有一个简单的 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 操作都有效,任何人都可以帮助并指导我哪里出错了吗?
在这种情况下,您不应该调用:
var query = ctx.Database.SqlQuery<CmdRegisterAssetDto>(sql, projectNameParam, countryCodeParam, locationParam, assetRegisteredParam);
而是调用:
var result = ctx.Database.ExecuteSqlCommand(sql, projectNameParam, countryCodeParam, locationParam, assetRegisteredParam);
请注意,唯一有效的区别是 SqlQuery<CmdRegisterAssetDto>
被替换为 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 启动),但此后每次都很快执行。在每种情况下,我都在输出参数中找到了一个值。
如果有人 运行 遇到麻烦,有一个普通的变体可以为您处理脚本细节:
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()
),从技术上讲,您可以使用原始解决方案。查询的 return 值将始终为 null,但您的 out 参数至少会被填充。这是因为 EF 查询使用延迟执行。
如果需要传入and/or日期:
public DateTime GetDate(string dateIn)
{
Regex regex = new Regex(@"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)");
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 存储过程,它传入三个参数,并有一个输出参数:
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 操作都有效,任何人都可以帮助并指导我哪里出错了吗?
在这种情况下,您不应该调用:
var query = ctx.Database.SqlQuery<CmdRegisterAssetDto>(sql, projectNameParam, countryCodeParam, locationParam, assetRegisteredParam);
而是调用:
var result = ctx.Database.ExecuteSqlCommand(sql, projectNameParam, countryCodeParam, locationParam, assetRegisteredParam);
请注意,唯一有效的区别是 SqlQuery<CmdRegisterAssetDto>
被替换为 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 启动),但此后每次都很快执行。在每种情况下,我都在输出参数中找到了一个值。
如果有人 运行 遇到麻烦,有一个普通的变体可以为您处理脚本细节:
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()
),从技术上讲,您可以使用原始解决方案。查询的 return 值将始终为 null,但您的 out 参数至少会被填充。这是因为 EF 查询使用延迟执行。
如果需要传入and/or日期:
public DateTime GetDate(string dateIn)
{
Regex regex = new Regex(@"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)");
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;
}