Send Int parameter from C# to Stored Procedure - c#

I have stored procedure which takes ID (INT) when I run query using DbContext.Database.SqlQuery I doesn't work. I have another Store Procedure which been script in very similar pattern except it taking all string parameter, perhaps I doing something wrong on this Int passing parameter!!!!
model class
public class DeleteFunctionNavigation_SP_Map
{
public int FunctionID { get; set; }
}
Stored Procedure
ALTER PROCEDURE [dbo].[DeleteFunctionsNavigation]
#FunctionID INT,
#Action_identity INT OUTPUT,
#ActionInFunction_Count INT OUT,
#Controller_identity INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SELECT #Action_identity = Navigation_FunctionInAction.ActionID
FROM Navigation_FunctionInAction
WHERE Navigation_FunctionInAction.Function_ID = #FunctionID
..........
//my other code here!
RETURN
END
C# Class
public void DeleteNavigationFunctionByID(int _FunctionNavigationID)
{
using (var dbContext = new FunctionContext())
{
var Action_identity_out = new SqlParameter("Action_identity", SqlDbType.Int) { Direction = System.Data.ParameterDirection.Output };
var ActionInFunction_Count_out = new SqlParameter("ActionInFunction_Count", SqlDbType.Int) { Direction = System.Data.ParameterDirection.Output };
var Controller_identity_out = new SqlParameter("Controller_identity", SqlDbType.Int) { Direction = System.Data.ParameterDirection.Output };
var _query = dbContext.Database.SqlQuery<DeleteFunctionNavigation_SP_Map>("exec DeleteFunctionsNavigation #FunctionID, #Action_identity out, #ActionInFunction_Count out, Controller_identity out",
new SqlParameter("#FunctionID", SqlDbType.Int).Value = _FunctionNavigationID,
Action_identity_out,
ActionInFunction_Count_out,
Controller_identity_out
);
}
}
Controller Method
[HttpPost]
public ActionResult DeleteFunctionNavigationByID(int _selectedNavigationFunctionID)
{
try
{
_FN_Services_a2.DeleteFunctionNavigationByID(_selectedNavigationFunctionID);
}
catch (Exception ex)
{
ModelState.AddModelError("", "Unable To Delete Requested Record!" + ex);
}
return RedirectToAction("SystemCoreHome");
}

Here's how I did this. I created a function like this:
public int ExecuteNonQueryInt(string commandString, bool isStoredProc = false, params object[] param)
{
int result = 0;
try
{
using (SqlConnection con = new SqlConnection("Your connection string here"))
{
con.Open();
using (SqlCommand cmd = con.CreateCommand())
{
cmd.CommandText = commandString;
cmd.CommandType = isStoredProc ? CommandType.StoredProcedure : CommandType.Text;
foreach (var parm in param)
{
cmd.Parameters.Add(parm);
}
result = cmd.ExecuteNonQuery();
}
con.Close();
}
}
catch (Exception)
{
result = 0;
}
return result;
}
Then I called it like this
SqlParameter[] sqlParameters = new SqlParameter[3]
{
new SqlParameter() { ParameterName = "keywords" , Value = dataTable, SqlDbType = SqlDbType.Structured },
new SqlParameter() { ParameterName = "moduleId" , Value = moduleId, SqlDbType = SqlDbType.UniqueIdentifier },
new SqlParameter() { ParameterName = "createdBy" , Value = createdBy, SqlDbType = SqlDbType.Int }
};
return base.ExecuteNonQueryInt("Your stored procedure", true, sqlParameters) > 0;

I have found the reason why it was not working
1) I use list of against business class
2) I was instantiating SQL parameter wrong or well it did not work for me; working model as following;
public void DeleteNavigationFunctionByID(int _FunctionNavigationID)
{
using (var dbContext = new FunctionContext())
{
List<DeleteFunctionNavigation_SP_Map> _query;
var Action_identity_out = new SqlParameter("Action_identity", SqlDbType.Int) { Direction = System.Data.ParameterDirection.Output };
var ActionInFunction_Count_out = new SqlParameter("ActionInFunction_Count", SqlDbType.Int) { Direction = System.Data.ParameterDirection.Output };
var Controller_identity_out = new SqlParameter("Controller_identity", SqlDbType.Int) { Direction = System.Data.ParameterDirection.Output };
_query = dbContext.Database.SqlQuery<DeleteFunctionNavigation_SP_Map>("exec DeleteFunctionsNavigation #FunctionID, #Action_identity out, #ActionInFunction_Count out, #Controller_identity out",
new SqlParameter("#FunctionID", _FunctionNavigationID),
Action_identity_out,
ActionInFunction_Count_out,
Controller_identity_out
).ToList();
}
}

