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

时间:2019-05-08 12:53:01

标签: c# .net oracle stored-procedures oracle.manageddataaccess

我正尝试将C#WebService方法中的列表对象发送到Oracle中的存储过程。

在此处发布之前,我已经尝试了所有建议的重复链接。到目前为止,这是我已经完成的工作:

  • 成功:在C#中,我可以将列表值从HTML页面传递到WebService方法。
  • 成功:在Oracle中,我创建了一个表,对象类型,表类型和存储过程以接受列表值。我能够使用匿名块和示例数据对此进行测试。
  • 问题:我无法将我的List值从C#WebMethod传递到Oracle存储过程。

我当前正在使用以下设置:

  • Visual Studio 2017
  • .NET Framework 4.6.1
  • Oracle.ManagedDataAccess 18.6.0

请记住,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存储过程,然后循环遍历数组的每个项目以执行插入。

这是我要传递的数据的示例。

Sample data being passed

2 个答案:

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