Update SQL comand doesn't change the data - c#

I know may be it is stupid question. But This query doesn't work. I search solution more than 1 hour.
Please Help
public static bool ChangeEventStatus(Connector cn, EventData eventData)
{
int updatedRows = 0;
using (OleDbCommand cmd = cn.CreateCommand())
{
cmd.CommandText = "Update EventList Set IsProcessed = ? Where EventId = ?";
cmd.Parameters.Add("IsProcessed", OleDbType.Boolean).Value = true;
cmd.Parameters.Add("EventId", OleDbType.BigInt).Value = eventData.EventId;
updatedRows = cmd.ExecuteNonQuery();
}
return (updatedRows == 1);
}
What is wrong in my code. ChangeEventStatus method return true, but database records doesn't change.

public static bool ChangeEventStatus(Connector cn, EventData eventData)
{
int updatedRows = 0;
using (OleDbConnection conn = new OleDbConnection(someConnectionString));
{
conn.Open();
using (OleDbCommand cmd = cn.CreateCommand())
{
cmd.CommandText = "Update EventList Set IsProcessed = ? Where EventId = ?";
cmd.Parameters.Add("#IsProcessed", OleDbType.Boolean).Value = true;
cmd.Parameters.Add("#EventId", OleDbType.BigInt).Value = eventData.EventId;
updatedRows = cmd.ExecuteNonQuery();
}
return (updatedRows == 1);
}
}

I found problem. thanks everyone. I forgot commit transaction after updateting

I changed the code a bit and wrote the test. All work fine.
Check:
Connection string
Is you pass correct EventId to function ?
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
Assert.AreEqual(true, ChangeEventStatus(new EventData() {EventId = 3}));
}
private static bool ChangeEventStatus(EventData eventData)
{
int updatedRows = 0;
using (var cn = new OleDbConnection("Provider=sqloledb;Data Source=localhost;Initial Catalog=FastExperiments;User Id = sa; Password = pass; "))
{
using (OleDbCommand cmd = cn.CreateCommand())
{
cn.Open();
cmd.CommandText = "Update EventList Set IsProcessed = ? Where EventId = ?";
cmd.Parameters.Add("IsProcessed", OleDbType.Boolean).Value = true;
cmd.Parameters.Add("EventId", OleDbType.BigInt).Value = eventData.EventId;
updatedRows = cmd.ExecuteNonQuery();
}
}
return (updatedRows == 1);
}
private class EventData
{
public int EventId { get; set; }
}
}

Related

ExecuteNonQuery Violation of Primary Key after putting value?