Related

Return output parameter from C#

I have a procedure with 2 input parameters and 2 output parameters. Currently its working fine. However, i want to take the two output parameters of the procedure and store it in a variable. Can anyone guide me how.
Stored procedure code:
create or replace PROCEDURE P_LOGIN_USER
(
USERNAME IN VARCHAR2
, ENCRYPTIONKEY IN VARCHAR2
, OUT_STATUS OUT VARCHAR2
, OUT_STATUS_DESC OUT VARCHAR2
)
------------------------Procedure Code
END P_LOGIN_USER
C# Code where i assign the input parameters. Missing part is getting the output parameters
OracleCommand cmd = new OracleCommand("P_LOGIN_USER", OrCon);
cmd.CommandType = CommandType.StoredProcedure;
OracleParameter in_username = new OracleParameter();
in_username.OracleDbType = OracleDbType.Varchar2;
in_username.Direction = ParameterDirection.Input;
in_username.Size = 500;
in_username.Value = username;
cmd.Parameters.Add(in_username);
OracleParameter in_ecnryptionkey = new OracleParameter();
in_ecnryptionkey.OracleDbType = OracleDbType.Varchar2;
in_ecnryptionkey.Direction = ParameterDirection.Input;
in_ecnryptionkey.Size = 500;
in_ecnryptionkey.Value = password;
cmd.Parameters.Add(in_ecnryptionkey);
OracleParameter out_1 = new OracleParameter();
out_1.OracleDbType = OracleDbType.Varchar2;
out_1.Direction = ParameterDirection.Output;
out_1.Size = 500;
cmd.Parameters.Add(out_1);
OracleParameter out_2 = new OracleParameter();
out_2.OracleDbType = OracleDbType.Varchar2;
out_2.Direction = ParameterDirection.Output;
out_2.Size = 500;
cmd.Parameters.Add(out_2);
try
{
OrCon.Open();
cmd.ExecuteNonQuery();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "P_LOGIN_USER";
cmd.Parameters.Add(new OracleParameter
{
ParameterName = "result",
Size = 1,
Direction = ParameterDirection.ReturnValue,
OracleDbType = OracleDbType.Varchar2
});
}
catch (OracleException ex)
{
Console.Write(ex.Message);
}
OrCon.Close();
You can get the value out output parameter like this.
string outputStatus = Convert.ToString(cmd.Parameters["#OUT_STATUS "].Value);
Put the above line after ExecuteNonQuery()
Stored procedure code:
create or replace PROCEDURE P_LOGIN_USER
(
USERNAME IN VARCHAR2
, ENCRYPTIONKEY IN VARCHAR2
, p_recordset OUT SYS_REFCURSOR) IS
BEGIN
OPEN p_recordset FOR
SELECT OUT_STATUS ,OUT_STATUS_DESC .....
END
C#:
using (var reader = cmd.ExecuteReader())
{
List<ListModel> obj= reader.MapToList<ListModel>();
return obj;
}
## **MapToList** ##
public static List<T> MapToList<T>(this DbDataReader dr) where T : new()
{
if (dr != null && dr.HasRows)
{
var entity = typeof(T);
var entities = new List<T>();
var propDict = new Dictionary<string, PropertyInfo>();
var props = entity.GetProperties(BindingFlags.Instance | BindingFlags.Public);
propDict = props.ToDictionary(p => p.Name.ToUpper(), p => p);
List<string> log = new List<string>();
while (dr.Read())
{
try
{
T newObject = new T();
for (int index = 0; index < dr.FieldCount; index++)
{
var columnname = dr.GetName(index).ToUpper();
if (propDict.ContainsKey(dr.GetName(index).ToUpper()))
{
var info = propDict[dr.GetName(index).ToUpper()];
if ((info != null) && info.CanWrite)
{
try
{
var val = dr.GetValue(index);
info.SetValue(newObject, (val == DBNull.Value) ? null : val, null);
}
catch (Exception ex)
{
var columename= dr.GetName(index).ToUpper();
var val= dr.GetValue(index);
var getype = val.GetType();
log.Add(columename + ":" + val + ":" + getype.ToString());
}
}
}
}
entities.Add(newObject);
}
catch (Exception ex)
{
}
}
return entities;
}
return null;
}

