Calling Stored Proc in EF 6 with output parameters? - c#

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

Related

Trying not to get duplicate code when more than one variable in table entity is DBNull

I have this code that gets all the Orders out of the database, but the variables ArriveTime and Paymethod are nullable in the database. If Paymethod returns DBNull in the database I want it to become iDeal and when ArriveTime is DBNull than I want it to be null. How do I do this without getting duplicate codes.
Right now I can't think of another way besides putting a lot of if-statements in the code...
Code right now (5) = arrivetime and (8) = paymethod:
public List<OrderDTO> GetAllOrders()
{
using (SqlConnection con = new SqlConnection(connectionString))
{
string query = "SELECT * FROM [dbo].[Order]";
using (SqlCommand cmd = new SqlCommand(query, con))
{
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
List<OrderDTO> orders = new List<OrderDTO>();
int orderIDindex = 1;
while (reader.Read())
{
if (reader.IsDBNull(5))
{
OrderDTO orderDTO = new OrderDTO
{
OrderID = Convert.ToInt32(reader["OrderID"]),
TotalPrice = Convert.ToInt32(reader["TotalPrice"]),
UserID = Convert.ToInt32(reader["UserID"]),
OrderDate = Convert.ToDateTime(reader["OrderDate"]),
ArriveTime = null,
To_Adress = reader["To_Adress"].ToString(),
ItemCount = Convert.ToInt32(reader["ItemCount"]),
Status = (OrderStatus.Orderstatus)Enum.Parse(typeof(OrderStatus.Orderstatus), reader["Status"].ToString()),
};
orders.Add(orderDTO);
}
else if (reader.IsDBNull(8))
{
OrderDTO orderDTO = new OrderDTO
{
OrderID = Convert.ToInt32(reader["OrderID"]),
TotalPrice = Convert.ToInt32(reader["TotalPrice"]),
UserID = Convert.ToInt32(reader["UserID"]),
OrderDate = Convert.ToDateTime(reader["OrderDate"]),
Paymethod = PayMethod.Paymethod.iDeal,
To_Adress = reader["To_Adress"].ToString(),
ItemCount = Convert.ToInt32(reader["ItemCount"]),
Status = (OrderStatus.Orderstatus)Enum.Parse(typeof(OrderStatus.Orderstatus), reader["Status"].ToString()),
};
orders.Add(orderDTO);
}
}
return orders;
}
}
}
Move the repeated code out of the conditions
//...omitted for brevity
while (reader.Read()) {
OrderDTO orderDTO = new OrderDTO {
OrderID = Convert.ToInt32(reader["OrderID"]),
TotalPrice = Convert.ToInt32(reader["TotalPrice"]),
UserID = Convert.ToInt32(reader["UserID"]),
OrderDate = Convert.ToDateTime(reader["OrderDate"]),
To_Adress = reader["To_Adress"].ToString(),
ItemCount = Convert.ToInt32(reader["ItemCount"]),
Status = (OrderStatus.Orderstatus)Enum.Parse(typeof(OrderStatus.Orderstatus), reader["Status"].ToString()),
};
if (reader.IsDBNull(5)) orderDTO.ArriveTime = null;
if (reader.IsDBNull(8)) orderDTO.Paymethod = PayMethod.Paymethod.iDeal,
orders.Add(orderDTO);
}
//...omitted for brevity
If the values are used when not DBNull then check for the condition and provide the appropriate value otherwise.
OrderDTO orderDTO = new OrderDTO {
//...omitted for brevity
ArriveTime = reader.IsDBNull(5) ? null : Convert.ToDateTime(reader[5]),
Paymethod = reader.IsDBNull(8) ? PayMethod.Paymethod.iDeal : //...parse the value,
//...omitted for brevity
};
A better alternative way to do it more easier is to use a ORM like dapper. You can reduce your 50+ LOC to less than 5.
With the help of dapper, all you need to do is
using (var connection = new SqlConnection(_sqlConnectionString))
{
var results = connection.Query<OrderDTO>(query).ToList();
if(results.Paymethod ==null){
results.Paymethod = PayMethod.Paymethod.iDeal
}
return results;
}
If ArriveTime is null it will be null. You don't have to re-assign it as null.

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

Send Int parameter from C# to Stored Procedure

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

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.

Giving data to a class property according to another classes property which I get from the database