I'm trying to write a CRUD app and I have a problem with the Create method. Program is crashing with error
System.Data.SqlClient.SqlException: 'Violation of PRIMARY KEY constraint 'PK_Pracownicy'. Cannot insert duplicate key in object 'dbo.Pracownicy'. The duplicate key value is (11).
The statement has been terminated.'
But I can see in my SQL Server that this position is added to Pracownicy table, so I don't know where is a problem. Its look like the error is generated after I put new values to table
class SqlHelper
{
public int RowsLenght { get; set; }
private SqlConnection sqlConnection;
public string Command { get; set; }
public SqlHelper(string connectionString)
{
sqlConnection = new SqlConnection(connectionString);
sqlConnection.Open();
}
public int Create(string command, SqlParameter []parameters)
{
//wykonanie polecenia na bazie
using var cmd = new SqlCommand(command, sqlConnection);
cmd.Parameters.AddRange(parameters);
ShowTable(cmd);
return cmd.ExecuteNonQuery();
}
public int Read(string command)
{
//wykonanie polecenia na bazie
using var cmd = new SqlCommand(command, sqlConnection);
ShowTable(cmd);
return cmd.ExecuteNonQuery();
}
private int ShowTable(SqlCommand command)
{
var reader = command.ExecuteReader();
while (reader.Read())
{
Console.WriteLine(reader.GetInt32(0) + "\t" + reader.GetString(2) + " " +
reader.GetString(1) + "\t" + reader.GetString(3));
RowsLenght++;
}
reader.Close();
return RowsLenght;
}
}
class Program
{
static void Main()
{
SqlConnectionStringBuilder connectionString = new SqlConnectionStringBuilder
{
DataSource = #"HAL\MSSERVER",
InitialCatalog = "ZNorthwind",
IntegratedSecurity = true,
ConnectTimeout = 30,
Encrypt = false,
TrustServerCertificate = false,
ApplicationIntent = 0,
MultiSubnetFailover = false
};
var connectionS = connectionString.ConnectionString;
SqlHelper sql = new SqlHelper(connectionS);
var command = "SELECT * FROM dbo.Pracownicy";
sql.Read(command);
command = "INSERT INTO dbo.Pracownicy (IDpracownika, Nazwisko, ImiÄ™, Stanowisko) VALUES (#IDpracownika, #Nazwisko, #Imie, #Stanowisko)";
var parameters = SetUpParameters(sql.RowsLenght);
sql.Create(command, parameters);
}
private static SqlParameter[] SetUpParameters(int lenght)
{
//inicjalizacja zmiennych:
Console.WriteLine("Podaj imie nowego pracownika: ");
var fname = Console.ReadLine();
Console.WriteLine("Podaj nazwisko pracownika: ");
var lname = Console.ReadLine();
Console.WriteLine("Podaj stanowisko pracownika: ");
var position = Console.ReadLine();
SqlParameter []param = new SqlParameter[4];
param[0] = new SqlParameter("IDpracownika", lenght + 1);
param[1] = new SqlParameter("Imie", fname);
param[2] = new SqlParameter("Nazwisko", lname);
param[3] = new SqlParameter("Stanowisko", position);
return param;
}
}
Thanks

C# return multiple columns and rows from SqlDataReader

