Accessing Oracle 9i Stored Procedures Using ADO.NET

编程范式的本质:阴阳二分,相融相异 本文源于一次真实的设计讨论,上片博客沿着四种语言实现的路子走下去,本篇博客则沿着设计模式的路线归结到计算机哲学的根。需求本身小得不能再小:给一个存放数据库读取结果的 `Dict` 列表按字段名排序。但当我们把约束加码到"字段演进时排序器永不改动"时,这个需求就不再是工程问题,而成了三种编程范式的正面交锋。沿着这条线走下去,我们最终抵达了一个更根本的结论——**编程范式不是一张并列的表格,而是一棵树,而树的根只有两个。** 阅读详情

Bill Hamilton

Summary: Use the Microsoft .NET Oracle Provider in the Microsoft .NET Framework 1.1 to access Oracle stored procedures and functions. (24 printed pages)

Contents

This article is a discussion of how to access Oracle stored procedures (named SQL programming blocks) and functions (which are programming blocks that return a single value) using ADO.NET.

You connect to an Oracle database using managed data providers, including the Microsoft® .NET Oracle Provider, the OLE DB .NET provider, the ODBC .NET data provider, or the ODP.NET provider from Oracle. This article uses the Microsoft® .NET Framework data provider for Oracle. Different functionality is available using the Oracle ODP.NET data provider or the Microsoft .NET Framework data provider for OLE DB.

The Oracle .NET data provider ships with the .NET Framework 1.1. If you are using .NET Framework 1.0, you will need to download the .NET Managed Provider for Oracle. For either version, the data provider classes are located in the System.Data.OracleClient namespace.

Overview

PL/SQL is the Oracle implementation of SQL. It is similar to T-SQL used by Microsoft® SQL Server™, with some exceptions that are discussed in more detail later in this article. Like T-SQL, PL/SQL extends standard SQL. PL/SQL is used to define named programming blocks such as stored procedures, functions, and triggers.

Classes

You use a subset of the classes in the System.Data.OracleClient namespace to execute Oracle stored procedures and functions. These classes are described in the following table:

Class

Description

OracleCommand

The SQL statement of stored procedure to execute against the Oracle database.

OracleConnection

An open connection to the database.

OracleParameter

A parameter to an OracleCommand and optionally, its mapping to a DataColumn.

OracleParameterCollection

A collection of OracleParameter objects.

OracleType

An enumeration of Oracle data types and structures.

Executing a Stored Procedure

Executing an Oracle stored procedure is similar to executing a SQL Server stored procedure. The following steps show how to execute an Oracle stored procedure and retrieve the result that it returns.

  1. Create a stored procedure called COUNT_JOB_HISTORY in the HR schema to count the records in the JOB_HISTORY table.
2.             CREATE OR REPLACE PROCEDURE COUNT_JOB_HISTORY
   
3.             (
   
4.                 reccount OUT NUMBER
   
5.             )
   
6.             IS
   
7.             BEGIN
   
8.                 SELECT COUNT(*) INTO reccount
   
9.                 FROM JOB_HISTORY;
   
10.          END COUNT_JOB_HISTORY;
   

The HR schema is a sample that is included in the default Oracle installation.

  1. Add a reference to System.Data.OracleClient.dll—the Microsoft .NET Framework Data Provider for Oracle—to your project.
  2. Import the types in the OracleClient class with a using directive.
13.          using System.Data.OracleClient;
   
  1. Create an OracleConnection object.