I'm getting data from two tables from database.
Table 1 - Members :
public List<Member> ListMembers()
{
List<Member> Members = new List<Member>();
string SELECT = "SELECT * FROM Members ORDER BY Id";
using (sqlConnection = new SqlConnection(sqlConnectionString_WORK))
{
sqlConnection.Open();
using (SqlCommand sqlCommand = new SqlCommand(SELECT, sqlConnection))
{
var sqlReader = sqlCommand.ExecuteReader();
while (sqlReader.Read())
{
var member = new Member
{
Id = Convert.ToInt32(sqlReader["Id"]),
Name = sqlReader["Name"].ToString(),
Surname = sqlReader["Surname"].ToString(),
EntryDate = Convert.ToDateTime(sqlReader["EntryDate"])
};
if (member.EntryDate < DateTime.Today)
{
member.Status = Status.Unpaid.ToString();
}
else
{
member.Status = Status.Paid.ToString();
}
Members.Add(member);
}
}
}
return Members;
}
Table 2 - Payments :
public List<Payment> ListPayments(Payment entity)
{
List<Payment> Payments = new List<Payment>();
string SELECT = "SELECT * FROM Payments WHERE Id = #Id";
using (sqlConnection = new SqlConnection(sqlConnectionString_WORK))
{
sqlConnection.Open();
using (SqlCommand sqlCommand = new SqlCommand(SELECT, sqlConnection))
{
sqlCommand.Parameters.Add("#Id", SqlDbType.Int).Value = entity.Id;
var sqlReader = sqlCommand.ExecuteReader();
while (sqlReader.Read())
{
var payment = new Payment
{
Id = Convert.ToInt32(sqlReader["Id"]),
Amount = Convert.ToDecimal(sqlReader["Amount"]),
StartDay = Convert.ToDateTime(sqlReader["StartDay"]),
EndDay = Convert.ToDateTime(sqlReader["EndDay"])
};
Payments.Add(payment);
}
}
}
return Payments;
}
The Payment's ID is equal to a specific Member's ID. (If a members ID is 100, all his/her payments will have the same ID 100).
Now... in the ListMembers() I gave Statuses "paid" and "unpaid" according to their EntryDate, but I want to give them Statuses according to their LAST Payment. So, instead of:
if (member.EntryDate < DateTime.Today)
{
member.Status = Status.Unpaid.ToString();
}
it should be :
if (lastPayment < DateTime.Today) // lastPayment is just a word I'm using, It's not declared anywhere, I don't know how to get that value.
{
member.Status = Status.Unpaid.ToString();
}
I'm really lost here, can someone help me ? I don't know what to do.
Get member payments:
public List<Payment> GetMemberPaymnets(int memberId)
{
List<Payment> Payments = new List<Payment>();
string SELECT = "SELECT ID,AMOUNT,STARTDAY,ENDDAY FROM Payments WHERE Id = #Id order by endday desc";
using (sqlConnection = new SqlConnection(sqlConnectionString_WORK))
{
sqlConnection.Open();
using (SqlCommand sqlCommand = new SqlCommand(SELECT, sqlConnection))
{
sqlCommand.Parameters.Add("#Id", SqlDbType.Int).Value = entity.Id;
var sqlReader = sqlCommand.ExecuteReader();
while (sqlReader.Read())
{
var payment = new Payment
{
Id = Convert.ToInt32(sqlReader["Id"]),
Amount = Convert.ToDecimal(sqlReader["Amount"]),
StartDay = Convert.ToDateTime(sqlReader["StartDay"]),
EndDay = Convert.ToDateTime(sqlReader["EndDay"])
};
Payments.Add(payment);
}
}
}
return Payments;
}
instead of
if (member.EntryDate < DateTime.Today)
{
member.Status = Status.Unpaid.ToString();
}
you can use something like this
foreach(Member memeber in memeberList)
{
List<Payment> memberPayments=GetMemberPayments(member.Id);
//also you need to check that memberPayments.Count>0
if(memberPayments[0].EndDay<DateTime.Today)
{
member.Status = Status.Unpaid.ToString();
}
}
But if you not need make some operations with member payments you can make database method that will return memeber payment status status by member id.
You can try getting the latest payment date with the Member query. Instead of
string SELECT = "SELECT * FROM Members ORDER BY Id";
Try
string SELECT = "SELECT m.Id, m.Name,
m.Surname, m.EntryDate, MAX(p.EndDay) AS EndDay FROM Members m
JOIN Payments p ON p.Id = m.Id
ORDER BY m.Id";
and then replace this:
if (member.EntryDate < DateTime.Today)
{
member.Status = Status.Unpaid.ToString();
}
with this:
var endDate = Convert.ToDateTime(sqlReader["EndDay"])
if (endDate < DateTime.Today)
{
member.Status = Status.Unpaid.ToString();
}
This assumes that there will always be one payment for a member. If there might be a member with no payments, you'll have to do some null checking and a left join.

Categories