简体   繁体   English

如何将列表对象从 C# 传递到 Oracle 存储过程?

[英]How To Pass a List Object From C# to an Oracle Stored Procedure?

I'm trying to send a List Object from my C# WebService method over to my stored procedure in Oracle.我正在尝试将一个列表对象从我的 C# WebService 方法发送到我在 Oracle 中的存储过程。

Before posting here, I've tried all suggested duplicate links.在这里发帖之前,我已经尝试了所有建议的重复链接。 Here's what I've accomplished so far:这是我到目前为止所完成的:

  • Success: In C#, I can pass my List values from my HTML page over to my WebService method.成功:在 C# 中,我可以将我的 List 值从我的 HTML 页面传递到我的 WebService 方法。
  • Success: In Oracle, I have created a Table, Object Type, Table Type, and Stored Procedure to accept the List values.成功:在 Oracle 中,我创建了一个表、对象类型、表类型和存储过程来接受列表值。 I was able to test this using an Anonymous block and sample data.我能够使用匿名块和示例数据对此进行测试。
  • Problem: I cannot get to pass my List values from my C# WebMethod over to my Oracle Stored Procedure.问题:我无法将我的列表值从我的 C# WebMethod 传递到我的 Oracle 存储过程。

I'm currently using the following setup:我目前使用以下设置:

  • Visual Studio 2017视觉工作室 2017
  • .NET Framework 4.6.1 .NET 框架 4.6.1
  • Oracle.ManagedDataAccess 18.6.0 Oracle.ManagedDataAccess 18.6.0

Keep in mind that the version of Oracle.ManagedDataAccess 18.6.0 does NOT contain the OracleDbType.Array as suggested in the older examples.请记住,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
        /

The result should allow me to pass my array Object from my WebService WebMethod over to my Oracle stored procedure and then loop through each item of the array to perform an Insert.结果应该允许我将我的数组对象从我的 WebService WebMethod 传递到我的 Oracle 存储过程,然后循环遍历数组的每个项目以执行插入。

Here's an example of the data I'm trying to pass in.这是我尝试传入的数据示例。

正在传递的样本数据

Please refer following link to setup ODAC Setup Ref and use follwing link to get the ODAC请参考以下链接设置 ODAC 设置参考并使用以下链接获取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];
        }
    }
}

this answer depends on commercial package, but if you're as desperate as i am, it's a lifesaver for very reasonable $300 (circa 2020-Q4)... scouts honor, i'm no shill这个答案取决于商业套餐,但如果你和我一样绝望,它是一个非常合理的 300 美元(大约 2020 年第四季度)的救命稻草......球探们的荣誉,我不是骗子

DevArt's Oracle provider makes elegant work of passing lists of objects to stored procs... it really works... .net core 3.1 compatible, tested on linux, no dependencies on native oracle client ... see my take on a working console app sample below based on the linked forum post DevArt 的 Oracle 提供程序在将对象列表传递给存储的 procs 方面进行了优雅的工作……它确实有效……与 .net core 3.1 兼容,在 linux 上测试,不依赖于本机 oracle 客户端……请参阅我对工作控制台应用程序的看法以下示例基于链接的论坛帖子

DevArt's "OracleType.GetObjectType()" API makes the UDT marshalling part of this incredibly trivial for us... way less to grok than the existing unmanaged ODP table type support samples i've seen out there for years DevArt 的“OracleType.GetObjectType()”API 使 UDT 编组成为这个令人难以置信的微不足道的部分......比我多年来在那里看到的现有非托管 ODP 表类型支持示例更难理解

strategic consideration - if you already have a sizable code base on an oracle provider, consider just leaving all that code as-is and only take on regression testing this new dependency where the specialized table type support is actually needed战略考虑 - 如果您已经在 oracle 提供程序上拥有相当大的代码库,请考虑将所有代码保持原样,仅在实际需要专用表类型支持的情况下对这个新依赖项进行回归测试

short and sweet sample for quick digestion快速消化的短而甜的样品

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 =)
        }
    }
}

corresponding oracle db definitions:对应的 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;
/

more elaborate shot at generically reflecting on an inbound object to hydrate the proc parms like the OP's request... take with caution, needs testing/bullet-proofing ... i'd love to get better alternatives if anybody cares to share更详细的拍摄一般反映入站对象以补充 proc 参数,如 OP 的请求......谨慎使用,需要测试/防弹......如果有人愿意分享,我很想获得更好的替代方案

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();
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 将C#中的整数列表传递到Oracle存储过程中 - Pass a list of integers from C# into Oracle stored procedure 从 C#,如何将 clob 值传递给 oracle 存储过程 - From C#, How to pass a clob value to oracle stored procedure 如何将表 OBJECT 输出参数从 C# ASP.NET 核心传递到 Z30162ED78B6C10F231411F2F4 - How to pass a TABLE OBJECT out parameter from C# ASP.NET Core to Oracle Stored Procedure 将表类型对象作为输入参数从C#传递给Oracle中的存储过程 - Pass table-type object as input parameter to Stored Procedure in Oracle from C# 将数据表从C#传递到Oracle存储过程 - Pass Datatable from C# to Oracle Stored procedure 如何在C#中使用函数将参数传递给oracle存储过程 - How to use function to pass parameter to oracle stored procedure in c# 如何在Oracle存储过程中将值从C#传递给refcursor参数 - How to pass a value from c# to refcursor parameter in oracle stored procedure 如何将表值参数从C#传递到Oracle存储过程 - How to pass a table-valued parameter from C# to Oracle stored procedure 数字表Oracle存储过程参数,如何从C#传递它 - TABLE OF NUMBERS Oracle stored procedure Parameter, How to pass it from C# 如何将 C# class object 作为参数传递给存储过程? - How to pass C# class object as parameter to a stored procedure?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM