Why does IDataReader lose a row? - c#

I am facing this problem. I have a stored procedure which returns 6 rows when I execute it.
But when I am retrieving the rows in my app by using ExecuteReader, it only returns only 5 rows. Why is it losing a row??
My stored procedure consists of 5 union statements which are getting filled from a single table:
dbase.AddInParameter(cmd, "#LoginUser", DbType.String, UserID);
try
{
using (IDataReader dr = dbase.ExecuteReader(cmd))
if (dr.Read())
{
dt = new DataTable("DashBoard");
dt.Load(dr);
}
}
dbase is my database object. And cmd is the SqlCommand used to call the stored procedure.
UserID is parameter is passing
Stored procedure code is:
ALTER PROCEDURE [dbo].[USP_ViewAdminDashBoard](#LoginUser varchar(75))
-- Add the parameters for the stored procedure here
AS
BEGIN
SET NOCOUNT ON;
SET DATEFORMAT DMY;
DECLARE #LastLoginDate as DateTime
Select #LastLoginDate = dbo.UDF_GetLastLoginByUser(#LoginUser)
Select 'Last Login Date', convert(varchar(12),#LastLoginDate,105)
Union
Select 'Nos. Records pending for Upload' as Title, convert(varchar(5),COUNT(s.BatchID)) Total from dbo.BREGISTRATIONENTRY s, Dbo.TBL_iBATCH B
where B.BatchID = s.BatchID And b.Forwarded = 0 and b.isBatchClosed = 1
END

Your first dr.Read is advancing to the first row. The DataTable.Load is reading the remaining rows but not the first row
Use HasRows to test for existence and don't use Read before the DataTable.Load
Edit:
Just load the DataTable without testing first: then test on the DataTable. There is no HasRows in the IDataReader interface.

IDataReader is a forward reader,it means when you read a row,that row will be deleted from the reader and you can't get it from the reader any more.

joshua i also has this problem while using enterprise library more than two time even i use same code written down but both time i found a problem in my store procedure there must be any wrong selection in query or any think else which db server does not detect and i solve it and
my IDataReader select all rows and also use same way as suggest by 'gbn'
using (IDataReader dr = oDb.ExecuteReader(p_oDbCommand))
{
if (dr != null)
{
ds.Tables[0].Load(dr);
}
}
return dt;

Related

Error when Passing a DataTable from vb.net to a stored procedure in SQL Server

I have a stored procedure that receives a table, but whenever I make the insert I get this error:
Cannot convert a char value to money. The char value has incorrect syntax.
I create a table as type
CREATE TYPE dbo.Example AS TABLE
(
Id_Exmp int,
Discount_Amount Money
)
I have a stored procedure that takes a datatable as a parameter:
CREATE PROCEDURE dbo.SP_Exmp
#datatable dbo.Example READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.destination_table(column_list)
SELECT column_list
FROM #datatable;
END
This is the code I have in VB.net:
Function Example(ByVal dtTable as datatable)
Try
ConexionSQL = New SqlConnection(IniciaConexion)
Command = New SqlCommand("SP_Exmp", ConexionSQL)
Command.CommandType = CommandType.StoredProcedure
'Parametros a Utilizar
Command.Parameters.AddWithValue("#datatable ", dtTable )
ConexionSQL.Open()
If Command.ExecuteNonQuery() Then
Return True
Else
Return False
End If
ConexionSQL.Close()
ConexionSQL.Dispose()
Command.Dispose()
Catch ex As Exception
MensajeError(ex, "Error")
Return False
End Try
End Function
A couple of things:
Command.Parameters.AddWithValue("#datatable ", dtTable )
You need to set its SqlDbType:
Command.Parameters.AddWithValue("#datatable ", dtTable).SqlDbType = SqlDbType.Structured;
2. Dispose your command before the connection, or even better use Using pattern:
Using ConexionSQL = New SqlConnection(IniciaConexion)
Using command As New SqlCommand
End Using
End Using
3. Instead of the Money DataType, try changing your SQL DataTable to have a second column of Decimal and your .Net Datatable to have a Decimal, eg:
DataTable dtTable = new DataTable();
dtTable.Columns.Add("Id_Exmp", typeof(int));
dtTable.Columns.Add("Discount_Amount", typeof(decimal));
I found out what it was the problem, the main issue was the order how I create the dtTable, in order to fill correctly the table type, I most create the column with the same order on the type, as an Example.
Dim DtTable as datatable = Exec_Query(Query:="Select Id_exm, Discoun..", LRw:=false)
for wherever reason I put in disorder, discount first and id second, I didn't pay attention to that,
then everything works just fine.
You have to open your connection.
ConexionSQL.Open().

Displaying data from SQL database in C# WinForm controls (textBox, comboBox, label)

I have a performance problem with displaying data from an SQL database in my application. The problem is that I have a large number of parameters that I need to display (customers personal data, his current statistics etc.).
So far I've used either SqlCommand.ExecuteScalar (for single parameters), or DataTable.Rows[].ItemArray.GetValue() (for multiple parameters - I fill the DataTable with SqlDataAdapter whose query withdraws the necessary data from the database) and assigned their values to the appropriate control. Assuming that command is an SqlCommand type:
For single parameter
command.CommandText = "SELECT Parameter1 FROM MyTable WHERE Condition = Value";
textBox1.Text = command.ExecuteScalar().ToString();
For multiple parameters (SDA is a SqlDataAdapter):
command.CommandText="SELECT Parameter1 - ParameterN FROM MyTable WHERE Condition = Value";
SDA.SelectCommand = command;
SDA.Fill(MyDataTable);
textBox1.Text = MyDataTable.Rows[0].ItemArray.GetValue(0).ToString();
comboBox1.Text = MyDataTable.Rows[0].ItemArray.GetValue(1).ToString();
/*
I repeat similar lines of code for each parameter and display it in the appropriate control.
*/
This approach works correctly but when I have a large number of parameters (20+), it works very slowly.
Is there a more efficient way to display these amounts of data, and how would I implement it?
Thank you
Probably, with the second example, a SqlDataReader will perform better because you read the values just one time, while with a DataAdapter, you need to load the DataTable and then loop over the rows of the table (Effectively reading data two times).
command.CommandText="SELECT Field1,...,FieldN FROM MyTable WHERE Condition = Value";
SqlDataReader reader = command.ExecuteReader();
while(reader.Read())
{
// Of course this works correctly just if your query returns one row....
textBox1.Text = reader.GetString(0);
comboBox1.Text = reader.GetString(n);
}
You could also try with the Field<T> extension for the DataRow
command.CommandText="SELECT Field1,...,FieldN FROM MyTable WHERE Condition = Value";
SqlDataAdapter SDA = new SqlDataAdapter(command);
SDA.Fill(MyDataTable);
textBox1.Text = MyDataTable.Rows[0].Field<string>("Field1");
comboBox1.Text = MyDataTable.Rows[0].Field<string>("FieldN");
However, I think that the real performance gain would be in the query that you submit to the database engine and in the correct working of indexes on your tables.
Try to retrieve the minimun number of rows possible, search on indexed fields and/or change to a stored procedure.
here i had write sample stored procedure in wich you can get idea...
you can pass as amny parameter as you can in xml format and insert into temp table...
now you have table with value Name/value pair means Paramater name /value....
now you can do your furteher work...
/*
EXEC wa_TempGetDaya '<SampleXML>
<tblXML><AccountID>3</AccountID><Code>11</Code><Description>Leptospiral infect NEC</Description></tblXML>
</SampleXML>'
*/
CREATE PROCEDURE wa_TempGetDaya
(
#ParaXML NVARCHAR(MAX)
)
AS
SET NOCOUNT ON
BEGIN
DECLARE #AccountID INT
DECLARE #MyXML XML
SET #MyXML = #ParaXML
IF OBJECT_ID('tempdb..#TempData') IS NOT NULL
DROP TABLE #TempData
SELECT * INTO #TempData
FROM (
SELECT
Parse.value('(AccountID)[1]', 'INT') AS 'AccountID',
Parse.value('(Code)[1]', 'Varchar(100)') AS 'Code',
Parse.value('(Description)[1]', 'varchar(1000)') AS 'Description'
FROM
#MyXML.nodes('/SampleXML/tblXML') AS YourData(Parse)
) AS tbl
declare #para1 varchar(20)
declare #para2 varchar(20)
declare #para3 varchar(20)
SELECT #para1 =AccountID ,#para2 =Code,#para3 =Description from #TempICD
END

calling a stored procedure from C# using SqlDataAdapter

I have a stored procedure which has been well tested and works perfectly from SQL Server Management Studio. All the procedure does is check for the existence of a record in a table, return it if it exists, or create it and then return it if it doesn't.
The procedure looks like this:
CREATE proc [dbo].[spInsertSerialBatch]
#MOS_JOB varchar(12), --PASSED COMMAND LINE
#MOS_LOT varchar(4) = NULL, --PASSED COMMAND LINE
#MES_USER varchar(12) = 'NOT PASSED',--PASSED COMMAND LINE
#COMPUTERNAME varchar(100) = 'NOT PASSED' --ENVIRONMENT VARIABLE
as ....
I use a SqlDataAdapter, which I have used repeatedly without any problems. The setup looks like this:
using (SqlCommand sqlComm = new SqlCommand("dbo.spInsertSerialBatch", serialBatchDataConnection))
{
if (serialBatchDataConnection.State != ConnectionState.Open)
{
serialBatchDataConnection.Open();
}
sqlComm.CommandType = CommandType.StoredProcedure;
sqlComm.Parameters.AddWithValue("#MOS_JOB", options.jobNumber);
sqlComm.Parameters.AddWithValue("#MOS_LOT", options.lotNumber);
sqlComm.Parameters.AddWithValue("#MES_USER", options.userId);
sqlComm.Parameters.AddWithValue("#COMPUTERNAME", System.Environment.MachineName);
SqlDataAdapter sda = new SqlDataAdapter(sqlComm);
DataTable dt = new DataTable();
int rowsAffected = sda.Fill(dt);
}
I then examine the results of the table after Fill is executed. It works fine when the row exists in the table, but if it doesn't, and the stored proc needs to generate it, Fill returns 0 rows and the data table remains empty. No errors/exceptions are thrown, I just get no results.
I suppose I could change the code to use ExecuteNonQuery and not use the DataAdapter, but I see no reason why this shouldn't just work; I prefer having a data table (which may result in more than a single row in some cases) than using a data reader and looping over the data to get the results.
Any suggestions as to why this might fail? I've looked over several posts on this and other sites that are similar, but haven't found a satisfactory answer. Any suggestions are greatly appreciated.
Thanks,
Gary
The entrire sp is quite large and probably too proprietary to publish...
--return inserted rows
SELECT 'CREATED' as [spRESULT], o.*
FROM #output o
END
/*
* Return existing SerialBatch(s)
*/
BEGIN
SELECT 'RETRIEVED' as [spRESULT], s.*
FROM SerialBatch s
WHERE SerialBatchId = #formattedId
UNION
/*
* pull in products that have components that need labels as well
*/
SELECT 'RETRIEVED' as [spRESULT],s.*
FROM SerialBatch s
WHERE SerialBatchParentId = #formattedId
END
This is the end of the stored procedure. I tried executing a DataReader instead and the result is the same...I get no results for the case when the sp has to create it, but again it runs perfectly stand-alone in SQL Server Management Studio.
Problem solved. Turns out that the OpenQuery string passed to Oracle was converting an empty string to a NULL and preventing the new row from being returned. All I need to add was a check for both NULL and empty string:
if #MOS_LOT IS NULL or #MOS_LOT = ''
set #MOS_LOT = ' ' --EMPTY STRINGS BEING EQUIVALENT TO NULLS

C# - SELECT Query number of rows affected is always -1

I have this code and it always returns -1.I have three tables (a picture is more suggestive ):
I want to see if the row is already in the ReservationDetails table, and if it's not to insert it.
try
{
SqlConnection conn = new SqlConnection...
SqlCommand slct = new SqlCommand("SELECT * FROM ReservationDetails WHERE rID=#rID AND RNumber=#RNumber", conn);
slct.Parameters.AddWithValue("#rID", (int)comboBox1.SelectedValue);
slct.Parameters.AddWithValue("#RNumber", dataGridView1.SelectedRows[0].Cells[0].Value);
int noRows;//counts if we already have the entry in the table
conn.Open();
noRows = slct.ExecuteNonQuery();
conn.Close();
MessageBox.Show("The result of select="+noRows);
if (noRows ==0) //we can insert the new row
Have you read the documentation of SqlCommand.ExecuteNonQuery?
For UPDATE, INSERT, and DELETE statements, the return value is the number of rows affected by the command. When a trigger exists on a table being inserted or updated, the return value includes the number of rows affected by both the insert or update operation and the number of rows affected by the trigger or triggers. For all other types of statements, the return value is -1. If a rollback occurs, the return value is also -1.
And your query is SELECT.
You should
1) Change your TSQL to
SELECT COUNT(*) FROM ReservationDetails WHERE ...
(better still, use IF EXISTS ...)
2) and use ExecuteScalar():
noRows = (int) slct.ExecuteScalar();
Also: you will need to use a transaction (or some other atomic technique), or else someone could insert a row in-between you testing and trying to insert it...
All that said, it would be better to create a stored procedure that given your parameters, atomically tests and inserts into the table, returning 1 if successful, or 0 if the row already existed.
It is better to do it in a single query so that you do not need to request server two times.
Create a procedure like this and call it from the code.
IF EXISTS (SELECT 1 from ReservationDetails WHERE rID=#rID AND RNumber=#RNumber)
BEGIN
insert into ReservationDetails values(#rID,#RNumber)
END
As per Microsoft:
You can use the ExecuteNonQuery to perform catalog operations (for example, querying the structure of a database or creating database objects such as tables), or to change the data in a database without using a DataSet by executing UPDATE, INSERT, or DELETE statements.
What you may need, instead of ExecuteNonQuery is ExecuteScalar and put the COUNT in your select query.
i.e.
SqlCommand slct = new SqlCommand("SELECT COUNT(*) FROM ReservationDetails WHERE rID=#rID AND RNumber=#RNumber", conn);
Also, try to make use of the using statement in C#, so you don't need to worry about closing the connection manually, even if things fail.
i.e.
using (SqlConnection conn = new SqlConnection(connString))
{
SqlCommand cmd = new SqlCommand(sql, conn);
try
{
conn.Open();
newProdID = (Int32)cmd.ExecuteScalar();
}
catch (Exception ex)
{
//Do stuff
}
}
see:
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.executescalar.aspx
#nickNatra
When ever you use
Select command
It will return you values. Which can be either used by
DataSet or SqlDataReader
But
command.ExecuteNonQuery()
is used only when you are using
Insert , Update , Delete where the Rows are getting effected in your table
Yes, If you do want to know how much records are there in your query.
You can perform
a) Modify your query "select count(*) from table"
where you will only get one value ie. Number of Rows.
b) Using this query perform command. ExecuteScalar() which will return only First row and first column which is the Row Count
Hence this satisfy's your requirement.
Cheers!!

Just one column from a stored procedure

I have this stored procedure in my SQL Server;
CREATE PROC GetChild
#Child_ID int
AS
SELECT * FROM Children WHERE Child_ID = #Child_ID
I am calling this stored procedure from C#.
I would like to know, if it is possible to call just one column from this table instead of the whole record from C#.?
Assuming you mean return one column, if this is what your stored procedure looks like then no. It will always return all columns back to the client.
You can simply ignore the returned columns that you do not need. Or you can change the stored procedure to only return one column. But as is, it always returns all of them.
You have only have three choices.
Rewrite the Stored procedure to just return the columns you want.
e.g. SELECT foo from Children Where Child_id = #Child_ID
Use a DataReader and just get the columns you want from that
Using a reader directly
while (reader.Read())
`Console.WriteLine("{0}", reader.GetInt32(0));`
Using the Linq extension methods which allows you to filter and sort the results as well as getting just the columns you want.
var List = rdr.Cast<IDataRecord>()
.Select(s => s.GetInt32(0)).ToList();
Abandon the stored procedure and write Select statements against the table. See Pranay's answer
just write below query
select columnname from Children where Child_ID = #Child_ID
columnname- is name of the column you want to retrive
Code for you
SqlConnection mySqlConnection =new SqlConnection("server=(local)\\SQLEXPRESS;database=MyDatabase;Integrated Security=SSPI;");
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText ="select columnname from Children where Child_ID = #Child_ID";
mySqlCommand .Parameters.Add("#Child_ID", SqlDbType.Int);
mySqlCommand .Parameters["#Child_ID"].Value = idvalue;
mySqlConnection.Open();
SqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader(CommandBehavior.SingleRow);
while (mySqlDataReader.Read()){
Console.WriteLine("mySqlDataReader[\" columnname\"] = " +
mySqlDataReader["columnname"]);
}
mySqlDataReader.Close();
mySqlConnection.Close();
Use a SqlDataReader for this:
SqlConnection DbConn = new SqlConnection(YourConnStringHere);
SqlCommand ExecStoredProc = new SqlCommand();
ExecStoredProc.CommandType = CommandType.StoredProcedure;
ExecStoredProc.CommandText = "GetChild";
ExecStoredProc.Connection = DbConn;
ExecStoredProc.Parameters.AddWithValue("#ChildID", YourChildId);
using (DbConn)
{
DbConn.Open();
using (SqlDataReader sdr = ExecStoredProc.ExecuteReader())
{
while(sdr.Read())
// reference your column name like this:
// sdr.GetString(sdr.GetOrdinal("YourColumnName"));
}
}
You can reference any column returned by the SqlDataReader.Read() method. Likewise, if you are looking for an integer value:
int someInt = sdr.GetInt32(sdr.GetOrdinal("YourColumnName"));
From this thread ( Insert results of a stored procedure into a temporary table ), you might want to try OPENROWSET.
First, configure your DB,
sp_configure 'Show Advanced Options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO
then, depending on your connection :
SELECT yourcolumnname
FROM
OPENROWSET(
'SQLNCLI',
'server=yourservername;database=yourdbname;uid=youruserid;pwd=youruserpwd',
'EXEC [GetChild] yourchildid'
)
or
SELECT yourcolumnname
FROM
OPENROWSET(
'SQLNCLI',
'server=yourservername;database=yourdbname;Trusted_Connection=yes',
'EXEC [GetChild] yourchildid')
I wouldn't use this solution when retrieving only one line. Performance would be really bad.
For retrieving a great number of lines, this should do the job.

Categories

Resources