15.          OracleConnection conn = new OracleConnection("Data Source=oracledb;
   
16.              User Id=UserID;Password=Password;");
   

Substitute your values for the name of the Oracle database, the username, and the password.

  1. Create an OracleCommand object. Set its Connection property to the connection created in Step 4. Set its CommandText to the name of the stored procedure and its CommandText property to CommandType.StoredProcedure. The command object executes the specified stored procedure when you call one of the Execute() methods described in Step 8.
18.          OracleCommand cmd = new OracleCommand();
   
19.          cmd.Connection = conn;
   
20.          cmd.CommandText = "COUNT_JOB_HISTORY";
   
21.          cmd.CommandType = CommandType.StoredProcedure;
   

You might have to use escape sequences if your stored procedure name contains special characters. You can reuse an existing OracleCommand object by resetting the CommandText property.

  1. Create OracleParameter objects for the input, output, and return values and add them to the collection of parameters of the OracleCommand object.
23.          cmd.Parameters.Add("reccount", OracleType.Number).Direction =
   
24.              ParameterDirection.Output;
   

This line of code is shorthand for the following two lines of code:

cmd.Parameters.Add("reccount", OracleType.Number);
   
cmd.Parameters["reccount"].Direction = ParameterDirection.Output;
   
  1. If you are retrieving a result set, create a DataSet, DataTable, or DataReader. In this example, we are just getting the count in the output parameter created in Step 6.
  2. Open the connection and execute the stored procedure using one of the Execute methods of the OracleCommand object shown below:

Method

Description

ExecuteReader

Builds an OracleDataReader by executing a stored procedure that returns a result set.

ExecuteNonQuery

Executes a query or procedure that does not return a result set returning the number of rows affected.

ExecuteOracleNonQuery

Executes a query, returning the number of rows affected.

This method also uses an OracleString parameter to return the row id for the last row modified by an UPDATE, INSERT, or DELETE query.

ExecuteScalar

Executes a query or procedure and returns either the return value or the value of the first column of the first row of the result set as a .NET Framework data type.

ExecuteOracleScalar

Executes a query or procedure and returns either the return value or the value of the first column of the first row of the result set as an OracleType data type.

27.   Remember to close the connection when you are done.

28.          conn.Open();
   
29.          cmd.ExecuteNonQuery();
   
30.          conn.Close();
   

31.   If you are using a DataAdapter to fill a DataTable or DataSet, you can rely on the DataAdapter to open and close the connection.

  1. Process the results. In our example, the record count is available in the output parameter which is displayed to the console:
33.          Console.WriteLine(cmd.Parameters["reccount"].Value);
   

The code developed in this example to execute the stored procedure and retrieve the result follows:

OracleConnection conn = new OracleConnection("Data Source=oracledb;
   
    User Id=UserID;Password=Password;");
   

  
    
  
OracleCommand cmd = new OracleCommand();
   
cmd.Connection = conn;
   
cmd.CommandText = "COUNT_JOB_HISTORY";
   
cmd.CommandType = CommandType.StoredProcedure;
   
cmd.Parameters.Add("reccount", OracleType.Number).Direction =
   
    ParameterDirection.Output;
   

  
    
  
conn.Open();
   
cmd.ExecuteNonQuery();
   
conn.Close();
   

  
    
  
Console.WriteLine(cmd.Parameters["reccount"].Value);
   

Stored Procedures That Do Not Return Data

The ExecuteOracleNonQuery() method of the OracleCommand class executes a SQL statement or stored procedure that does not return any rows. The method returns an int value representing the number of rows affected for the UPDATE, INSERT, and DELETE commands; otherwise it returns -1. If you are executing an INSERT, DELETE, or UPDATE statement that affects exactly one row, the method has a single OracleString out rowid argument that uniquely identifies the affected row in the Oracle database. You can use this value to optimize subsequent related queries.

You can also use the ExecuteNonQuery() method of the OracleCommand class to execute a stored procedure that does not return data, but you will not get the unique row identifier described above.

While neither of the above commands returns any data, output parameters and return values mapped to parameters are populated with data. This lets you return one or more scalar values from a stored procedure using either of the above commands.

The following Oracle stored procedure deletes all job history for an employee specified by the single input parameter and does not return any data.

CREATE OR REPLACE PROCEDURE DELETE_JOB_HISTORY
   
(
   
    p_employee_id NUMBER
   
)
   
IS
   
BEGIN
   
    DELETE FROM job_history
   
    WHERE employee_id = p_employee_id;
   
END DELETE_JOB_HISTORY;
   

The following code runs the stored procedure.

// create the connection
   
OracleConnection conn = new OracleConnection("Data Source=oracledb;
   
    User Id=UserID;Password=Password;");
   

  
    
  
// create the command for the stored procedure
   
OracleCommand cmd = new OracleCommand();
   
cmd.Connection = conn;
   
cmd.CommandText = "COUNT_JOB_HISTORY";
   
cmd.CommandType = CommandType.StoredProcedure;
   

  
    
  
// add the parameter specifying the employee for whom to delete records
   
cmd.Parameters.Add("p_employee_id", OracleType.Number).Value = 102;
   

  
    
  
OracleString rowId;
   
// execute the stored procedure
   
conn.Open();
   
int rowsAffected = cmd.ExecuteNonQuery();
   
conn.Close();
   

  
    
  
Console.WriteLine("Rows affected: " + rowsAffected);
   

If you have not modified the default HR installation, the one record for employee 102 in the JOB_HISTORY table is deleted and the following is output to the console:

Rows affected: 1
   

Accessing Return Values

The RETURN statement immediately returns control to the calling program from a stored procedure. The RETURN statement in an Oracle stored procedure cannot return a value like it can in T-SQL.

Oracle functions are subprograms that compute and return a single value. They are structured similarly to stored procedures, except they always have a RETURN clause that must return a value.

A function that returns the e-mail for a specified employee follows:

CREATE OR REPLACE FUNCTION GET_EMPLOYEE_EMAIL (
   
    p_employee_id NUMBER
   
)
   
RETURN VARCHAR2
   
IS p_email VARCHAR2(25);
   
BEGIN
   
    SELECT EMAIL INTO p_email FROM EMPLOYEES
   
    WHERE EMPLOYEE_ID = p_employee_id;
   
    
   
    RETURN p_email;
   
END GET_EMPLOYEE_EMAIL;
   

You execute the function in the same way as a stored procedure. Use a ParameterDirection.ReturnValue parameter to get the result returned by the function. The following code shows how:

// create the connection
   
OracleConnection conn = new OracleConnection("Data Source=oracledb;
   
    User Id=UserID;Password=Password;");
   

  
    
  
// create the command for the function
   
OracleCommand cmd = new OracleCommand();
   
cmd.Connection = conn;
   
cmd.CommandText = "GET_EMPLOYEE_EMAIL";
   
cmd.CommandType = CommandType.StoredProcedure;
   

  
    
  
// add the parameters, including the return parameter to retrieve
   
// the return value
   
cmd.Parameters.Add("p_employee_id", OracleType.Number).Value = 101;
   
cmd.Parameters.Add("p_email", OracleType.VarChar, 25).Direction =
   
    ParameterDirection.ReturnValue;
   

  
    
  
// execute the function
   
conn.Open();
   
cmd.ExecuteNonQuery();
   
conn.Close();
   

  
    
  
// output the result
   
Console.WriteLine("Email address is: " + cmd.Parameters["p_email"].Value);
   

The console output shows the email address for employee 101.

Email address is: NKOCHHAR
   

Result Sets and REF CURSORS

You use the REF CURSOR data type to work with Oracle result sets. A REF CURSOR is a pointer into a result set returned by a PL/SQL query. Unlike a normal cursor, a REF CURSOR is a variable, a reference to a cursor that can be set to point to different result sets at execution time. Use a REF CURSOR output parameter to pass a result set from an Oracle structured program back to the calling application. Define an output parameter of data type OracleType.Cursor in the calling application to access the result set pointed to by the REF CURSOR. The OracleConnection must remain open while the REF CURSOR is being used.

Packages

One significant difference between stored procedures in PL/SQL and T-SQL is the Oracle package construct used by PL/SQL. There is no equivalent in T-SQL. A package is a container for logically related programming blocks such as stored procedures and functions. It has two parts:

  • Specification (or spec): Defines the name of the package and supplies method signatures (prototypes) for each stored procedure or function in the package. The header also defines any global declarations. The style of the spec is similar to a C or C++ header file.
  • Body: Contains code for the stored procedures and functions defined in the package header.

The parameters for each stored procedure or function appear within parentheses and are separated by commas. Each parameter is optionally tagged with one of three identifiers:

  • IN: The value is passed into the PL/SQL block from the calling application. IN is the default direction if one is not specified.
  • OUT: A value generated by the stored procedure and passed back to the calling application.
  • INOUT: A value that is passed into the PL/SQL block, possibly modified within the block, and returned to the calling application.

Each parameter is also tagged to indicate the data type.

The following package spec defines four procedures that create, retrieve, update, and delete data from the LOCATIONS table in the HR schema.

CREATE OR REPLACE PACKAGE CRUD_LOCATIONS AS
   
TYPE T_CURSOR IS REF CURSOR;
   
PROCEDURE GetLocations (cur_Locations OUT T_CURSOR);
   
PROCEDURE UpdateLocations (p_location_id IN NUMBER,
   
    p_street_address IN VARCHAR2,
   
    p_postal_code IN VARCHAR2,
   
    p_city IN VARCHAR2,
   
    p_state_province IN VARCHAR2,
   
    p_country_id IN CHAR);
   
PROCEDURE DeleteLocations (p_location_id IN NUMBER);
   
PROCEDURE InsertLocations (p_location_id OUT NUMBER,
   
    p_street_address IN VARCHAR2,
   
    p_postal_code IN VARCHAR2,
   
    p_city IN VARCHAR2,
   
    p_state_province IN VARCHAR2,
   
    p_country_id IN CHAR);
   
END CRUD_LOCATIONS;
   

The following excerpt from the package body for the above package spec shows the implementation details for the first procedure in the GetLocations package:

CREATE OR REPLACE PACKAGE BODY CRUD_LOCATIONS AS
   
PROCEDURE GetLocations (cur_Locations OUT T_CURSOR)
   
IS
   
BEGIN
   
    OPEN cur_Locations FOR
   
    SELECT * FROM LOCATIONS;
   
END GetLocations;
   

  
    
  
-- Implementation of other procedures ommitted.
   

  
    
  
END CRUD_LOCATIONS;
   

Using a DataReader

You create an OracleDataReader by calling the ExecuteReader() method of the OracleCommand object. This section shows how to use a DataReader to access the result set returned by the stored procedure SELECT_JOB_HISTORY. The package spec follows:

CREATE OR REPLACE PACKAGE SELECT_JOB_HISTORY AS
   
TYPE T_CURSOR IS REF CURSOR;
   
PROCEDURE GetJobHistoryByEmployeeId
   
(
   
    p_employee_id IN NUMBER,
   
    cur_JobHistory OUT T_CURSOR
   
);
   
END SELECT_JOB_HISTORY;
   

The package body defines a single procedure that retrieves a result set of the job history for a specified employee and returns it as a REF CURSOR output parameter:

CREATE OR REPLACE PACKAGE BODY SELECT_JOB_HISTORY AS
   
PROCEDURE GetJobHistoryByEmployeeId
   
(
   
    p_employee_id IN NUMBER,
   
    cur_JobHistory OUT T_CURSOR
   
)
   
IS
   
BEGIN
   
    OPEN cur_JobHistory FOR
   
    SELECT * FROM JOB_HISTORY
   
        WHERE employee_id = p_employee_id;
   

  
    
  
END GetJobHistoryByEmployeeId;
   
END SELECT_JOB_HISTORY;
   

The following code executes the procedure, creates a DataReader from the result set, and outputs the contents of the DataReader to the console.

// create connection
   
OracleConnection conn = new OracleConnection("Data Source=oracledb;
   
    User Id=UserID;Password=Password;");
   

  
    
  
// create the command for the stored procedure
   
OracleCommand cmd = new OracleCommand();
   
cmd.Connection = conn;
   
cmd.CommandText = "SELECT_JOB_HISTORY.GetJobHistoryByEmployeeId";
   
cmd.CommandType = CommandType.StoredProcedure;
   

  
    
  
// add the parameters for the stored procedure including the REF CURSOR
   
// to retrieve the result set
   
cmd.Parameters.Add("p_employee_id", OracleType.Number).Value = 101;
   
cmd.Parameters.Add("cur_JobHistory", OracleType.Cursor).Direction =
   
    ParameterDirection.Output;
   

  
    
  
// open the connection and create the DataReader
   
conn.Open();
   
OracleDataReader dr = cmd.ExecuteReader();
   

  
    
  
// output the results and close the connection.
   
while(dr.Read())
   
{
   
    for(int i = 0; i < dr.FieldCount; i++)
   
        Console.Write(dr[i].ToString() + ";");
   
    Console.WriteLine();
   
}
   
conn.Close();
   

With the default installation of the HR schema, the console output shows the semi colon delimited fields of each of the two records for employee 101:

101;
   
    9/21/1989
    
   
    12:00:00 AM
   ;
   
    10/27/1993
    
   
    12:00:00 AM
   ;AC_ACCOUNT;110;
   
101;
   
    10/28/1993
    
   
    12:00:00 AM
   ;
   
    3/15/1997
    
   
    12:00:00 AM
   ;AC_MGR;110;
   

The code shows that a procedure within a package is specified using both the name of the package and the procedure, separated by a period—SELECT_JOB_HISTORY (GetJobHistoryByEmployeeId, in this case).

The code also shows how to define the REF CURSOR parameter for the result set. Notice that the data type is OracleType.Cursor and that the direction is ParameterDirection.Output.

Also note that the connection is kept open the entire time that the result set in the REF CURSOR is accessed.

If a package returns more than one cursor, the DataReader accesses them in the order you add them to the parameters collection, not the order in which they appear in the procedure. Use the NextResult() method of the DataReader to advance to the next cursor.

Stored Procedures That Return a Single Value

The ExecuteOracleScalar() method of the OracleCommand class is used to execute a SQL statement or stored procedure that returns a single value as an OracleType data type. If the command returns a result set, the method returns the value of the first column of the first row. The method returns a null reference if a REF CURSOR is returned rather than the value of the first column of the first row to which the REF CURSOR points. The ExecuteScalar() method of the OracleCommand class is similar to the ExecuteOracleScalar() method, except it returns a value as a .NET Framework data type.

Having said that, neither of these methods is useful when working with Oracle stored procedures. Oracle stored procedures cannot return a value as part of the RETURN statement, only as OUT parameters—see the Stored Procedures That Do Not Return Data section. Also, you cannot return a result set except through a REF CURSOR output parameter—this is discussed in the next section.

You can retrieve the return value for an Oracle function only by using a RETURN parameter (shown in the previous section) and not by using the one of the ExecuteScalar methods.

Sequences

Oracle uses a sequence to generate unique numbers rather than the data type uniqueidentifier used by SQL Server. In either case, the main use is to generate a sequence of unique numbers for a primary key column. Unlike the uniqueidentifier data type, a sequence is a database object unrelated to the table or tables using it for primary key values.

An Oracle sequence is atomic and consistent. That is, once you access a sequence number, Oracle automatically increments the next number before processing the next request, guaranteeing no duplicates for a value.

You create an Oracle sequence using the CREATE SEQUENCE command. The command takes arguments including increment, start value, maximum value, cycling, and caching. You access sequence values using the NEXTVAL and CURRVAL keywords. NEXTVAL returns the next number in the sequence while CURRVAL provides access the current value. The sequence LOCATIONS_SEQ in the HR schema is defined as follows:

CREATE SEQUENCE LOCATIONS_SEQ
   
    INCREMENT BY 100
   
    START WITH 1
   
    MAXVALUE 9900
   
    MINVALUE 1
   
    NOCYCLE 
   
    NOCACHE
   
    NOORDER
   

Most of the sequence code is self-describing. NOCYCLE means that the sequence will not generate more values after the minimum or maximum value is reached. NOCACHE means that sequence values are not allocated before they are requested; preallocation can be used to improve performance. NOORDER means that the numbers are generated without the guarantee that they will be returned in the same order they were requested.

The following code shows a stored procedure that requests a sequence value, uses it to set the primary key value when inserting a record into the LOCATIONS table, and returns the primary key value in an OUT parameter.

CREATE OR REPLACE PROCEDURE ADD_LOCATION (
   
    p_location_id OUT NUMBER,
   
    p_street_address IN VARCHAR2,
   
    p_postal_code IN VARCHAR2,
   
    p_city IN VARCHAR2,
   
    p_state_province IN VARCHAR2,
   
    p_country_id IN CHAR
   
)
   
AS
   
BEGIN
   
    INSERT INTO LOCATIONS (
   
        LOCATION_ID,
   
        STREET_ADDRESS,
   
        POSTAL_CODE,
   
        CITY,
   
        STATE_PROVINCE,
   
        COUNTRY_ID)
   
    VALUES (
   
        LOCATIONS_SEQ.NEXTVAL,
   
        p_street_address,
   
        p_postal_code,
   
        p_city,
   
        p_state_province,
   
        p_country_id
   
    );
   
    
   
    SELECT LOCATIONS_SEQ.CURRVAL INTO p_location_id FROM DUAL;
   
END ADD_LOCATION;
   

The following code calls the stored procedure to insert a record and retrieve the returned sequence value.

// create the connection
   
OracleConnection conn = new OracleConnection("Data Source=oracledb;
   
    User Id=UserID;Password=Password;");
   

  
    
  
// create the command for the stored procedure
   
OracleCommand cmd = new OracleCommand();
   
cmd.Connection = conn;
   
cmd.CommandText = "ADD_LOCATION";
   
cmd.CommandType = CommandType.StoredProcedure;
   

  
    
  
// add the parameters for the stored procedure including the LOCATION_ID
   
// sequence value that is returned in the output parameter p_location_id
   
cmd.Parameters.Add("p_location_id", OracleType.Number).Direction =
   
    ParameterDirection.Output;
   
cmd.Parameters.Add("p_street_address", OracleType.VarChar).Value =
   
    "123 Any Street";
   
cmd.Parameters.Add("p_postal_code", OracleType.VarChar).Value = "33040";
   
cmd.Parameters.Add("p_city", OracleType.VarChar).Value = "
   
    
     Key West
    
   ";
   
cmd.Parameters.Add("p_state_province", OracleType.VarChar).Value = "FL";
   
cmd.Parameters.Add("p_country_id", OracleType.VarChar).Value = "US";
   

  
    
  
// execute the command to add the records      
   
OracleString rowId;
   
conn.Open();
   
int rowsAffected = cmd.ExecuteOracleNonQuery(out rowId);
   
conn.Close();
   

  
    
  
// output the results
   
Console.WriteLine("Rows affected: " + rowsAffected);
   
Console.WriteLine("Location ID: " +
   
    cmd.Parameters["p_location_id"].Value);
   

The console shows that one record was inserted into the table along with the primary key value generated by the sequence.

Rows affected: 1
   
Location ID: 3300
   

Filling a DataSet Using a DataAdapter

You use a REF CURSOR to fill a DataSet using a DataAdapter. The following code uses the stored procedure GetJobHistoryByEmployeeId defined in the Using a DataReader section to populate a DataSet with the result set that it returns in a REF CURSOR output parameter.

The code to fill a DataSet using a DataAdapter follows:

// create the connection
   
OracleConnection conn = new OracleConnection("Data Source=oracledb;
   
    User Id=UserID;Password=Password;");
   

  
    
  
// create the command for the stored procedure
   
OracleCommand cmd = new OracleCommand();
   
cmd.Connection = conn;
   
cmd.CommandText = "SELECT_JOB_HISTORY.GetJobHistoryByEmployeeId";
   
cmd.CommandType = CommandType.StoredProcedure;
   

  
    
  
// add the parameters for the stored procedure including the REF CURSOR
   
// to retrieve the result set
   
cmd.Parameters.Add("p_employee_id", OracleType.Number).Value = 101;
   
cmd.Parameters.Add("cur_JobHistory", OracleType.Cursor).Direction =
   
    ParameterDirection.Output;
   

  
    
  
// createt the DataAdapter from the command and use it to fill the
   
// DataSet
   
OracleDataAdapter da = new OracleDataAdapter(cmd);
   
DataSet ds = new DataSet();
   
da.Fill(ds);
   

  
    
  
// output the results.
   
Console.WriteLine(ds.Tables[0].Rows.Count);
   

With the default installation of the HR schema, the output indicates that there are two JOB_HISTORY records for employee 101.

Updating Oracle with a DataAdapter

You cannot simply use the Update() method of the OracleDataAdapter when you fill a DataSet using a REF CURSOR parameter. This is because Oracle does not provide the information required to determine what the table and column names are when the stored procedure is executed. To use the Update() method of the DataAdapter, you have to create procedures to update, insert, and delete records from the underlying table. The approach is similar to the one used with SQL Server.

This section shows how to build a package that handles the create, retrieve, update, and delete operations required both to retrieve LOCATION data from the Oracle database and to update disconnected changes made to the DataSet data back to the Oracle database. The package header follows:

CREATE OR REPLACE PACKAGE CRUD_LOCATIONS AS
   
TYPE T_CURSOR IS REF CURSOR;
   
PROCEDURE GetLocations (cur_Locations OUT T_CURSOR);
   
PROCEDURE UpdateLocations (
   
    p_location_id IN NUMBER,
   
    p_street_address IN VARCHAR2,
   
    p_postal_code IN VARCHAR2,
   
    p_city IN VARCHAR2,
   
    p_state_province IN VARCHAR2,
   
    p_country_id IN CHAR);
   
PROCEDURE DeleteLocations (p_location_id IN NUMBER);
   
PROCEDURE InsertLocations (
   
    p_location_id OUT NUMBER,
   
    p_street_address IN VARCHAR2,
   
    p_postal_code IN VARCHAR2,
   
    p_city IN VARCHAR2,
   
    p_state_province IN VARCHAR2,
   
    p_country_id IN CHAR);
   
END CRUD_LOCATIONS;
   

The package body follows:

CREATE OR REPLACE PACKAGE BODY CRUD_LOCATIONS AS
   
-- retrieve all LOCATION records
   
PROCEDURE GetLocations (cur_Locations OUT T_CURSOR)
   
IS
   
BEGIN
   
    OPEN cur_Locations FOR
   
    SELECT * FROM LOCATIONS;
   
END GetLocations;
   

  
    
  
-- update a LOCATION record
   
PROCEDURE UpdateLocations (
   
    p_location_id IN NUMBER,
   
    p_street_address IN VARCHAR2,
   
    p_postal_code IN VARCHAR2,
   
    p_city IN VARCHAR2,
   
    p_state_province IN VARCHAR2,
   
    p_country_id IN CHAR)
   
IS
   
BEGIN
   
    UPDATE LOCATIONS
   
    SET
   
        STREET_ADDRESS = p_street_address,
   
        POSTAL_CODE = p_postal_code,
   
        CITY = p_city,
   
        STATE_PROVINCE = p_state_province,
   
        COUNTRY_ID = p_country_id
   
    WHERE
   
        LOCATION_ID = p_location_id;        
   
END UpdateLocations;
   

  
    
  
-- delete a LOCATION record
   
PROCEDURE DeleteLocations (p_location_id IN NUMBER)
   
IS
   
BEGIN
   
     DELETE FROM LOCATIONS
   
     WHERE LOCATION_ID = p_location_id;
   
END DeleteLocations;
   

  
    
  
-- insert a LOCATION record
   
PROCEDURE InsertLocations
   
(
   
    p_location_id OUT NUMBER,
   
    p_street_address IN VARCHAR2,
   
    p_postal_code IN VARCHAR2,
   
    p_city IN VARCHAR2,
   
    p_state_province IN VARCHAR2,
   
    p_country_id IN CHAR
   
)
   
AS
   
BEGIN
   
    INSERT INTO LOCATIONS (
   
        LOCATION_ID,
   
        STREET_ADDRESS,
   
        POSTAL_CODE,
   
        CITY,
   
        STATE_PROVINCE,
   
        COUNTRY_ID)
   
    VALUES (
   
        LOCATIONS_SEQ.NEXTVAL,
   
        p_street_address,
   
        p_postal_code,
   
        p_city,
   
        p_state_province,
   
        p_country_id
   
    );
   
    
   
    SELECT LOCATIONS_SEQ.CURRVAL INTO p_location_id FROM DUAL;
   
END InsertLocations;
   
END CRUD_LOCATIONS;
   

The following code defines a DataAdapter to use the procedures defined in the above package to create, retrieve, update, and delete data allowing the DataAdapter. The DataAdapter can be used both to retrieve data into a DataSet and update the Oracle database with changes made to the DataSet.

// define the connection string
   
String connString = "Data Source=oracledb;User Id=UserID;Password=Password;";
   

  
    
  
// create the data adapter
   
OracleDataAdapter da = new OracleDataAdapter();
   

  
    
  
// define the select command for the data adapter
   
OracleCommand selectCommand =
   
    new OracleCommand("CRUD_LOCATIONS.GetLocations",
   
    new OracleConnection(connString));
   
selectCommand.CommandType = CommandType.StoredProcedure;
   
selectCommand.Parameters.Add("cur_Locations",
   
    OracleType.Cursor).Direction = ParameterDirection.Output;
   
da.SelectCommand = selectCommand;
   

  
    
  
// define the udpate command for the data adapter
   
OracleCommand updateCommand =
   
    new OracleCommand("CRUD_LOCATIONS.UpdateLocations",
   
    new OracleConnection(connString));
   
updateCommand.CommandType = CommandType.StoredProcedure;
   
updateCommand.Parameters.Add("p_location_id", OracleType.Number, 4,
   
    "LOCATION_ID");
   
updateCommand.Parameters.Add("p_street_address", OracleType.VarChar, 40,
   
    "STREET_ADDRESS");
   
updateCommand.Parameters.Add("p_postal_code", OracleType.VarChar, 12,
   
    "POSTAL_CODE");
   
updateCommand.Parameters.Add("p_city", OracleType.VarChar, 30, "CITY");
   
updateCommand.Parameters.Add("p_state_province", OracleType.VarChar, 25,
   
    "STATE_PROVINCE");
   
updateCommand.Parameters.Add("p_country_id", OracleType.Char, 2,
   
    "COUNTRY_ID");
   
da.UpdateCommand = updateCommand;
   

  
    
  
// define the delete command for the data adapter
   
OracleCommand deleteCommand =
   
    new OracleCommand("CRUD_LOCATIONS.DeleteLocations",
   
    new OracleConnection(connString));
   
deleteCommand.CommandType = CommandType.StoredProcedure;
   
deleteCommand.Parameters.Add("p_location_id", OracleType.Number, 4,
   
    "LOCATION_ID");
   
da.DeleteCommand = deleteCommand;
   

  
    
  
OracleCommand insertCommand =
   
    new OracleCommand("CRUD_LOCATIONS.InsertLocations",
   
    new OracleConnection(connString));
   
insertCommand.CommandType = CommandType.StoredProcedure;
   
insertCommand.Parameters.Add("p_location_id", OracleType.Number, 4,
   
    "LOCATION_ID");
   
insertCommand.Parameters.Add("p_street_address", OracleType.VarChar, 40,
   
    "STREET_ADDRESS");
   
insertCommand.Parameters.Add("p_postal_code", OracleType.VarChar, 12,
   
    "POSTAL_CODE");
   
insertCommand.Parameters.Add("p_city", OracleType.VarChar, 30, "CITY");
   
insertCommand.Parameters.Add("p_state_province", OracleType.VarChar, 25,
   
    "STATE_PROVINCE");
   
insertCommand.Parameters.Add("p_country_id", OracleType.Char, 2,
   
    "COUNTRY_ID");
   
da.InsertCommand = insertCommand;
   

  
    
  
// define a DataTable and fill it using the data adapter
   
DataTable dt = new DataTable();
   
da.Fill(dt);
   

  
    
  
// ... do work that adds, edits, updates, or deletes records in the table
   

  
    
  
// call the Update() method of the data adapter to update the Oracle
   
// database with changes made to the data
   
da.Update(dt);
   

Working with Multiple Result Sets

Oracle does not support batch queries, so you cannot return multiple result sets from a command. With a stored procedure, returning multiple result sets is similar to returning a single result set; you have to use REF CURSOR output parameters. To return multiple result sets, use multiple REF CURSOR output parameters.

The package spec that returns two result sets—all EMPLOYEES and JOBS records—follows:

CREATE OR REPLACE PACKAGE SELECT_EMPLOYEES_JOBS AS
   
TYPE T_CURSOR IS REF CURSOR;
   
PROCEDURE GetEmployeesAndJobs (
   
    cur_Employees OUT T_CURSOR,    
   
    cur_Jobs OUT T_CURSOR
   
);
   
END SELECT_EMPLOYEES_JOBS;
   

The package body follows:

CREATE OR REPLACE PACKAGE BODY SELECT_EMPLOYEES_JOBS AS
   
PROCEDURE GetEmployeesAndJobs
   
(
   
    cur_Employees OUT T_CURSOR,
   
    cur_Jobs OUT T_CURSOR
   
)
   
IS
   
BEGIN
   
    -- return all EMPLOYEES records
   
    OPEN cur_Employees FOR
   
    SELECT * FROM Employees;
   

  
    
  
    -- return all JOBS records
   
    OPEN cur_Jobs FOR
   
    SELECT * FROM Jobs;
   
END GetEmployeesAndJobs;
   
END SELECT_EMPLOYEES_JOBS;
   

The following code shows how to fill two related tables in a DataSet using the two result sets returned from the above package:

// create the connection
   
OracleConnection conn = new OracleConnection("Data Source=oracledb;
   
    User Id=UserID;Password=Password;");
   

  
    
  
// define the command for the stored procedure
   
OracleCommand cmd = new OracleCommand();
   
cmd.Connection = conn;
   
cmd.CommandText = "SELECT_EMPLOYEES_JOBS.GetEmployeesAndJobs";
   

  
    
  
// add the parameters including the two REF CURSOR types to retrieve
   
// the two result sets
   
cmd.Parameters.Add("cur_Employees", OracleType.Cursor).Direction =
   
    ParameterDirection.Output;
   
cmd.Parameters.Add("cur_Jobs", OracleType.Cursor).Direction =
   
    ParameterDirection.Output;
   
cmd.CommandType = CommandType.StoredProcedure;
   

  
    
  
// create the DataAdapter and map tables
   
OracleDataAdapter da = new OracleDataAdapter(cmd);
   
da.TableMappings.Add("Table", "EMPLOYEES");
   
da.TableMappings.Add("Table1", "JOBS");
   

  
    
  
// create and fill the DataSet
   
DataSet ds = new DataSet();
   
da.Fill(ds);
   

  
    
  
// create a relation
   
ds.Relations.Add("EMPLOYEES_JOBS_RELATION",
   
   ds.Tables["JOBS"].Columns["JOB_ID"],
   
   ds.Tables["EMPLOYEES"].Columns["JOB_ID"]);
   

  
    
  
// output the second employee (zero-based array) and job title
   
// based on the relation
   
Console.WriteLine("Employee ID: " +
   
    ds.Tables["EMPLOYEES"].Rows[1]["EMPLOYEE_ID"] +
   
    "; Job Title: " +
   
    ds.Tables["EMPLOYEES"].Rows[1].GetParentRow(
   
    "EMPLOYEES_JOBS_RELATION")["JOB_TITLE"]);
   

The console output shows the job title for the second employee:

Employee ID: 101; Job Title: Administration Vice President
   

Conclusion

The Oracle .NET data provider makes it easy to execute stored procedures and access the return values, whether they are one or more scalar values or result sets. You can use Oracle procedures together with the OracleDataAdapter to fill a DataSet, work with the disconnected data, and update the changes to the Oracle database at a later time.

The main differences between Oracle procedures and Microsoft SQL Server stored procedures are that Oracle procedures must return values as output parameters, and that result sets must be returned as REF CURSOR objects to the calling program using output parameters.

角谱迭代与傅里叶变换迭代算法(GS)的原理及其实例演示分析 角谱迭代与傅里叶变换迭代算法(GS)的原理及其实例演示分析 目前,迭代法和强度传输方程(TIE)法是两类典型的非干涉相位检索技术(Phase Retrieval,被称为相位恢复、相位检索、相位反演法、相位复原等)。迭代法中的经典算法是Gerchberg-Saxton(GS),随后,出现了包括错误减少算法(ER)、混合输入输出法(HIO)、梯度搜索算法、角谱迭代算法等改进算法。... 阅读详情

相关推荐

CANoe之UDS诊断自动化测试(四):创建诊断自定义Panel发送诊断(替代Session Control窗口)

通过自定义的Panel界面实现诊断发送的功能,并且还可以根据自己的测试需求添加需要的诊断信息,使用非常灵活。

weixin_69677286的博客 1903

Working with MS Access Stored Procedures in VB.NET Montaque(转贴) (转)

Working with MS Access Stored Procedures in VB.NET Montaque(转贴) (转)[@more@]Working with MS Access Stored Procedure...

csd3176的博客 226

YOLOv8实战:5分钟搞定Windows环境下的目标检测模型部署(附常见报错解决方案)

本文详细介绍了在Windows环境下快速部署YOLOv8目标检测模型的实战指南,包括环境配置、模型加载、快速验证及常见报错解决方案。通过5分钟快速入门教程,帮助开发者高效实现计算机视觉应用,特别适合需要快速上手的AI开发者。

weixin_30566063的博客 556

Working with MS Access Stored Procedures in VB.NET Montaque(转贴)

Working with MS Access Stored Procedures in VB.NET. Part 1 by David Wasserman, MCP 2/27/2002 Article source code: msaccess_sp.zipIntroductionIn the more recent releases of Microsoft Access, grea

Montaque & DotNet 1890

论文Agile Autonomy: Learning High-Speed Flight in the Wild开源代码运行记录

​ Agile Autonomy: Learning High-Speed Flight in the Wild Learning High-Speed Flight in the Wild 论文链接:https://www.science.org/doi/10.1126/scirobotics.abg5810 代码:https://github.com/uzh-rpg/agile_autonomy 国内复现:https://github.com/Howardcl/AgileAutonomyPX4

Hanamizukimr的博客 1488

告别RXTX:SpringBoot集成jSerialComm实现更现代的串口通信(附完整代码对比)

本文详细介绍了如何从传统的RXTX库迁移到jSerialComm,实现SpringBoot项目中更现代的串口通信。通过对比两种技术的依赖配置、API设计和跨平台支持,展示了jSerialComm在简化部署、提升性能和增强稳定性方面的优势,并提供了完整的代码示例和实战经验。

weixin_42522964的博客 284

Oracle静默安装,含仅软件安装和数据库安装及常见报错处理方法DBT-10317 DBT-05509 [FATAL] ORA-03114

本文详解Oracle 19c单机版静默安装与数据库创建全流程,涵盖目录配置、响应文件编写、安装及建库命令,附常见报错(如DBT-10317、ORA-03114)解析与解决方案,包含删库指令与日志分析,适合内网环境自学实战,免费干货,实用性强。

qq_41474349的博客 173

STM32Cube高效开发教程<基础篇>(一)----概述

一、 STM32系列器件和开发工具发展历史声明:本人水平有限,博客可能存在部分错误的地方,请广大读者谅解并向本人反馈错误。本专栏博客参考《STM32Cube高效开发教程(基础篇)》,有意向的读者可以购买正版书籍辅助学习,本书籍由王维波老师、鄢志丹老师、王钊老师倾力打造,书籍内容干货满满。一、 STM32系列器件和开发工具发展历史。

我的博客 1171

Mysql数据库基础篇(三):全面掌握数据库与数据表操作:深度剖析底层文件差异与核心管理机制

如果你想彻底改掉某个字段的名字,应该使用 change。实例:将 employee表中的 name列修改为xingming注意:使用 change时,即便不改变数据类型,在指定了新列名后依然需要完整地写一遍它的数据类型与约束定义。

2501_93641778的博客 229

数据资产入表——解读100页数据资产入表解读与分析【附全文阅读】

本文档《数据资产入表:解读与分析》面向企业财务、数据管理、法务合规及高管等核心岗位,系统解析数据资产入表全流程。以"政策-实操-价值"为主线,首先解读入表政策要求,明确会计处理规范;其次详细拆解数据盘点、治理、确权、评估等实操步骤,提供行业案例参考;最后分析入表难点及后续运营策略,为企业提供从政策理解到落地执行的一站式解决方案。

cdfunlove的博客 389

MATLAB - 利用非线性模型预测控制(Nonlinear MPC)来控制四旋翼飞行器

本示例展示了如何利用非线性模型预测控制(MPC)为四旋翼飞行器设计一个跟踪轨迹的控制器。

机器人控制与仿真应用 4431

Oracle 增量同步配置指南:只搬变了的数据,不重复也不漏数

Oracle 老 ERP 的数据要同步到新系统,全量搬不动、增量又怕漏。这篇按八步走完:账号权限、看版本号、建任务组、选变化识别字段、定频率、处理删除、排查琥珀色叹号。重点在第四步和第七步,五个坑都在那儿。

weixin_39226344的博客 381

BGP路由聚合:自动聚合和手动聚合

路由聚合是指将多条具体的路由合并为一条聚合路由,以减少路由表的条目数。这种技术通过只向对等体发送聚合后的路由,而不发送所有的具体路由,来减小路由表的规模。同时,被聚合的路由如果发生路由振荡,也不再对网络造成影响,从而提高了网络的稳定性。

网络技术联盟站 1219

智叟云集系统 · 数据分层与管理体验

智叟云集系统 · 数据分层与管理体验 · 数据分层与管理体验。

2301_77178996的博客 414

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

oracle直方图测试

预期:没有直方图时,优化器假设 status 分布均匀(选择性 = 1/NDV ≈ 1/100),两个查询的预估行数(E-Rows)会非常接近(约 1000 行),很可能对两个查询都选择索引扫描,或者都选择全表扫描,无法区分数据量的巨大差异。

ZHANG123HUO的博客 50

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Oracle 19c RAC ASM 在线扩容实战

最近有一套 Oracle 19c RAC 数据库,DATA 磁盘组空间使用率已经接近 90%,需要扩容。环境是两节点 RAC,底层使用 Dell EMC SAN 存储,通过 Multipath 管理共享磁盘,存储侧已经分配好一块 2TB 的新 LUN,只需要在操作系统上识别,然后加入 ASM 磁盘组。整个过程不需要停库,本文记录一下实际操作步骤。这次 Oracle 19c RAC ASM 扩容整体比较顺利。

LuciferLiu_DBA 270

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

15_MySQL 数据同步与迁移方案 —— 复制 / DataX / Canal / 异构同步(对标 Oracle GoldenGate)

15_MySQL 数据同步与迁移方案 —— 复制 / DataX / Canal / 异构同步(对标 Oracle GoldenGate) 系列定位 :《MySQL 数据库全栈运维实战系列》第 15 篇,与《Oracle 数据库全栈运维实战系列(VMware 版)》同构对标(Oracle 15 OG…

dk843108982的博客 232

任务8-1 数据库用户权限管理

本实战训练MySQL权限管理的完整流程。通过CREATE USER建用户,GRANT按需授权,SHOW GRANTS核查权限。岗位变动时,使用REVOKE回收多余权限,并用FLUSH PRIVILEGES刷新缓存。全流程旨在落实最小权限原则,规范数据库安全运维。

howard2005的专栏 150

带着小白学习-微服务-5 实战:配置中心-数据库连接

如前所述,服务治理主要包含注册中心,前面所有的微服务子工程均已注册到nacos,然后由相应的应用到nacos进行拉取。接下来,我们将使用nacos的配置中心,用于保存和分发应用运行所需的动态配置。比如数据库的各类信息等。

wdyan297的博客 206

SQL Server的系统存储过程sp_MSforeachdb,实现遍历所有数据库,查询数据库中是否存在表

sp_MSforeachdb是SQL Server中一个未公开的系统存储过程,用于遍历当前实例下的所有数据库,并对每个数据库执行指定的SQL命令。它相当于一个“批量循环工具”,可以避免手动逐个数据库操作或写复杂的游标脚本。🔄 核心作用?

weixin_43933286的博客 179

MySQL数据库和表的操作

本文系统介绍了MySQL数据库与表的基本操作。包括创建、查看、修改和删除数据库,重点讲解字符集与校验规则对大小写敏感性的影响;通过SHOW、ALTER、DROP等命令实现数据库管理;介绍数据备份(mysqldump)与恢复(SOURCE)方法;并详细阐述表的创建、结构查看(DESC)、字段增删改(ALTER TABLE)及表删除操作,涵盖字段类型、注释、索引等关键要素,为数据库开发与维护提供完整指导。

2201_75570391的博客 355

Oracle 19c 在线重定义后旧表空间清理及 ASM 存储资源回收 SOP

Oracle 19c 在线重定义后旧表空间清理与 ASM 存储资源回收 SOP

「YYL DBA手记」 数据库 · 架构 · 运维 · 实战 yyldba.blog.youkuaiyun.com 481

【服务器数据恢复】RAID5阵列双盘掉线Oracle+OA系统的数据恢复案例

某医药公司服务器组建RAID5磁盘阵列,阵列内两块硬盘先后离线,直接造成服务器宕机。服务器操作系统为RedhatLinux,承载基于Oracle数据库搭建的OA业务系统,业务数据价值高、恢复时效要求紧急。由于该OA配套Oracle版本早已停止技术支持,客户需求为:完整恢复业务数据,同时尽可能复原原有操作系统环境。

beiya123的博客 123

Oracle 紧急安全警报:Java 9.1 分漏洞 CVE-2026-91403,撤销对 eMudhra 风险证书的信任

本文梳理 Java JSSE 证书信任风险 CVE-2026-91403 及后续 CVE-2026-114820 的处置链路,说明 eMudhra 风险证书、交叉签名与实际利用条件,提供运行时、信任库、容器及 CI 环境的排查方法,并结合供应商公告给出修复、应急缓解和验收清单。

初入江湖,多多关照 543

AI Agent记忆从不是向量库:Oracle拆解读写分离与记忆晋升门,终结无脑存聊天的伪智能——读Wojtek Pluta演讲

在过去一年里,绝大多数团队在为自己的 AI Agent 增加“记忆能力”时,做法几乎如出一辙:开一个向量数据库(Pinecone / Milvus / Chroma),在每轮对话结束时,把用户的提问和 Agent 的回复转成 Embedding 向量直接往里塞;<summary> 审查 Kyle 的贷款完整性,审批未通过,等待补件。这种记忆能力完全脱离了脆弱的会话上下文窗口,靠的是数据库底座驱动的显式记忆装配。在写路径中,决定一条信息能否从“临时上下文”跃升为“长期持久化记忆”的核心关卡,正是。

鲜衣怒马楼兰月 128

Oracle ORA-12560 故障排查实战:Windows TCP 连接资源不足(端口缓冲池爆满)与重启恢复 SOP

Windows 环境 Oracle ORA-12560 故障排查与处理:TCP 连接资源不足及重启恢复实战

「YYL DBA手记」 数据库 · 架构 · 运维 · 实战 yyldba.blog.youkuaiyun.com 421
下一篇: Freeze Table Header and Columns
AIRFLYNET
博客等级 码龄25年 6粉丝 12原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值