Calling Stored Proc in EF 6 with output parameters?

I have an MVC 5 project that I'm trying to figure out how to call an Oracle stored proc that has output parameters. I've done some searching and this is the closest I've come but I get an error that it is invalid SQL.
Even if I get it to run successfully, I'm assuming it will pass the out put values to the results object and have an IsValid and Remarks properties populated?
public class ValidateCSPolicyResult
{
public string IsValid { get; set; }
public string Remarks { get; set; }
}
using (var ctx = new DataContext())
{
var empId = new SqlParameter("#empId", employeeId);
var startDate = new SqlParameter("#startDate", startDateTime);
var endDate = new SqlParameter("#endDate", endDateTime);
var swapEmpId = new SqlParameter("#swapEmpId", swapEmployeeId);
var isValid = new SqlParameter
{
ParameterName = "Valid",
Value = "N",
Direction = ParameterDirection.Output
};
var remarks = new SqlParameter
{
ParameterName = "Remarks",
Value = "",
Direction = ParameterDirection.Output
};
var results = ctx.Database.SqlQuery(ValidateCSPolicyResult)("VALIDATE_CSW_POLICY #empId, #startDate, #endDate, #swapEmpId, #Valid out, #Remarks out").FirstOrDefault();
}

c# genereic sql context patameter

I have a mssql-context-class for easy access to the database. It contains a function for inserting datarows, that looks like this:
public int? Insert(string tableName, Action<SqlParameterCollection> actionSqlParameterCollection)
{
using (var sqlConnection = new SqlConnection(ConnectionString))
{
sqlConnection.Open();
using (var sqlCommand = sqlConnection.CreateCommand())
{
var commandText = $"insert into {tableName} (#columns) output inserted.id values (#values)";
var valueBuilder = new StringBuilder();
var columnBuilder = new StringBuilder();
actionSqlParameterCollection?.Invoke(sqlCommand.Parameters); //Fill the parameters from outside with some values
foreach (SqlParameter parameter in sqlCommand.Parameters)
{
valueBuilder.Append($",#{parameter.ParameterName}");
columnBuilder.Append($",{parameter.ParameterName}");
}
commandText = commandText.Replace("#values", valueBuilder.ToString().Substring(1));
commandText = commandText.Replace("#columns", columnBuilder.ToString().Substring(1));
sqlCommand.CommandText = commandText;
object result = sqlCommand.ExecuteScalar();
return (int?)result;
}
}
}
Calling this would look something like this:
var context = MsSqlContext.CreateFrom("some_connectionstring");
context.Insert("myTable", parameters => {
parameters.AddWithValue("foo_1", "bar_1");
parameters.AddWithValue("foo_2", "bar_2");
});
Now i want to build a generic sql-context-class which can also handle mysql-databases. The insert-function looks like this so far:
public int? Insert(string tableName, Action<IDataParameterCollection> actionParameterCollection)
{
using (var connection = this.CreateConnection())
{
using (var command = connection.CreateCommand())
{
var commandText = $"insert into {tableName} (#field) values (#values)";
var valueBuilder = new StringBuilder();
var columnBuilder = new StringBuilder();
actionParameterCollection?.Invoke(command.Parameters);
foreach (IDbDataParameter parameter in command.Parameters)
{
valueBuilder.Append($",#{parameter.ParameterName}");
columnBuilder.Append($",{parameter.ParameterName}");
}
commandText = commandText.Replace("#values", valueBuilder.ToString().Substring(1));
commandText = commandText.Replace("#columns", columnBuilder.ToString().Substring(1));
command.CommandText = commandText;
object result = command.ExecuteScalar();
return (int?)result;
}
}
}
When i try to call the function it looks like this:
var context = SqlContext.CreateFrom(SqlProvider.MySql, "Server=localhost;Database=4713_demo;Uid=root;Pwd=;");
context.Insert("my_table", parameters =>
{
parameters.Add(?); //It expects an object
});
My Problem is, dont want to do something like
context.Insert("my_table", parameters =>
{
parameters.Add(context.CreateParameter("foo","bar"));
});
I just want to pass the parametername and the parametervalue. the context-class itself is aware of its provider and should create the parameter. How to afford that?
The solution i came up with, is this SqlParameterizer-class.
public class SqlParameterizer
{
private SqlProvider Provider { get; set; }
private List<IDbDataParameter> ParameterList { get; set; }
public SqlParameterizer(SqlProvider sqlProvider)
{
this.Provider = sqlProvider;
this.ParameterList = new List<IDbDataParameter>();
}
public void Add(string parameterName, object parameterValue)
{
switch(this.Provider)
{
case SqlProvider.MsSql:
this.ParameterList.Add(new SqlParameter(parameterName, parameterValue));
break;
case SqlProvider.MySql:
this.ParameterList.Add(new MySqlParameter(parameterName, parameterValue));
break;
case SqlProvider.OracleSql:
throw new Exception($"SqlProvider '{this.Provider}' not supported yet...");
default:
throw new Exception($"Unknown SqlProvider '{this.Provider}'");
}
}
public IDbDataParameter[] GetParameters()
{
return ParameterList.ToArray();
}
}
Using this class will look like this:
var commandText = $"insert into {tableName} (#columns) values (#values)";
var valueBuilder = new StringBuilder();
var columnBuilder = new StringBuilder();
var parameterizer = new SqlParameterizer(this.Provider);
actionValueParameterizer?.Invoke(parameterizer);
foreach(IDbDataParameter parameter in parameterizer.GetParameters())
{
command.Parameters.Add(parameter);
valueBuilder.Append($",#{parameter.ParameterName}");
columnBuilder.Append($",{parameter.ParameterName}");
}
commandText = commandText.Replace("#values", valueBuilder.ToString().Substring(1));
commandText = commandText.Replace("#columns", columnBuilder.ToString().Substring(1));
command.CommandText = commandText;
command.ExecuteNonQuery();
Calling my insert-function:
context.Insert("some_table", parameterizer =>
{
parameterizer.Add("some_column", "some_value");
});

