我正尝试将C#WebService方法中的列表对象发送到Oracle中的存储过程。
在此处发布之前,我已经尝试了所有建议的重复链接。到目前为止,这是我已经完成的工作:
我当前正在使用以下设置:
请记住,Oracle.ManagedDataAccess 18.6.0版本不包含旧示例中建议的OracleDbType.Array
。
public class Automobile
{
public string Make { get; set; }
public string Model { get; set; }
public string Year { get; set; }
public string Country { get; set; }
}
using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public string InsertCars(List<Automobile> myCars, int userID)
{
DataSet dataSet = new DataSet();
using (OracleConnection sqlConnection = new OracleConnection(OracleDBConnection))
{
using (OracleCommand sqlCommand = new OracleCommand("sp_InsertCars", sqlConnection))
{
sqlConnection.Open();
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.Parameters.Add(
new OracleParameter
{
CollectionType = OracleCollectionType.PLSQLAssociativeArray,
Direction = ParameterDirection.Input,
ParameterName = "p_CarList",
UdtTypeName = "tt_Automobile",
Size = myCars.Count,
Value = myCars.ToArray()
}
);
sqlCommand.Parameters.Add(
new OracleParameter
{
OracleDbType = OracleDbType.Int32,
Direction = ParameterDirection.Input,
ParameterName = "p_UserID",
Value = userID
}
);
sqlCommand.Parameters.Add(
new OracleParameter
{
OracleDbType = OracleDbType.RefCursor,
Direction = ParameterDirection.Output,
ParameterName = "o_Cursor"
}
);
using (OracleDataAdapter sqlAdapter = new OracleDataAdapter(sqlCommand))
{
sqlAdapter.SelectCommand = sqlCommand;
sqlAdapter.Fill(dataSet);
}
}
return JsonConvert.SerializeObject(dataSet);
}
}
CREATE TABLE tblCars
(
RecordID INT GENERATED BY DEFAULT AS IDENTITY NOMINVALUE NOMAXVALUE INCREMENT BY 1 START WITH 1 NOCACHE NOCYCLE NOORDER,
Make NVARCHAR2(100) NULL,
Model NVARCHAR2(100) NULL,
Year NVARCHAR2(4) NULL,
Country NVARCHAR2(100) NULL,
UserID INT NULL
);
CREATE OR REPLACE TYPE ot_Automobile AS OBJECT
(
Make varchar2(100),
Model varchar2(100),
Year varchar2(4),
Country varchar2(100)
);
CREATE OR REPLACE TYPE tt_Automobile AS TABLE OF ot_Automobile;
CREATE OR REPLACE PROCEDURE sp_InsertCars
(
p_CarList In tt_Automobile,
p_UserID In integer,
o_Cursor Out Sys_RefCursor
)
AS
BEGIN
DBMS_Output.Enable;
For RowItem In (Select * From Table(p_CarList))
Loop
Insert Into tblCars
(
Make,
Model,
Year,
Country,
UserID
)
Values(
RowItem.Make,
RowItem.Model,
RowItem.Year,
RowItem.Country,
p_UserID
);
End Loop;
-- Return our results after insert
Open o_Cursor For
Select Make, Model, Year, Country From tblCars Where UserID = p_UserID;
EXCEPTION
When Others Then
DBMS_Output.Put_Line('SQL Error: ' || SQLERRM);
END sp_InsertCars;
COMMIT
/
结果应允许我将数组对象从WebService WebMethod传递到Oracle存储过程,然后循环遍历数组的每个项目以执行插入。
这是我要传递的数据的示例。
答案 0 :(得分:0)
请参考以下链接来设置ODAC Setup Ref并使用以下链接获取ODAC
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;
using System;
using System.Data;
namespace Strace_CustomTypes
{
class Program
{
static void Main(string[] args)
{
// Setup Ref - https://o7planning.org/en/10509/connecting-to-oracle-database-using-csharp-without-oracle-client
// ODAC 64bit ODAC122010Xcopy_x64.zip - https://www.oracle.com/technetwork/database/windows/downloads/index-090165.html
// .Net Framework 4
// 'Connection string' to connect directly to Oracle.
string connString = "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=0.0.0.0)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=SIT)));Password=PASSWORD;User ID=USERID";
OracleConnection straceOracleDBConn = new OracleConnection(connString);
OracleCommand cmd = new OracleCommand("PKG_TEMP.TEST_ARRAY", straceOracleDBConn);
cmd.CommandType = CommandType.StoredProcedure;
try
{
straceOracleDBConn.Open();
CustomVarray pScanResult = new CustomVarray();
pScanResult.Array = new string[] { "hello", "world" };
OracleParameter param = new OracleParameter();
param.OracleDbType = OracleDbType.Array;
param.Direction = ParameterDirection.Input;
param.UdtTypeName = "USERID.VARCHAR2_ARRAY";
param.Value = pScanResult;
cmd.Parameters.Add(param);
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message} {Environment.NewLine} {ex.StackTrace}");
}
finally
{
straceOracleDBConn.Close();
cmd.Dispose();
straceOracleDBConn.Dispose();
}
Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
}
//Ref https://www.codeproject.com/Articles/33829/How-to-use-Oracle-11g-ODP-NET-UDT-in-an-Oracle-Sto
public class CustomVarray : IOracleCustomType, INullable
{
[OracleArrayMapping()]
public string[] Array;
private OracleUdtStatus[] m_statusArray;
public OracleUdtStatus[] StatusArray
{
get
{
return this.m_statusArray;
}
set
{
this.m_statusArray = value;
}
}
private bool m_bIsNull;
public bool IsNull
{
get
{
return m_bIsNull;
}
}
public static CustomVarray Null
{
get
{
CustomVarray obj = new CustomVarray();
obj.m_bIsNull = true;
return obj;
}
}
public void FromCustomObject(OracleConnection con, IntPtr pUdt)
{
OracleUdt.SetValue(con, pUdt, 0, Array, m_statusArray);
}
public void ToCustomObject(OracleConnection con, IntPtr pUdt)
{
object objectStatusArray = null;
Array = (string[])OracleUdt.GetValue(con, pUdt, 0, out objectStatusArray);
m_statusArray = (OracleUdtStatus[])objectStatusArray;
}
}
[OracleCustomTypeMapping("USERID.VARCHAR2_ARRAY")]
public class CustomVarrayFactory : IOracleArrayTypeFactory, IOracleCustomTypeFactory
{
public Array CreateArray(int numElems)
{
return new string[numElems];
}
public IOracleCustomType CreateObject()
{
return new CustomVarray();
}
public Array CreateStatusArray(int numElems)
{
return new OracleUdtStatus[numElems];
}
}
}
答案 1 :(得分:0)
这个答案取决于商业包装,但是如果你像我一样绝望,那是一个非常合理的$ 300(大约在2020-Q4)的救星……侦察兵的荣誉,我不傻
DevArt's Oracle provider可以很好地完成将对象列表传递到存储的proc的工作...它确实可以工作.... net core 3.1兼容,已在linux上进行了测试,不依赖本机oracle客户端...请参阅在下面基于链接的论坛帖子的工作控制台应用程序示例中
DevArt的“ OracleType.GetObjectType()” API对我们来说,使UDT编组成为了这一难以置信的琐事……比我多年来已经看到的现有非托管ODP表类型支持示例更容易理解
战略上的考虑-如果您已经有一个基于oracle提供程序的可观代码,请考虑按原样保留所有代码,仅对实际上需要专用表类型支持的新依赖项进行回归测试
短而甜的样品可快速消化
using System;
using System.Data;
using Devart.Data.Oracle;
namespace ConsoleApp1
{
class Program
{
private static int oraTable;
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
//good docs:
//direct connection: https://www.devart.com/dotconnect/oracle/docs/StoredProcedures-OracleCommand.html
//linux licensing: https://www.devart.com/dotconnect/oracle/docs/?LicensingStandard.html
using OracleConnection db = new OracleConnection("{insert yours}");
//devart trial licensing nutshell... on WINDOWS, download & run their installer...
//the only thing you really need from that install is the license key file dropped here:
// %programdata%\DevArt\License\Devart.Data.Oracle.key
// then just go nuget the "Devart.Data.Oracle" package reference
//on Windows, the trial license file gets automatically picked up by their runtime
//if you're on Linux, basically just read their good instructions out there
//i've just tested it on WSL so far and plan to try on Azure linux app svc within next day
//db.ConnectionString = "license key=trial:Devart.Data.Oracle.key;" + db.ConnectionString;
db.Direct = true; //nugget: crucial!! https://www.devart.com/dotconnect/oracle/docs/DirectMode.html
db.Open();
var cmd = db.CreateCommand("UserPermissions_u", CommandType.StoredProcedure);
cmd.DeriveParameters();
//tblParm.OracleDbType = OracleDbType.Table;
//passing "table" type proc parm example: https://forums.devart.com/viewtopic.php?t=22243
var obj = new OracleObject(OracleType.GetObjectType("UserPerm", db));
var tbl = new OracleTable(OracleType.GetObjectType("UserPerms", db));
obj["UserPermissionId"] = "sR1CKjKYSKvgU90GUgqq+w==";
obj["adv"] = 1;
tbl.Add(obj);
cmd.Parameters["IN_Email"].Value = "banderson@kingcounty.gov";
cmd.Parameters["IN_Permissions"].Value = tbl;
cmd.ExecuteNonQuery();
//"i can't believe it's not butter!" -me, just now =)
}
}
}
对应的oracle db定义:
create or replace type UserPerm as object ( UserPermissionId varchar2(24), std number(1), adv number(1) );
create or replace type UserPerms as table of UserPerm;
create or replace PROCEDURE UserPermissions_u (
IN_Email IN varchar2,
IN_Permissions IN UserPerms
) is
dummyvar number default 0;
begin
select count(*) into dummyvar from table(IN_Permissions);
end;
/
更详细地说明一般是像OP的请求那样对入站对象进行补水,以使proc parms保持水准...谨慎行事,需要测试/防弹...如果有人在乎,我希望获得更好的选择分享
using System;
using System.Data;
using Devart.Data.Oracle;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections;
namespace ConsoleApp1
{
public class User
{
public string Email { get; set; }
public List<UserPermissionEffective> Permissions { get; set; }
}
public class UserPermissionEffective
{
public string UserPermissionId { get; set; }
public string Email { get; set; }
public bool Std { get; set; }
public bool Adv { get; set; }
public string Mod { get; set; }
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var dto = new User { Email = "testy@mctesterson.com", Permissions = new List<UserPermissionEffective> {
new UserPermissionEffective { UserPermissionId = "1", Std = false, Adv = true },
new UserPermissionEffective { UserPermissionId = "2", Std = true, Adv = false }
} };
if (dto == null) return;
//good docs:
//direct connection: https://www.devart.com/dotconnect/oracle/docs/StoredProcedures-OracleCommand.html
//linux licensing: https://www.devart.com/dotconnect/oracle/docs/?LicensingStandard.html
var dbstring = Environment.GetEnvironmentVariable("dbstring");
using OracleConnection db = new OracleConnection(dbstring);
db.ConnectionString = "license key=trial:Devart.Data.Oracle.key;" + db.ConnectionString;
db.Direct = true; //nugget: crucial!! https://www.devart.com/dotconnect/oracle/docs/DirectMode.html
db.Open();
var cmd = db.CreateCommand("UserPermissions_u", CommandType.StoredProcedure);
cmd.DeriveParameters();
//regex gets everything following the last underscore. e.g. INOUT_PARMNAME yields PARMNAME
var regex = new Regex(@"([^_\W]+)$", RegexOptions.Compiled);
//get the inboud model's root properties
var dtoProps = dto.GetType().GetProperties();
//loop over all parms assigning model properties values by name
//going by parms as the driver versus object properties to favor destination over source
//since we often ignore some superfluous inbound properties
foreach (OracleParameter parm in cmd.Parameters)
{
var cleanParmName = regex.Match(parm.ParameterName).Value.ToUpper();
var dtoPropInfo = dtoProps.FirstOrDefault(prop => prop.Name.ToUpper() == cleanParmName);
//if table type, then drill into the nested list
if (parm.OracleDbType == OracleDbType.Table)
{
//the type we're assigning from must be a list
//https://stackoverflow.com/questions/4115968/how-to-tell-whether-a-type-is-a-list-or-array-or-ienumerable-or/4115970#4115970
Assert.IsTrue(typeof(IEnumerable).IsAssignableFrom(dtoPropInfo.PropertyType));
var listProperty = (dtoPropInfo.GetValue(dto) as IEnumerable<Object>).ToArray();
//don't bother further logic if the list is empty
if (listProperty.Length == 0) return;
//get the oracle table & item Udt's to be instanced and hydrated from the inbound dto
var tableUdt = OracleType.GetObjectType(parm.ObjectTypeName, db);
var itemUdt = OracleType.GetObjectType(tableUdt.ItemObjectType.Name, db);
var dbList = new OracleTable(tableUdt);
//and the internal list item objects
var subPropInfos = dtoPropInfo.PropertyType.GenericTypeArguments[0].GetProperties().ToDictionary(i=>i.Name.ToUpper(), i=>i);
//for every item passed in...
foreach (var dtoSubItem in listProperty) {
//create db objects for every row of data we want to send
var dbObj = new OracleObject(itemUdt);
//and map the properties from the inbound dto sub items to oracle items by name
//using reflection to enumerate the properties by name
foreach (OracleAttribute field in itemUdt.Attributes)
{
var val = subPropInfos[field.Name.ToUpper()].GetValue(dtoSubItem);
//small tweak to map inbound booleans to 1's & 0's on the db since oracle doesn't support boolean!?!
var isDbBool = field.DbType == OracleDbType.Integer && field.Precision == 1;
dbObj[field] = isDbBool ? ((bool)val ? 1 : 0) : val;
}
//lastly add the db obj to the db table
dbList.Add(dbObj);
}
parm.Value = dbList;
}
else {
parm.Value = dtoPropInfo.GetValue(dto);
}
}
cmd.ExecuteNonQuery();
}
}
}