DataTable empty after .load(SqlDataReader) - c#

After I run DataTable.load(SqlDataReader), the DataTable seems to be empty, but the DataReader contains results (can be seen while debugging).
Can anyone help me with this one please? I can't seem to find any solution for this problem, nor can't I find a mistake in my algorithm..
I found some solutions for using a DataAdapter & Fill(), but I'm just curious about this 'problem'.
My code:
DataTable DeviceProperties = new DataTable();
try
{
string query = "SELECT PropertyID, PropertyName from DeviceProperties WHERE DeviceID = #DeviceID;";
using (SqlCommand cmdSelectDeviceProperties = new SqlCommand(query, connectionDBTest))
{
cmdSelectDeviceProperties.Parameters.Add("#DeviceID", SqlDbType.BigInt).Value = deviceID;
using (SqlDataReader rdrSelectDeviceProperties = cmdSelectDeviceProperties.ExecuteReader())
{
if (rdrSelectDeviceProperties.HasRows)
DeviceProperties.Load(rdrSelectDeviceProperties);
else
Console.WriteLine("No Device Properties found..");
}
}
}
catch (Exception ex)
{
Console.WriteLine("Error getDeviceProperties: " + ex);
}
return DeviceProperties;
FYI: the rdrSelectDeviceProperties.HasRows passes every time, so the reader certainly contains some value.. The correct results can be found in the rdrSelectDeviceProperties, during debugging.. But the DeviceProperties data table remains empty..
EXACT PROBLEM: the data table seemed empty while debugging, because the hover over showed: '{ }'.
The rest of the code, depending on the data table, responded as the data table was empty.
SOLUTION: When you press the magnifying glass and you get the representation of the data in the data table.
My mistake was in the rest of the code (comparing the data in the data table with string, without using '.ToString()').
Learn from my mistakes..

Not sure what's causing this issue but i would use SqlDataAdapter.Fill(dataTable) instead of DataTable.Load(sqlDatareader):
DataTable tblSelectDeviceProperties = new DataTable();
using (var daSelectDeviceProperties = new SqlDataAdapter(query, connectionDBTest))
{
// no need to open/close the connection with DataAdapter.Fill
daSelectDeviceProperties.Fill(tblSelectDeviceProperties);
}
if (tblSelectDeviceProperties.Rows.Count == 0)
Console.WriteLine("No Device Properties found..");

Related

Datagridview NullReferenceException Error when renewing in timer

