Oracle Entity Framework Core pass table parameter to stored procedure - oracle

I am trying to pass a parameter to a stored procedure using the Oracle.EntityFrameworkCore package like this:
DataTable table = new DataTable();
table.Columns.Add("keyColumn", typeof(string));
table.Columns.Add("valueColumn", typeof(string));
var row = table.NewRow();
row.ItemArray = new object[]
{
entry.KeyColumn,
entry.ValueColumn
};
table.Rows.Add(row);
var parameter = new OracleParameter("entries",table);
parameter.UdtTypeName = "entry_type_list";
return context.Database.ExecuteSqlCommandAsync(
new RawSqlString( #"EXEC set_entry_list (:entries)" ),
parameter);
The stored procedure and type are defined like this:
CREATE OR REPLACE TYPE entry_type AS OBJECT
(
"keyColumn" NVARCHAR2(3),
"valueColumn" NVARCHAR2(3)
);
CREATE OR REPLACE TYPE entry_type_list AS TABLE OF entry_type;
CREATE OR REPLACE PROCEDURE set_entry_list (entries entry_type_list) AS
BEGIN
REM Doing stuff
END;
But I get an error:
System.ArgumentException: Value does not fall within the expected range.
at Oracle.ManagedDataAccess.Client.OracleParameter..ctor(String parameterName, Object obj)
The only sources for this is an answer how to do this with SQL Server, but no answer for Oracle with EFCore. The issue here seems to be that Oracle only accepts an OracleParameter whereas others use SqlParameter.
If I use the SqlParameter type like this:
var parameter = new SqlParameter("entries", SqlDbType.Structured);
parameter.TypeName = "entry_type_list";
parameter.Value = table;
I get this error:
System.InvalidCastException: Unable to cast object of type'System.Data.SqlClient.SqlParameter' to type 'Oracle.ManagedDataAccess.Client.OracleParameter'.
I also did try setting parameter.OracleDbType to different values like Blob, RefCursor, Clob or XmlType, setting parameter.DbType to Object or setting CollectionType to PLSQLAssociativeArray with no success. Also passing a list or an array of objects instead of a table did not succeed.
I currently have no idea what else I could try.
Any method to pass a big amount of entities to a stored procedure in a performant way would help. I use them with the merge-command so I need to be able to convert those parameters to a table.

I now found a solution using a temporary table and using this one as my input parameter.
As I can't pass a complete table, but an array of simple objects I have to fill this table by passing one array for each column:
var keyColumn = new OracleParameter( "keyColumn", OracleDbType.Decimal );
keyColumn.Value = values.Select( c => c.KeyColumn).ToArray();
var valueColumn = new OracleParameter( "valueColumn", OracleDbType.Decimal );
valueColumn = values.Select( c => c.ValueColumn).ToArray();
using ( var transaction = this.dbContext.Database.BeginTransaction( IsolationLevel.ReadCommitted) )
{
var connection = this.dbContext.Database.GetDbConnection() as OracleConnection;
OracleCommand cmd = connection.CreateCommand();
cmd.CommandText = #"
INSERT INTO TMP_TABLE
(
""keyColumn"",
""valueColumn""
)
VALUES (
:keyColumn,
:valueColumn)";
cmd.Parameters.Add( keyColumn );
cmd.Parameters.Add( valueColumn );
cmd.ArrayBindCount = values.Length;
var insertCount = await cmd.ExecuteNonQueryAsync();
cmd = connection.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "dbo.stored_procedure";
var result = await cmd.ExecuteNonQueryAsync();
transaction.Commit();
}
I created the temp table like this:
CREATE
GLOBAL TEMPORARY TABLE "dbo"."TMP_TABLE"
ON COMMIT DELETE ROWS
AS SELECT * FROM "dbo"."REAL_TABLE" WHERE 0=1;
And changed my stored procedure to use it:
CREATE OR REPLACE PROCEDURE stored_procedure AS
BEGIN
REM use the "dbo"."TMP_TABLE"
END;
This answer helped me with the approach of bulk inserting with one array per column. The thread also contains some further discussion about the topic and a more generic approach.

Related

How to Insert a row and return autoincrement value in sqlite in wp?

I have an app using sqlite client. In the app i insert the data in a table and i need the Id which is autoincreament. Is there anyway to get the id in executenonquery? s.th like sqlparameter or ...
I am using the following method to fetch data and i thought rec variable hold the id but it is always rec=1 and i dont know what this is good for?
int x= (System.Windows.Application.Current as App).db.Insert
<CsWidget>(ObjWidget, #"Insert into Tbl_Widget (Name) values(#Name");
public int Insert<T>(T obj, string statement) where T : new()
{
Open();
SQLiteCommand cmd = db.CreateCommand(statement);
int rec = cmd.ExecuteNonQuery(obj);
return rec;
}
Try ExecuteScalar method instead of ExecuteNonQuery. It can work that way depending on your SQLite wrapper. If not the separate query after insert is your only way.
As #gleb.kudr said, a separate query after the insert is the only way.
cmd.ExecuteNonQuery();
cmd.CommandText = "SELECT LAST_INSERT_ROWID()";
object r = cmd.ExecuteScalar();
int id = 0;
int.TryParse( r.ToString(), out id );

Update entity columns iterating through col list using LINQ

I can get column list from the table using LINQ like this:
OrderDataContext ctx = new OrderDataContext();
var cols = ctx.Mapping.MappingSource
.GetModel( typeof( OrderDataContext ) )
.GetMetaType( typeof( ProductInformation ) )
.DataMembers;
This gives me the list of columns, so I can do this:
foreach ( var col in cols )
{
// Get the value of this column from another table
GetPositionForThisField( col.Name );
}
So this all works, I can iterate through column list and pull the values for those columns from an another table (since the column names are the keys in that another table), so I don't have to do switch....or lot of if...then...
Now the question:
After I get these values, how do I populate the entity in order to save it back? I would normally go like this:
ProductInformation info = new ProductInformation();
info.SomeField1 = val1;
info.SomeField2 = val2;
ctx.ProductInformation.InsertOnSubmit( info );
ctx.SubmitChanges();
But how to use the same column collection from above to populate the columns while iterating over that, when there is no such thing as:
info["field1"].Value = val1;
Thanks.
Just fetch the object that you want to modofy, set the property and call SubmitChanges. There is no need to create a new object and insert it. The Context tracks your changed properties and generates the update statement accordingly. In your case you may want to set the properties via reflection rather than manually since you are reading them from another table.
You'll need to use reflection. Assuming you can get the PropertyInfo from the metadata:
PropertyInfo property = GetPropertyForThisField(col.Name);
property.SetValue(info, val1, null);

ORA-04043 Error when oracle type is declared in a package [duplicate]

I have a Java app accessing an oracle stored procedure. The arguments to the stored procedure include an array type. I do it like the following...
con = this._getConnection();
Connection narrowdConn = (Connection)WSJdbcUtil.getNativeConnection( (WSJdbcConnection)con );
callable = con.prepareCall("{call MY_PKG.MY_PROCEDURE(?, ?)}");
ArrayDescriptor arrayDescriptor = ArrayDescriptor.createDescriptor("VARCHAR2_ARR", narrowdConn);
ARRAY arrayArg1 = new ARRAY(arrayDescriptor, con, docNames);
ARRAY arrayArg2 = new ARRAY(arrayDescriptor, con, docTypes);
callable.setArray(1, arrayArg1);
callable.setArray(2, arrayArg2);
callable.execute();
Now, I am getting this Exception...
java.sql.SQLException: invalid name pattern: MY_PKG.VARCHAR2_ARR
VARCHAR2_ARR is a public TYPE, defined inside an Oracle Package like the following:
TYPE VARCHAR2_ARR IS TABLE OF VARCHAR2(50);
And used as such in my stored proc...
PROCEDURE MY_PROCEDURE
(V_ARR_ARG1 IN VARCHAR2_ARR,
V_ARR_ARG2 IN VARCHAR2_ARR)
the type VARCHAR2_ARR is a PLSQL type, you won't be able to interface it directly from java. I suggest you look into this thread on AskTom regarding a similar question.
Here are a couple suggestions:
create a SQL TYPE that you can bind from java
insert into a temporary table from java and read from it in plsql
In both cases you will have to either modify the PLSQL procedure or add a new translation procedure.
We need to set accessToUnderlyingConnectionAllowed falg true while creating a datasource
I did stuck at this problem. Hope this illustration might help you:
Steps to create the procedure in oracle.
Create type of the desired object as:
CREATE OR REPLACE TYPE STUDENT_TYPE IS OBJECT
( ID NUMBER,
NAME VARCHAR2(50));
Create another object of array as:
CREATE OR REPLACE TYPE STUDENT_ARRAY IS TABLE OF STUDENT_TYPE;
Create procedure to accepting array of object and do the desired operation, I am here just inserting the entries in table as:
CREATE OR REPLACE PROCEDURE SP_INSERT_STUDENT_RECORD_IN_BULK(
IN_STUDENT_RECORDS STUDENT_ARRAY) IS
BEGIN
FOR i IN IN_STUDENT_RECORDS.first.. IN_STUDENT_RECORDS.last
LOOP
BEGIN
INSERT INTO STUDENT(ID,NAME) VALUES
(IN_STUDENT_RECORDS(i).ID, IN_STUDENT_RECORDS(i).NAME)
END;
END LOOP:
END SP_INSERT_STUDENT_RECORD_IN_BULK;
Code snippet to call Procedure through Java
import org.springframework.jdbc.core.*;
import oracle.jdbc.OracleConnection;
....
List<Student> students = getStudentList();
try(Connection hikariCon = dataSource.getConnection()){
if(hikariCon.isWrapperFor(OracleConnection.class)){
OracleConnection con = hikariCon.unwrap(OracleConnection.class);
Object[] students = students.stream().map(student -> {
return con.createStruct(STUDENT_TYPE, new Object[]{
student.getId(),
student.getName()
});
}).collect(Collectors.list()).flatMap(List::stream).toArray();
SimpleJdbcCall jdbcCall = new SimpleJdbcCall(dataSource)
.withProcedureName("SP_INSERT_STUDENT_RECORD_IN_BULK")
.declareParameters(
new SqlParameter("IN_STUDENT_RECORDS", Types.ARRAY));
jdbcCall.execute(con.createOracleArray(STUDENT_ARRAY,students))
}
} catch(Exception e){
log.error("Error due to- {}",e,getMessage());
}

How to call stored procedure in MVC by EF

Where can I get good tutorial on Entity framework with Stored Procedure in MVC framework?
Is it better to use Enterprise library in this case when I have almost everything written in the stored procedure.
Note: I am using stored procedure because they are really very complex and some of them is over 1000 lines.
MVC is in this case absolutely not related. The way how you call stored procedure from EF will be still the same. I guess you want to use stored procedures without actually using entities and linq-to-entities (main EF features), don't you? Generally you need:
EDMX file (ado.net entity data model) where you run update from database and add all stored procedures you want to use. EDMX file also generates derived ObjectContext and all entities by default.
Next you must go to Model Browser and create Function import for each procedure. Function import will create method on the derived ObjectContext which will allow you call the stored procedure as any other .net method.
During function import you will have to create complex type (it can happen automatically) for result set returned from stored procedure.
You also don't have to use function imports at all and you can execute procedures directly by calling either:
objectContext.ExecuteSqlCommand("storedProcedureName", SqlParameters) for SPs not returning record set
objectContext.ExecuteStoreQuery<ResultType>("storedProcedureName", SqlParameters) for SPs returning record set. ResultType must have properties with same names as columns in result set. It can work only with flat types (no nested objects).
There are some limitations when using stored procedures:
Entity framework doesn't like stored procedures which returns dynamic result sets (based on some condition result set has different columns)
Entity framework doesn't support stored procedures returning multiple result sets - there are EFExtensions which does but it is more like doing ADO.NET directly.
If you are using Entityframwork Code-first,This way you can Use your stored-Procedure, In this Example I have four Input parameters.
var startDateTY = masterSales.PolicyStartDate;
var endateTY = masterSales.PolicyEndDate;
var startDatePY = masterSales.PolicyStartDate.Value.AddYears(-1);
var endatePY = masterSales.PolicyEndDate.Value.AddYears(-1);
var spParameters = new object[4];
spParameters[0] = new SqlParameter()
{
ParameterName = "startDateTY",
Value = startDateTY
};
spParameters[1] = new SqlParameter()
{
ParameterName = "endateTY",
Value = endateTY
};
spParameters[2] = new SqlParameter()
{
ParameterName = "startDatePY",
Value = startDatePY
};
spParameters[3] = new SqlParameter()
{
ParameterName = "endatePY",
Value = endatePY
};
var datalist = objContext.Database.SqlQuery<vMasterSalesAgentReport>("dbo.usp_GetSalesAgentReport #startDateTY,#endateTY,#startDatePY,#endatePY", spParameters).ToList();
store = "sp_selectmark #regid='" + id + "'";
var st = db.ExecuteStoreQuery<Sp>("exec " + store).ToList();
GridView1.DataSource = st;
GridView1.DataBind();
or
string store = "";
store = "sp_inserttbreg #name='" + regobj.name + "',#age='" + regobj.age + "',#place='" + regobj.place + "',#gender='" + regobj.gender + "',#email='" + regobj.email + "',#fon='" + regobj.fon + "'";

How to return a RefCursor from Oracle function?

I am trying to execute a user-defined Oracle function that returns a RefCursor using ODP.NET. Here is the function:
CREATE OR REPLACE FUNCTION PKG.FUNC_TEST (ID IN TABLE.ID%type)
RETURN SYS_REFCURSOR
AS
REF_TEST SYS_REFCURSOR;
BEGIN
OPEN REF_TEST FOR
SELECT *
FROM TABLE;
RETURN REF_TEST;
END;
/
I can call this function in Toad (select func_test(7) from dual) and get back a CURSOR. But I need to get the cursor using C# and ODP.NET to fill a DataSet, but I keep getting a NullReferenceException - "Object reference not set to an instance of an object". Here is what I have for that:
OracleConnection oracleCon = new OracleConnection(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString);
OracleCommand sqlCom = new OracleCommand("select func_test(7) from dual", oracleCon);
sqlCom.Parameters.Add("REF_TEST", OracleDbType.RefCursor, ParameterDirection.ReturnValue);
OracleDataAdapter dataAdapter = new OracleDataAdapter();
dataAdapter.SelectCommand = sqlCom;
DataSet dataSet = new DataSet();
dataAdapter.Fill(dataSet); //FAILS HERE with NullReferenceException
I was able to find lots of info and samples on using stored procedures and ODP.NET, but not so much for returning RefCursors from functions.
EDIT: I do not want to explicitly add input parameters to the OracleCommand object (i.e. sqlCom.Parameters.Add("id", OracleDbType.Int32,ParameterDirection.Input).Value = 7;) as that makes it difficult to implement this as a generic RESTful web service, but I'm reserving it as my last resort but would use stored procedures instead.
Any help is much appreciated!
I think you are missing the sqlCom.ExecuteNonQuery();
also, instead of running the select func_test(7) from dual; lets switch it to run the function and pass in the param
OracleConnection oracleCon = new OracleConnection(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString);
// Set the command
string anonymous_block = "begin " +
" :refcursor1 := func_test(7) ;" +
"end;";
//fill in your function and variables via the above example
OracleCommand sqlCom= con.CreateCommand();
sqlCom.CommandText = anonymous_block;
// Bind
sqlCom.Parameters.Add("refcursor1", OracleDbType.RefCursor);
sqlCom.Parameters[0].Direction = ParameterDirection.ReturnValue;
try
{
// Execute command; Have the parameters populated
sqlCom.ExecuteNonQuery();
// Create the OracleDataAdapter
OracleDataAdapter da = new OracleDataAdapter(sqlCom);
// Populate a DataSet with refcursor1.
DataSet ds = new DataSet();
da.Fill(ds, "refcursor1", (OracleRefCursor)(sqlCom.Parameters["refcursor1"].Value));
// Print out the field count the REF Cursor
Console.WriteLine("Field count: " + ds.Tables["refcursor1"].Columns.Count);
}
catch (Exception e)
{
Console.WriteLine("Error: {0}", e.Message);
}
finally
{
// Dispose OracleCommand object
cmd.Dispose();
// Close and Dispose OracleConnection object
con.Close();
con.Dispose();}
this is based on the example ODP that can be found # %ora_home%\Client_1\ODP.NET\samples\RefCursor\Sample5.csproj
If you want to avoid (for better or worst!) the custom built param collection for each proc/function call you can get around that by utilizing anonymous blocks in your code, I have ammended (once again untested!) the code above to reflect this technique.
Here is a nice blog (from none other than Mark Williams) showing this technique.
http://oradim.blogspot.com/2007/04/odpnet-tip-anonymous-plsql-and.html

Resources