I have a scenario to query DB for two columns and return all the rows to a different method for further processing. I am not sure if my approach below is the best. Can you please share any better techniques to accomplish this.
The below code works well for a single row returned from SQL.
public (Int32 SiteID, string SiteName) QueryDB(string ConnStr)
{
Int32 SiteID = 0;
string SiteName = "";
using (SqlConnection con = new SqlConnection(ConnStr))
{
SqlCommand cmd = new SqlCommand("spGetSiteDetails", con);
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
SiteID = Convert.ToInt32(reader[0]);
SiteName = reader[1].ToString();
}
}
return (SiteID, SiteName);
}
In order to address multiple rows result, I am using string concatenated in a List. I believe there must be a better way of doing this because I have an overhead of having to split the string in the list to use the values..
public List<string> QueryDB(string ConnStr)
{
List<string> SiteDetails = new List<string>();
using (SqlConnection con = new SqlConnection(ConnStr))
{
SqlCommand cmd = new SqlCommand("spGetSiteDetails", con);
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
SiteDetails.Add(reader[0] + "|" + reader[1]);
}
}
return SiteDetails;
}
You can return a List<(int SiteID, string SiteName)>:
var list = new List<(int, string)>();
while (reader.Read())
{
var siteID = Convert.ToInt32(reader[0]);
var siteName = reader[1].ToString();
list.Add((siteID, siteName));
}
return list;
However, personally I'd recommend not returning value-tuples in public APIs, and returning your own custom type instead - i.e. a List<SiteInfo> for a class SiteInfo or readonly struct SiteInfo that has an int SiteID and string SiteName as properties; i.e.
public sealed class SiteInfo
{
public int SiteID {get;set;}
public string SiteID {get;set;}
}
with
var list = new List<SiteInfo>();
while (reader.Read())
{
var siteID = Convert.ToInt32(reader[0]);
var siteName = reader[1].ToString();
list.Add(new SiteInfo { SiteID = siteID, SiteName = siteName });
}
return list;
Either way: Dapper could really help you with this!
public List<(int SiteID, string SiteName)> QueryDB(string ConnStr)
{
using (SqlConnection con = new SqlConnection(ConnStr))
{
return con.Query<(int,string)>("spGetSiteDetails",
commandType: CommandType.StoredProcedure).AsList();
}
or
public List<SiteInfo> QueryDB(string ConnStr)
{
using (SqlConnection con = new SqlConnection(ConnStr))
{
return con.Query<SiteInfo>("spGetSiteDetails",
commandType: CommandType.StoredProcedure).AsList();
}
To return a collection you can write:
public IEnumerable<(Int32 SiteID, string SiteName)> QueryDB(string ConnStr)
{
using (SqlConnection con = new SqlConnection(ConnStr))
{
SqlCommand cmd = new SqlCommand("spGetSiteDetails", con);
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
yield return (Convert.ToInt32(reader[0]), reader[1].ToString());
}
}
}
I found the DataTable which solves the problem straight away
dataTable.Load(reader);

C# update statement not updating when update button clicked

i am creating c# project so far insert and delete buttons are working but when i hit update button it gives data has not been updated and i cant see what is wrong with my code please help
public bool Update(Classre c)
{
bool isSuccess = false;
SqlConnection conn = new SqlConnection(myconnstring);
try
{
string sql = "UPDATE Class SET ClassName=#ClassName,ClassLevel=#ClassLevel WHERE ClassID=#ClassID";
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.Parameters.AddWithValue("#ClassName", c.ClassName);
cmd.Parameters.AddWithValue("#ClassLevel", c.ClassLevel);
conn.Open();
int rows = cmd.ExecuteNonQuery();
if (rows > 0)
{
isSuccess = true;
}
else
{
isSuccess = false;
}
}
catch (Exception ex)
{
}
finally
{
conn.Close();
}
return isSuccess;
}
and this is my update button code where i call the class that holds my update code
private void button3_Click(object sender, EventArgs e)
{
c.ClassID = int.Parse(textBox1.Text);
c.ClassName = textBox2.Text;
c.ClassLevel = comboBox1.Text;
bool success = c.Update(c);
if (success == true)
{
// label4.Text = "Data Has been updated";
MessageBox.Show("Data Has been updated");
DataTable dt = c.Select();
dataGridView1.DataSource = dt;
}
else
{
//label4.Text = "Data Has not been updated";
MessageBox.Show("Data Has not been updated");
}
}
I would prefer to use a stored procedure instead of pass through sql but you could greatly simplify this. As stated above your try/catch is worse than not having one because it squelches the error.
public bool Update(Classre c)
{
USING(SqlConnection conn = new SqlConnection(myconnstring))
{
string sql = "UPDATE Class SET ClassName = #ClassName, ClassLevel = #ClassLevel WHERE ClassID = #ClassID";
USING(SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.Add("#ClassName", SqlDbType.VarChar, 4000).Value = c.ClassName;
cmd.Parameters.Add("#ClassLevel", SqlDbType.Int).Value = c.ClassLevel;
cmd.Parameters.Add("#ClassID", SqlDbType.Int).Value = c.ClassID;
conn.Open();
int rows = cmd.ExecuteNonQuery();
return rows > 0;
}
}
}

checking user name or user email already exists

I am working in a simple registration page where the user can't enter the same user name or email, I made a code that prevent the user from entering the username and it worked but when I tried to prevent the user from entring the same username or email it didn't work.
and my question is, "How can I add another condition where the user can't enter email that already exists?"
I tried to do it in this code, but it did't work:
protected void Button_Click(object sender, EventArgs e)
{
SqlConnection con = new SqlConnection( ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString );
SqlCommand cmd1 = new SqlCommand("select 1 from Table where Name =#UserName", con);
SqlCommand cmd2 = new SqlCommand("select 1 from Table where Email=#UserEmail", con);
con.Open();
cmd1.Parameters.AddWithValue("#UserName", Name_id.Text);
cmd2.Parameters.AddWithValue("#UserEmail", Email_id.Text);
using (var dr1 = cmd1.ExecuteReader())
{
if (dr1.HasRows)
{
Label1.Text = "user name already exists";
}
using (var dr2 = cmd2.ExecuteReader())
{
if (dr2.HasRows)
{
Label1.Text = "email already exists";
}
else
{
dr1.Close();
dr2.Close();
//add new users
con.Close();
}
}
}
}
but i get this error:
There is already an open DataReader associated with this Command which must be closed first.
Like I said in my comment your design is bad !
First you should have Data Access Layer. This should be project in big solutions but in your case you can put it like new directory. In this directory you create SqlManager class here is the code:
public class SqlManager
{
public static string ConnectionString
{
get
{
return ConfigurationManager.ConnectionStrings["DevConnString"].ConnectionString;
}
}
public static SqlConnection GetSqlConnection(SqlCommand cmd)
{
if (cmd.Connection == null)
{
SqlConnection conn = new SqlConnection(ConnectionString);
conn.Open();
cmd.Connection = conn;
return conn;
}
return cmd.Connection;
}
public static int ExecuteNonQuery(SqlCommand cmd)
{
SqlConnection conn = GetSqlConnection(cmd);
try
{
return cmd.ExecuteNonQuery();
}
catch
{
throw;
}
finally
{
conn.Close();
}
}
public static object ExecuteScalar(SqlCommand cmd)
{
SqlConnection conn = GetSqlConnection(cmd);
try
{
return cmd.ExecuteScalar();
}
catch
{
throw;
}
finally
{
conn.Close();
}
}
public static DataSet GetDataSet(SqlCommand cmd)
{
return GetDataSet(cmd, "Table");
}
public static DataSet GetDataSet(SqlCommand cmd, string defaultTable)
{
SqlConnection conn = GetSqlConnection(cmd);
try
{
DataSet resultDst = new DataSet();
using (SqlDataAdapter adapter = new SqlDataAdapter(cmd))
{
adapter.Fill(resultDst, defaultTable);
}
return resultDst;
}
catch
{
throw;
}
finally
{
conn.Close();
}
}
public static DataRow GetDataRow(SqlCommand cmd)
{
return GetDataRow(cmd, "Table");
}
public static DataRow GetDataRow(SqlCommand cmd, string defaultTable)
{
SqlConnection conn = GetSqlConnection(cmd);
try
{
DataSet resultDst = new DataSet();
using (SqlDataAdapter adapter = new SqlDataAdapter(cmd))
{
adapter.Fill(resultDst, defaultTable);
}
if (resultDst.Tables.Count > 0 && resultDst.Tables[0].Rows.Count > 0)
{
return resultDst.Tables[0].Rows[0];
}
else
{
return null;
}
}
catch
{
throw;
}
finally
{
conn.Close();
}
}
}
After that you should have Business Object Layer. In bigger solution is project in your case directory. If you are in the page TaxesEdit.aspx, you should add Tax.cs class in the BO(business object).
Example of methods for the class, for your first button:
public DataSet GetTaxesByUserName(string userName)
{
SqlCommand cmd = new SqlCommand(#"
select 1 from Table where Name =#UserName");
cmd.Parameters.AddWithValue("#UserName", userName);
return DA.SqlManager.GetDataSet(cmd);
}
You fetch all the needed data in datasets. After that you make checks like taxesDst.Tables[0].Rows.Count > 0 (or == 0)
For Insert you can have method like this:
public virtual void Insert(params object[] colValues)
{
if (colValues == null || colValues.Length % 2 != 0)
throw new ArgumentException("Invalid column values passed in. Expects pairs (ColumnName, ColumnValue).");
SqlCommand cmd = new SqlCommand("INSERT INTO " + TableName + " ( {0} ) VALUES ( {1} )");
string insertCols = string.Empty;
string insertParams = string.Empty;
for (int i = 0; i < colValues.Length; i += 2)
{
string separator = ", ";
if (i == colValues.Length - 2)
separator = "";
string param = "#P" + i;
insertCols += colValues[i] + separator;
insertParams += param + separator;
cmd.Parameters.AddWithValue(param, colValues[i + 1]);
}
cmd.CommandText = string.Format(cmd.CommandText, insertCols, insertParams);
DA.SqlManager.ExecuteNonQuery(cmd);
}
For this you need to have property TableName in the current BO class.
In this case this methods can be used everywhere and you need only one line of code to invoke them and no problems like yours will happen.
You have opened another DataReader inside the First and thats causing the problem. Here I have re-arranged your code a bit
SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString);
SqlCommand cmd1 = new SqlCommand("select 1 from Table where Name =#UserName", con),
cmd2 = new SqlCommand("select 1 from Table where Email=#UserEmail", con);
con.Open();
cmd1.Parameters.AddWithValue("#UserName", Name_id.Text);
cmd2.Parameters.AddWithValue("#UserEmail", Email_id.Text);
bool userExists = false, mailExists = false;
using (var dr1 = cmd1.ExecuteReader())
if (userExists = dr1.HasRows) Label1.Text = "user name already exists";
using (var dr2 = cmd2.ExecuteReader())
if (mailExists = dr2.HasRows) Label1.Text = "email already exists";
if (!(userExists || mailExists)) {
// can add User
}
You need to close one datareader before opening the other one. Although it's not how I'd do it, but you can deal with the runtime error by closing the datareader after each IF:
using (var dr1 = cmd1.ExecuteReader())
{
if (dr1.HasRows)
{
string Text = "user name already exists";
}
dr1.Close();
}
using (var dr2 = cmd2.ExecuteReader())
{
if (dr2.HasRows)
{
string ext = "email already exists";
}
else
{
//add new users
}
dr2.Close();
}
con.Close();
This may work, although there are a few things I would do differently...
protected void Button_Click(object sender, EventArgs e)
{
bool inputIsValid = true;
var con = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString);
var userNameCmd = new SqlCommand("SELECT 1 FROM Table WHERE Name = #UserName", con);
var emailCmd = new SqlCommand("SELECT 1 FROM Table WHERE Email = #UserEmail", con);
con.Open();
userNameCmd.Parameters.AddWithValue("#UserName", Name_id.Text);
emailCmd.Parameters.AddWithValue("#UserEmail", Email_id.Text);
using (var userNameReader = userNameCmd.ExecuteReader())
{
if (userNameReader.HasRows)
{
inputIsValid = false;
Label1.Text = "User name already exists";
}
}
using (var emailReader = emailCmd.ExecuteReader())
{
if (emailReader.HasRows)
{
inputIsValid = false;
Label1.Text = "Email address already exists";
}
}
if (inputIsValid)
{
// Insert code goes here
}
con.Close();
}
Why don't you do something like this:
[Flags]
public enum ValidationStatus
{
Valid = 0 ,
UserNameInUse = 1 ,
EmailInUse = 2 ,
}
public ValidationStatus ValidateUser( string userName , string emailAddr )
{
ValidationStatus status ;
string connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString ;
using ( SqlConnection con = new SqlConnection( connectionString ) )
using ( SqlCommand cmd = con.CreateCommand() )
{
cmd.CommandText + #"
select status = coalesce( ( select 1 from dbo.myTable t where t.UserName = #UserName ) , 0 )
+ coalesce( ( select 2 from dbo.myTable t where t.UserEmail = #UserEmail ) , 0 )
" ;
cmd.Parameters.AddWithValue( "#UserName" , userName ) ;
cmd.Parameters.AddWithValue( "#emailAddr" , emailAddr ) ;
int value = (int) cmd.ExecuteScalar() ;
status = (ValidationStatus) value ;
}
return status ;
}
Aside from anything else, hitting the DB twice for something like this is silly. And this more clearly expresses intent.
Then you can use it in your button click handler, something like this:
protected void Button_Click( object sender , EventArgs e )
{
string userName = Name_id.Text ;
string emailAddr = Email_id.Text ;
ValidationStatus status = ValidateUser( userName , emailAddr ) ;
switch ( status )
{
case ValidationStatus.Valid :
Label1.Text = "" ;
break ;
case ValidationStatus.EmailInUse :
Label1.Text = "Email address in use" ;
break ;
case ValidationStatus.UserNameInUse :
Label1.Text = "User name in use" ;
break ;
case ValidationStatus.EmailInUse|ValidationStatus.UserNameInUse:
Label1.Text = "Both user name and email address in use." ;
break ;
default :
throw new InvalidOperationException() ;
}
if ( status == ValidationStatus.Valid )
{
CreateNewUser() ;
}
}

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.

Categories