Is this the right way to query a SQL Server CE table for a record, populating and returning a custom object?

The code below works, but I'm wondering if it's more loquacious than necessary:
public static InventoryItem SelectLocalInventoryItem(string ID)
{
const int ID_COL = 0;
const int PACKSIZE_COL = 1;
const int DESCRIPTION_COL = 2;
const int DEPTDOTSUBDEPT_COL = 3;
const int UNITCOST_COL = 4;
const int UNITLIST_COL = 5;
const int UPCCODE_COL = 6;
const int UPCPACKSIZE_COL = 7;
const int CRVID_COL = 8;
var invItem = new InventoryItem();
using (var conn = new SqlCeConnection(dataSource))
{
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM InventoryItems WHERE Id = #IDVal";
var IDParam = cmd.CreateParameter();
IDParam.ParameterName = "#IdVal";
IDParam.Value = ID;
cmd.Parameters.Add(IDParam);
conn.Open();
cmd.Prepare();
using (var reader = cmd.ExecuteReader())
{
if (reader.Read())
{
invItem.Id = reader.GetString(ID_COL);
invItem.PackSize = reader.GetInt16(PACKSIZE_COL);
invItem.Description = reader.GetString(DESCRIPTION_COL);
invItem.DeptDotSubdept = reader.GetDouble(DEPTDOTSUBDEPT_COL);
invItem.Unit_Cost = reader.GetDouble(UNITCOST_COL);
invItem.Unit_List = reader.GetDouble(UNITLIST_COL);
invItem.UPC_code = reader.GetString(UPCCODE_COL);
invItem.UPC_pack_size = reader.GetInt16(UPCPACKSIZE_COL);
invItem.CRV_Id = reader.GetInt32(CRVID_COL);
}
}
conn.Close();
cmd.Dispose();
return invItem;
}
}
The table being queried is created like so:
using (var connection = new SqlCeConnection(dataSource))
{
connection.Open();
using (var command = new SqlCeCommand())
{
command.Connection = connection;
if (TableExists(connection, "InventoryItems"))
{
command.CommandText = "DROP TABLE InventoryItems";
command.ExecuteNonQuery();
}
command.CommandText = "CREATE TABLE InventoryItems (Id nvarchar(50) NOT
NULL, PackSize smallint NOT NULL, Description nvarchar(255),
DeptDotSubdept float, UnitCost float, UnitList float, UPCCode
nvarchar(50), UPCPackSize smallint, CRVId int);";
command.ExecuteNonQuery();
. . .
}
}
The class is declared thusly:
public class InventoryItem
{
public string Id { get; set; }
public int PackSize { get; set; }
public string Description { get; set; }
public double DeptDotSubdept { get; set; }
public double Unit_Cost { get; set; }
public double Unit_List { get; set; }
public string UPC_code { get; set; }
public int UPC_pack_size { get; set; }
public int CRV_Id { get; set; }
}
Is there an easier/quicker way to accomplish this, or do I really have to painstakingly manually assign each returned column to each class member?
UPDATE
I implemented Sergey K's suggestions, and here it is now:
public static InventoryItem SelectLocalInventoryItem(string ID)
{
InventoryItem invItem = null;
using (var conn = new SqlCeConnection(dataSource))
{
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM InventoryItems WHERE Id = #IDVal";
var IDParam = cmd.CreateParameter();
IDParam.ParameterName = "#IdVal";
IDParam.Value = ID;
cmd.Parameters.Add(IDParam);
conn.Open();
cmd.Prepare();
using (var reader = cmd.ExecuteReader())
{
if (reader.Read())
{
invItem = new InventoryItem
{
Id = Convert.ToString(reader["Id"]),
PackSize = Convert.ToInt16(reader["PackSize"]),
Description = Convert.ToString(reader["Description"]),
DeptDotSubdept = Convert.ToDouble(reader["DeptDotSubdept"]),
Unit_Cost = Convert.ToDouble(reader["UnitCost"]),
Unit_List = Convert.ToDouble(reader["UnitList"]),
UPC_code = Convert.ToString(reader["UPCCode"]),
UPC_pack_size = Convert.ToInt16(reader["UPCPackSize"]),
CRV_Id = Convert.ToInt32(reader["CRVId"])
};
}
}
return invItem;
}
}
UPDATE 2
For the record/posterity, here is a related method that returns all the values, rather than a single "record"/class instance:
public static List<InventoryItem> SelectLocalInventoryItems()
{
List<InventoryItem> invItems = new List<InventoryItem>();
using (var conn = new SqlCeConnection(dataSource))
{
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM InventoryItems";
conn.Open();
cmd.Prepare();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var invItem = new InventoryItem
{
Id = Convert.ToString(reader["Id"]),
PackSize = Convert.ToInt16(reader["PackSize"]),
Description = Convert.ToString(reader["Description"]),
DeptDotSubdept = Convert.ToDouble(reader["DeptDotSubdept"]),
Unit_Cost = Convert.ToDouble(reader["UnitCost"]),
Unit_List = Convert.ToDouble(reader["UnitList"]),
UPC_code = Convert.ToString(reader["UPCCode"]),
UPC_pack_size = Convert.ToInt16(reader["UPCPackSize"]),
CRV_Id = Convert.ToInt32(reader["CRVId"])
};
invItems.Add(invItem);
}
}
}
return invItems;
}
UPDATE 3
This is an update of update 2, following ctacke's suggestion:
public static List<HHSUtils.InventoryItem> SelectLocalInventoryItemsTableDirect()
{
var invItems = new List<HHSUtils.InventoryItem>();
using (var conn = new SqlCeConnection(dataSource))
{
conn.Open();
SqlCeCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.TableDirect;
cmd.CommandText = "InventoryItems";
using (SqlCeResultSet rs cmd.ExecuteResultSet(ResultSetOptions.Scrollable))
{
cmd.Prepare();
while (rs.Read())
{
var invItem = new HHSUtils.InventoryItem
{
Id = Convert.ToString(rs["Id"]),
PackSize = Convert.ToInt16(rs["PackSize"]),
Description = Convert.ToString(rs["Description"]),
DeptDotSubdept = Convert.ToDouble(rs["DeptDotSubdept"]),
Unit_Cost = Convert.ToDouble(rs["UnitCost"]),
Unit_List = Convert.ToDouble(rs["UnitList"]),
UPC_code = Convert.ToString(rs["UPCCode"]),
UPC_pack_size = Convert.ToInt16(rs["UPCPackSize"]),
CRV_Id = Convert.ToInt32(rs["CRVId"])
};
invItems.Add(invItem);
}
}
}
return invItems;
}
I don't know yet if ResultSetOptions.Scrollable is the best property to use here, though... This msdn article makes me only slightly wiser.
UPDATE 4
The TableDirect change seems to be good; so I tried to implement the GetValues suggestion, too. But changing this code:
using (SqlCeResultSet rs = cmd.ExecuteResultSet(ResultSetOptions.Scrollable))
{
cmd.Prepare();
while (rs.GetValues())
{
var invItem = new HHSUtils.InventoryItem
{
Id = Convert.ToString(rs["Id"]),
PackSize = Convert.ToInt16(rs["PackSize"]),
Description = Convert.ToString(rs["Description"]),
DeptDotSubdept = Convert.ToDouble(rs["DeptDotSubdept"]),
Unit_Cost = Convert.ToDouble(rs["UnitCost"]),
Unit_List = Convert.ToDouble(rs["UnitList"]),
UPC_code = Convert.ToString(rs["UPCCode"]),
UPC_pack_size = Convert.ToInt16(rs["UPCPackSize"]),
CRV_Id = Convert.ToInt32(rs["CRVId"])
};
invItems.Add(invItem);
}
}
}
...to this:
using (SqlCeResultSet rs = cmd.ExecuteResultSet(ResultSetOptions.Scrollable))
{
cmd.Prepare();
Object[] values = new Object[rs.FieldCount];
int fieldCount = rs.GetValues(values);
for (int i = 0; i < fieldCount; i++)
{
var invItem = new HHSUtils.InventoryItem
{
Id = Convert.ToString(rs["Id"]),
PackSize = Convert.ToInt16(rs["PackSize"]),
Description = Convert.ToString(rs["Description"]),
DeptDotSubdept = Convert.ToDouble(rs["DeptDotSubdept"]),
Unit_Cost = Convert.ToDouble(rs["UnitCost"]),
Unit_List = Convert.ToDouble(rs["UnitList"]),
UPC_code = Convert.ToString(rs["UPCCode"]),
UPC_pack_size = Convert.ToInt16(rs["UPCPackSize"]),
CRV_Id = Convert.ToInt32(rs["CRVId"])
};
invItems.Add(invItem);
}
}
}
...fails on the "int fieldCount = rs.GetValues(values);" line, with "No data exists for the row/column"
UPDATE 5
In response to ctacke: So it's simply a matter of adding "Object[] values = new Object[rs.FieldCount];" before the while and "rs.GetValues(values);" after it, like so:
using (SqlCeResultSet rs = cmd.ExecuteResultSet(ResultSetOptions.Scrollable))
{
cmd.Prepare();
Object[] values = new Object[rs.FieldCount];
while (rs.Read())
{
rs.GetValues(values);
var invItem = new HHSUtils.InventoryItem
{
. . .
? It seems to work...
UPDATE 6
For posterity, this seems to be good form and work well for a "Select *" on a SQL Server CE table:
public static List<HHSUtils.Department> SelectLocalDepartments()
{
var departments = new List<HHSUtils.Department>();
using (var conn = new SqlCeConnection(dataSource))
{
conn.Open();
SqlCeCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.TableDirect;
cmd.CommandText = "Departments";
using (SqlCeResultSet rs = cmd.ExecuteResultSet(ResultSetOptions.None))
{
var values = new Object[rs.FieldCount];
while(rs.Read())
{
rs.GetValues(values);
var dept = new HHSUtils.Department
{
Id = Convert.ToInt16(rs["Id"]),
DeptNumber = Convert.ToInt16(rs["DeptNum"]),
DeptName = Convert.ToString(rs["DepartmentName"]),
};
departments.Add(dept);
}
}
}
return departments;
}
I see few things you can do better.
Why not to use column name to get a value from reader? Something like
Convert.ToDouble(reader["DeptDotSubdept"]);
I don't see any sense to have constants to identify the column number in scope of your method.
You can use object initializer to instantiate your object.
if (reader.Read())
{
invItem = new InventoryItem{
Id = Convert.ToString(reader["Id"]),
.....
};
}
Return null reference if record is not found.
If you know what is using do, you might don't want to add this lines
conn.Close();
cmd.Dispose();
It's worth noting that if you're doing this across a lot of rows, this can be improved for speed greatly. There are two improvement paths:
Modest improvement can be made by first caching the numeric field ordinals before iterating the rows, using reader.GetValues to pull the entire data row, then accessing the resulting array with the cached ordinals.
The reason this is better is twofold. First, it skips the need for the reader to always look up the ordinal based on the name you provided, and two it doesn't require a roundtrip to the data for each field you want.
An order of magnitude improvement can be had by just opening the table using TableDirect instead of a SQL query and then doing the suggestions in #1.
EDIT
Something along these lines:
using (var rs = cmd.ExecuteResultSet())
{
var fieldCount = rs.fieldCount;
var values = new Object[rs.FieldCount];
// cache your ordinals here using rs.GetOrdinal(fieldname)
var ID_ORDINAL = rs.GetOrdinal("Id");
// etc
while(rs.Read())
{
rs.GetValues(values);
var invItem = new HHSUtils.InventoryItem
{
Id = (string)values[ID_ORDINAL],
PackSize = (short)values[PACK_SIZE_ORDINAL],
// etc
};
invItems.Add(invItem);
}
}
EDIT 2
It's probably worth noting that if you were using something like the OpenNETCF ORM, the code to do the above would look like this:
invItems = store.Select<InventoryItem>();
That's it, just one line. And it would have used TableDirect by default.

Calling Oracle function that returns type from C#

I am using c# to try and call a function in an Oracle package that returns a Type.
I've spent the last couple of days researching this, so far the advice i've come across is:
to use the odp.net 11g Data Access driver.
ensure the parameter output direction is set to returnvalue
ensure the parameter output is first parameter added
Give the output parameter a udttypename which is the Oracle type
name.
To make sure this udttypename is Upper Case (A few similar cases asked on her were resolved by this)
Below is the Oracle package (package is called prefs):
Type P_Details Is Record(
var1 a.a_Type_Key%Type
,var2 Varchar2(1)
,var3 a.b%Type
,var4 c.Type_Key%Type
,var5 d.Code%Type
,var6 d.Product_Path%Type
,var7 a.Channel_Key%Type
,var8 a.From_Date%Type
,var9 a.To_Date%Type);
Type P_List Is Table Details;
Function Get(p_1 In Number,
p_2 In Varchar2,
p_3 In Varchar2,
p_4 In Date,
p_5 In Out Varchar2) Return List;
Below is the C# code used for calling the Oracle package.
using (var connection = new OracleConnection(ConnectionString))
{
using (var command = new OracleCommand
{
CommandType = CommandType.StoredProcedure,
CommandText = "PACKAGENAME.FUNCTIONNAME",
Connection = connection,
BindByName = true
})
{
var output = new OracleParameter
{
UdtTypeName = "PREFS.PREFERENCE_LIST",
ParameterName = "p_details",
OracleDbType = OracleDbType.Object,
Direction = ParameterDirection.ReturnValue
};
command.Parameters.Add(output);
command.Parameters.Add(new OracleParameter
{
ParameterName = "p_1",
OracleDbType = OracleDbType.Decimal,
Direction = ParameterDirection.Input,
Value = details.RuleId
});
command.Parameters.Add(new OracleParameter
{
ParameterName = "p_2",
OracleDbType = OracleDbType.Decimal,
Direction = ParameterDirection.Input,
Value = details.CustomerDetails.CtiId
});
command.Parameters.Add(new OracleParameter
{
ParameterName = "p_3",
OracleDbType = OracleDbType.Varchar2,
Direction = ParameterDirection.Input,
Value = details.CustomerDetails.Surname
});
command.Parameters.Add(new OracleParameter
{
ParameterName = "p_4",
OracleDbType = OracleDbType.Varchar2,
Direction = ParameterDirection.Input,
Value = details.CustomerDetails.Postcode
});
command.Parameters.Add(new OracleParameter
{
ParameterName = "p_5",
OracleDbType = OracleDbType.Date,
Direction = ParameterDirection.Input,
Value = details.CustomerDetails.DateOfBirth
});
command.Parameters.Add(new OracleParameter
{
ParameterName = "p_6",
OracleDbType = OracleDbType.Varchar2,
Direction = ParameterDirection.InputOutput
});
connection.Open();
command.ExecuteNonQuery();
}
}
I'm now getting an error
"OCI-22303: type "PACKAGENAME.TYPENAME" not found"
For the UDTTYPENAME i've tried the following formats
TYPENAME
FUNCTIONNAME.TYPENAME
PACKAGENAME.TYPENAME
PACKAGENAME.TYPENAME
PACKAGENAME.FUNCTIONNAME.TYPENAME
SCHEMA.PACKAGENAME.TYPENAME
I'd appreciate any help and response for this as i've now run out of ideas.
You can actually simplify the casting and preparing parameters for all your Routines(Procedures,functions etc.) and also parameters of ref cursor type with this little automation
a) define a following type and routine in a common package (lets call it utils).
Type recRoutineSchema is Record (ColumnName varchar2(64),DataType Varchar2(20), ColumnOrder number, Direction varchar2(10), sSize nUMBER);
Type tblRoutineSchema is table of recRoutineSchema;
function ftRoutineSchema(pkg varchar2,Routine varchar2) return tblRoutineSchema PIPELINED is
x recRoutineSchema;
pkN varchar2(100);
rtN varchar2(100);
Begin
FOR Y in ( Select Argument_Name ColumnName
,Data_type DataType
,Position ColumnOrder
,In_out Direction
,Data_length SSize
from user_ARGUMENTS
where package_Name=Upper(pkg)
and object_name=Upper(Routine) order by position
)
LOOP
PIPE ROW(Y);
END LOOP;
Return;
End;
b) and a c# method to call above function to retrieve and setup parameters of the procedure/function you are calling
public void SetupParams(string RoutineName, OracleCommand cmd, IDictionary<string, string> prms, bool keepConnectionOpen = true)
{
Debug.WriteLine("Setting parameters for " + RoutineName);
if (cmd != null) cmd.Parameters.Clear();
string pname = "";
string[] s = RoutineName.Split('.');
DataTable tblParams = Select(String.Format("Select * from Table(pkgUtils.ftRoutineSchema('{0}','{1}')) ", s[0], s[1]));
cmd.CommandText=RoutineName;
foreach (DataRow dr in tblParams.Rows)
{
using (OracleParameter p = new OracleParameter())
{
pname = dr["COLUMnNAME"].ToString() == "" ? "returnvalue" : pname = dr["COLUMnNAME"].ToString().ToLower();
if (prms.Keys.Contains(pname)) p.Value = prms[pname];
string direction = dr["Direction"].ToString().ToLower();
string sptype = (string)dr["DataType"];
string[] sx = dr["DataType"].ToString().Split(new char[] { '(', ',', ')' });
direction = pname == "returnvalue" ? "rc" : direction;
p.ParameterName = pname;
#region case type switch
switch (sx[0].ToLower())
{
case "number":
// p.DbType = OracleDbType.Decimal;
p.OracleDbType = OracleDbType.Decimal;
break;
case "varchar2":
p.DbType = DbType.String;
p.Size = 65536;
// p.Size = prms[pname].Length;
// p.Size = int.Parse(sx[1]);
break;
case "ref cursor":
p.OracleDbType = OracleDbType.RefCursor;
// direction = "rc"; // force return value
break;
case "datetime":
p.DbType = DbType.DateTime;
break;
case "ntext":
case "text":
p.DbType = DbType.String;
p.Size = 65536;
break;
default:
break;
}
//-------------------------------------------------------------------------------
switch (direction)
{
case "in": p.Direction = ParameterDirection.Input; break;
case "out": p.Direction = ParameterDirection.Output; break;
case "in/out": p.Direction = ParameterDirection.InputOutput; break;
case "rc": p.Direction = ParameterDirection.ReturnValue; break;
default: break;
}
#endregion
cmd.Parameters.Add(p); ;
}
}
}
c). now you can easily call any function/proc as follows this procedure actually returns two out refcursor parameters to poplulate a dataset.
private void btnDumpExcel_Click(object sender, EventArgs e)
{
IDictionary<string, string> p = new Dictionary<string, string>();
p.Add("pcomno", "020");
p.Add("pcpls", "221");
p.Add("pUploaderName", "Anthony Peiris");
try
{
pGroupDs = O.execProc2DatSet("priceWorx.prSnapshotDiscounts", p, false, false);
Excel.MakeWorkBook(ref pGroupDs, ref O, "1");
}
catch (Exception ex)
{
Debug.WriteLine(ex);
Debugger.Break();
}
//Excel.MakeWorkBook(ref ds, ref O, "1");
}
Here is the method O.execProc2DataSet
public DataSet execProc2DatSet(string storedProcedureName, IDictionary<string, string> prms, bool propagateDbInfo, bool leaveConnectionOpen = false)
{
// initPackage(storedProcedureName.Substring(0,storedProcedureName.IndexOf('.')));
try
{
using (OracleCommand cmd = new OracleCommand("", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = storedProcedureName;
//dep = new OracleDependency(cmd);
//dep.OnChange += new OnChangeEventHandler(dep_OnChange);
if (prms != null) SetupParams(storedProcedureName, cmd, prms, true);
using (OracleDataAdapter da = new OracleDataAdapter(cmd))
{
if (conn.State != ConnectionState.Open)
{
conn.Open();
cmd.Connection = conn;
}
using (DataSet ds = new DataSet())
{
da.Fill(ds);
return ds;
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
Debugger.Break();
return null;
}
finally
{
if (!leaveConnectionOpen) conn.Close();
}
}
This approach allows you to change your Proce/function parameters without being concerend about what parameters may have changed since last, since the parameter setup is now fully automatic.
HTH

Categories