I have an application where I need to get a certain number as a reference value to send to PLC. To do that I get all the rows from the database and list them on my datagridview (dgv) and with a timer I reload the datagridview with updated rows. To load the data I use the code below;
public void updatedgv()
{
try
{
using (SqlConnection con = new SqlConnection(Program.sqlcon))
{
string q = "SELECT CONCAT(RL.istekID,'-',RL.lineID) '#',M.makName 'Makine Adı',R.partiNo 'PartiNo',C.kimAd 'Kimyasal',RL.miktar 'Miktar',RL.durum 'Durum', RL.lineID 'Kuyruk No' FROM RECLN RL JOIN REC R ON RL.istekID=R.istekID JOIN MAK M ON R.makID=M.makID JOIN CHEM C ON C.kimID=RL.kimID WHERE RL.durum IN (1,2)";
using (SqlCommand com = new SqlCommand(q, con))
{
if (con.State == ConnectionState.Closed) { con.Open(); } else { con.Close(); con.Open(); }
using (SqlDataAdapter sda = new SqlDataAdapter(com))
{
try
{
dt = new DataTable();
sda.Fill(dt);
dgv.DataSource = dt;
}
catch (Exception ex) { this.Text = ex.Message; }
}
}
}
}
catch (Exception ex) { this.Text = ex.Message; }
}
But once in a while, it gives the error below and my application stops working.
System.NullReferenceException: 'Object reference not set to an instance of an object.'
I have tried everything to avoid the problem but unfortunately, I couldn't find what I need to do.
I believe I am doing something wrong with my code but I can't figure it out.
You should put more effort into your question, it wastes time of people who could help and makes it difficult to answer, when it's not clear.
I suspect there's couple of things fundamentally wrong with your design.
First of all, if you need only some data from the database, then don't pull all the table to be processed (aka taken one value from it), but rather put the filter into the SQL statement and get one scallar value (or reduced result set, whatever is appropriate).
To answer the question, to avoid the error you get (provided it's from the reading that cell you mentioned, which is actually quite probable), you simply need to ensure you're reading existing cell and existing value:
if (dgv.Rows.Count > 0) // check row exists, to work with a row
{
DataGridViewRow irow = dgv.Rows(0);
if (dgv.Columns.Count > 0) // check column exists, to work with a row
{
// Dim icol As DataGridViewColumn = dgv.Columns(0) ' <<< if column reference needed
DataGridViewCell icell = irow.Cells(0);
if (!IsDBNull(icell) && !IsNothing(icell)) // check cell is not empty
myResult = icell.Value.ToString();// <<< Mind datatypes and type conversions! Not shown here!
}
}
My guess is, that once in a while you get a dbnull value in that cell and your program crashes.

C# - DataTable Out of Memory exception in application to catch SQL Server "INSERT" events

I have been tasked with creating an application that monitors any "INSERT" events on a specific table. I was going to go about this using SqlDependency to create a notification link between the DB and the C# app, but it turns out I am not able to do this due to security issues.
Due to this, I have modeled my application as follows:
This is well and good, but as it turns out, the SQL table I am querying has a rather large size. The table has nearly 3.5 Million rows 55 columns. When loading into the C# DataTable object, I am getting an out of memory exception.
internal static DataTable ExecuteQuery(string query, Dictionary<string,string> parameters = null)
{
try
{
using (SqlConnection dbconn = new SqlConnection(SQLServer.Settings.ConnectionString))
using (SqlCommand cmd = new SqlCommand())
{
dbconn.Open(); // Open the connection
cmd.CommandText = query; // Set the query text
cmd.Connection = dbconn;
if (parameters != null)
{
foreach (var parameter in parameters) // Add filter parameters
cmd.Parameters.AddWithValue(parameter.Key, parameter.Value);
}
var dt = new DataTable();
using (SqlDataAdapter adpt = new SqlDataAdapter(cmd)){adpt.Fill(dt);} // MY ERROR OCCURS HERE!
dbconn.Close();
queryError = false;
return dt;
}
}
catch(Exception ex)
{
queryError = true;
EventLogger.WriteToLog("ExecuteQuery()", "Application", "Error: An error has occured while performing a database query.\r\nException: " + ex.Message);
return null;
}
}
When running the code above, I get the following error at the line for SqlDataAdapter.Fill(dt)
Exception of type 'System.OutOfMemoryException' was thrown.
Is there a way that I can either restructure my application OR prevent this incredibly high memory consumption from the DataTable class? SQL server seems capable enough to do a select * from the table but when I fill a DataTable with the same data, I use up over 6GB of RAM! Why is there so much overhead when using DataTable?
Here is a link to my flowchart.
I was able to resolve this issue by making use of the SqlDataReaderclass. This class lets you "stream" the sql result set row by row rather bringing back the entire result set all at once and loading that into memory.
So now in step 5 from the flow chart, I can query for only the very first row. Then in step 6, I can query again at a later date and iterate through the new result set one row at a time until I find the original row I started at. All the while, I am filling a DataTable with the new results. This accomplishes two things.
I don't need to load all the data from the query all at once into local memory.
I can immediately get the "inverse" DataSet. AKA... I can get the newly inserted rows that didn't exist the first time I checked.
Which is exactly what I was after. Here is just a portion of the code:
private static SqlDataReader reader;
private static SqlConnection dbconn = new SqlConnection(SQLServer.Settings.ConnectionString);
private void GetNextRows(int numRows)
{
if (dbconn.State != ConnectionState.Open)
OpenConnection();
// Iterate columns one by one for the specified limit.
int rowCnt = 0;
while (rowCnt < numRows)
{
while (reader.Read())
{
object[] row = new object[reader.FieldCount];
reader.GetValues(row);
resultsTable.LoadDataRow(row, LoadOption.PreserveChanges);
rowCnt++;
sessionRowPosition++;
break;
}
}
}
The whole class would be too large for me to post here but one of the caveats was that the interval between checks for me was long, on the order of days, so I needed to close the connection between checks. When closing the connection with a SqlDataReader, you loose your row position so I needed to add a counter to keep track of that.
Check you query for select. You probably get from database many rows.

Using DataTable.Load() method is not working in case two resultsets returned by DataReader

With the motive of enhancing the performance I am trying to eliminate Dataset use & implement DataReader. Here my Oracle Procedure returning two refcursors & when I am loading the first recordset in to the first DataTable, the next one never gets loaded.
Sample code looks something like this :
DataSet ds = new DataSet();
using (OracleConnection db = new OracleConnection(conString))
{
try
{
using (OracleCommand mycom = new OracleCommand())
{
mycom.CommandText = "myPkg.pr_mySP";
mycom.Connection = db;
mycom.CommandType = CommandType.StoredProcedure;
mycom.Parameters.Add("ref_list1", OracleDbType.RefCursor).Direction = ParameterDirection.Output;
mycom.Parameters.Add("ref_list2", OracleDbType.RefCursor).Direction = ParameterDirection.Output;
//mycom.FetchSize = mycom.FetchSize * 64;
db.Open();
using (OracleDataReader reader = mycom.ExecuteReader())
{
DataTable custMapList = new DataTable("dtcustMapList");
custMapList.Load(reader);
reader.NextResult(); // POST THIS THE SECOND DATATABLE DOESNOT GETTING POPULATED
DataTable custMapSubList = new DataTable("dtcustMapSubList");
custMapSubList.Load(reader);
ds.Tables.Add(custMapList);
ds.Tables.Add(custMapSubList);
}
}
}
catch (Exception ex)
{
returnString += "Error, " + ex.Message;
}
I know there are alternative methods like looping using while(reader.Read()) ... & then using reader.NextResult() will work, but in that case I have to change many other codes which I think can be avoided if the above works fine.
Appreciate an early response.
Looking at the reference source for the DataTable.Load method it is clear that the method calls NextResult() before exiting, so you don't need to do it.
....
if(!reader.IsClosed && !reader.NextResult())
reader.Close();
....
And by the way, there is no need to go to the source. Also MSDN says:
The Load method consumes the first result set from the loaded
IDataReader, and after successful completion, sets the reader's
position to the next result set, if any.
So you just need to remove this line
// reader.NextResult(); // POST THIS THE SECOND DATATABLE DOESNOT GETTING POPULATED

What object to show tables (Windows form) from SQL C#

I've got two questions basically. I have been searching quite a bit. But I mainly find console applications that are so basic I understand them regarding SQL.
However -- I take user input from my view class and send It with a method to a connect class. I want the connect class to handle all the work with SQL. Now I have a working connection and I write a correct SQL statement. How do I return it to the view class and what would you recommend like a ListBox.
When we did this in java we got a resultset and translated it so it would fit in an jtable. I wonder how I can solve this in Visual Studio.
public void askSQL (string sqlQuestion)
{
SqlCommand cmd = new SqlCommand();
cmd.CommandText = sqlQuestion;
cmd.Connection = connector;
try
{
rdr = cmd.ExecuteReader();
while (rdr.Read())
{
MessageBox.Show("Du läser in data");
}
}
catch (Exception e)
{
MessageBox.Show("Fel vid anslutningen!" + e);
}
}
I currently have no problems with the code. The connection is working and I recieve multiple answers "Du läser in data" since there are multiple columns and rows in my table.
Have the function return DataTable object that will be populated with the database data:
public DataTable askSQL (string sqlQuestion)
{
DataTable table = new DataTable();
try
{
using (SqlDataAdapter adapter = new SqlDataAdapter(sqlQuestion, connector))
{
adapter.Fill(table);
}
}
catch (Exception e)
{
MessageBox.Show("Fel vid anslutningen!" + e);
}
return table;
}
This will return something as close to table structure as possible.. the DataTable has Rows collection, each row with data of one record from database, and each DataRow has ItemArray collection with the field values.
For exampe, to access field called "Email" from the second row, have such code:
DataTable table = connect.askSQL("Select Email From Users Where UserId In (1, 2)");
string email = table.Rows[1]["Email"].ToString();
You can iterate over the rows with simple loop:
foreach (DataRow row in table.Rows)
{
string email = row["Email"].ToString();
MessageBox.Show("Current email: " + email);
}
Hope this is enough information. :)

Sqldataapter advice

At present i have a the following code populating a datagridview showing the user account information on our system. What i want to do do is have a checkbox on the datagridview for the option "accountenabled" and a update button at the bottom of the form so it will update all users that have had changes made against them. I am currently pulling the data back using an sqldatareader however from what i have read i need to use a sqldataadapter. I`ve created the column names on the datagridview and the reader is currently pulling everything back correctly.
Could someone please point me in the right direction of doing this with an sqldatadapter?
Thanks
public UserAdmin()
{
InitializeComponent();
//Load user list
// Locals
Functionality func = new Functionality();
SqlConnection supportDB = null;
SqlCommand CheckUser = null;
SqlDataReader rdr;
DataSet ds = new DataSet();
DataTable dt = new DataTable();
string User = System.Environment.UserName.ToString();
string spName = "gssp_ShowAllUsers";
try
{
using (supportDB = new SqlConnection(GSCoreFunc.ConnectionDetails.getConnectionString(ConnectionType.SupportDB)))
{
using (CheckUser = new SqlCommand(spName, supportDB))
{
// Set the command type
CheckUser.CommandType = CommandType.StoredProcedure;
// Populate the parameters.
CheckUser.Parameters.Add(func.CreateParameter("#spErrorID", SqlDbType.Int, ParameterDirection.Output, DBNull.Value));
// Open the connection and populate the reader with the SP output
supportDB.Open();
rdr = CheckUser.ExecuteReader();
if (CheckUser.Parameters["#spErrorID"].Value != null)
{
throw new InvalidOperationException();
}
// If the data reader has rows display output on label
if (rdr.HasRows)
{
//Output values
while (rdr.Read())
{
//Bind to data table
dgvUsers.Rows.Add(rdr["agentID"].ToString(), rdr["createdon"].ToString(), rdr["firstname"].ToString(), rdr["lastname"].ToString(), rdr["username"].ToString(), rdr["emailaddress"].ToString(), rdr["Departments"].ToString(), rdr["accountenabled"].ToString(), rdr["AgentAccountLevel"].ToString());
}
}
// Close reader and connection.
rdr.Close();
supportDB.Close();
}
}
}
catch (Exception ex)
{
//Show error message
string error = ex.ToString(); //Real error
string FriendlyError = "There has been error loading the user list"; // Error user will see
GSCoreFunc.ShowMessageBox.msgBoxErrorShow(FriendlyError);
//Log error to ExceptionDB
GSCoreFunc.ReportException.reportEx(GSCoreFunc.ApplicationInformation.ApplicationName, error, FriendlyError, GSCoreFunc.ApplicationInformation.ComputerName, GSCoreFunc.ApplicationInformation.OperatingSystem, GSCoreFunc.ApplicationInformation.screenSize, GSCoreFunc.ApplicationInformation.IPAdddress, GSCoreFunc.ApplicationInformation.domainName);// Pass error to GSCoreFunc to log to the ExceptionDB
}
}
private void btClose_Click(object sender, EventArgs e)
{
//Close window
Close();
}
}
}
There is nothing wrong with using the SqlDataReader. The SqlDataAdapter is a higher level api that allows you to iterate through an SqlDataReader and store a copy of the results in a DataTable or a DataSet. This copy can then be used as the data source for your DataGridView.
One thing I would change with your code would be to use data binding instead of generating each row manually. If you set the DataSource property of the grid to either your SqlDataReader or to a DataTable filled by an SqlDataAdapter and then call the grids DataBind() method the grid should be filled automatically with your data.
To control the columns you would make sure your query only returns the required columns, and you would define the column setup in your aspx-file.
Using data binding is generally an easier and more flexible approach, so you should consider using that instead.
Look at this code
Initialize a sql adapter and fill with data source . Use a connection string other than using sql data source because it would be easy for customizing. :)

Categories