Scenario
I'm working with SQL Server 2017 (not possible to change)
I'm using Visual Studio 2019 in C# console and .NET Framework 4.5 (possible to change)
I'm using ADO.NET because several years before we couldn't use Entity Framework, as the system is made to work with a stored procedure that returns at least 100k rows (possible to change)
Situation
I have an USP that returns a table that is at least 100k of rows by 20 fields. I need to add an output parameter in order to get also an ID created by the USP itself. So, the situation is that I need return a table and an ID (called ProcMonitorId). I don't know if this is even so possible (See workarounds section)
At the SQL level is seems to be so far so good:
CREATE PROCEDURE [myschema].[mystore]
#ProcMonitorId BIGINT OUTPUT
AS
BEGIN
BEGIN TRANSACTION
(...)
SELECT fields FROM myTable
SELECT #ProcMonitorId = #internalVariable
SQL execution:
And at repository layer (only relevant lines, someone were surprised for health of example):
var command = new SqlCommand("myStoreProcedure", mycon);
command.CommandType = CommandType.StoredProcedure;
SqlParameter outPutParameter = new SqlParameter();
outPutParameter.ParameterName = "#ProcMonitorId";
outPutParameter.SqlDbType = System.Data.SqlDbType.BigInt;
outPutParameter.Direction = System.Data.ParameterDirection.Output;
command.Parameters.Add(outPutParameter);
// Open connection etc-etc that works
SqlDataAdapter da = new SqlDataAdapter(command);
DataTable dt = new DataTable();
string ProcMonitorId = outPutParameter.Value.ToString();
da.Fill(dt);
Everything worked fine until the addition of the output at C# level. It returns in the line:
string ProcMonitorId = outPutParameter.Value.ToString();
it returns NullReferenceException because Value is null (that can't be) and of course, can't convert to String. I would solve this situation by adding a ? but if that's situation happens for real, I need catch it any way as error. The main idea is that Value can not be null.
As I don't have any ORM map, (and my expertise is not ADO.NET but Entity Framework) I can't understand why is null (No, is not null at SQL layer, always return a value)
Question
How can I solve this error or how can I return a BIGINT parameter and ALSO a table result?
Workarounds
As I first glance I have to solve it quickly, I made a:
SELECT 1 as type, #procID as procid, null as data1, null as data2
UNION ALL
SELECT 2 as type, null as procid, data1, data2
in order to simulate a "header" and "data" rows on one single table.
But I don't like this solution and is not very elegant and flexible. I've to parse the header every time.
Thanks in advance and please comment anything, tip, help, workaround, I will be glade to update my answer if more information is needed.
Also I can make my Framework to .NET Core or change to Entity Framework. That I can't change is my SQL version
Update #2
No changes in SQL - Still working as screenshot
In C# - Hangs out for ever
SqlConnection connection = new SqlConnection(ConfigurationManager.AppSettings["DbConnection"]);
connection.Open();
var command = new SqlCommand("myUSP", connection);
command.CommandType = CommandType.StoredProcedure;
command.CommandTimeout = Convert.ToInt16(ConfigurationManager.AppSettings["DataBaseTimeOut"]);
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
SqlParameter r = command.Parameters.Add("#ProcMonitorId", SqlDbType.BigInt);
r.Direction = ParameterDirection.Output;
DataTable dt = new DataTable();
using (var rdr = command.ExecuteReader())
{
dt.Load(rdr);
long id = (long)r.Value;
}
SqlDataAdapter da = new SqlDataAdapter(command);
da.Fill(dt);
The parameter value won't be available until after you consume the resultset, eg
var cmd0 = new SqlCommand("create or alter procedure pFoo #id int output as begin select * from sys.objects; set #id = 12; end", con);
cmd0.ExecuteNonQuery();
var cmd = new SqlCommand("pFoo", con);
cmd.CommandType = CommandType.StoredProcedure;
var p1 = cmd.Parameters.Add("#id", SqlDbType.Int);
p1.Direction = ParameterDirection.Output;
var dt = new DataTable();
using (var rdr = cmd.ExecuteReader())
{
dt.Load(rdr);
var id = (int)p1.Value;
}
You should use a Parameter with the Direction property set to ReturnValue, and, inside the sp, declare an internal variable and set it to the value you want.
Then call the RETURN statement before leaving the StoredProcedure.
As an example, see this SP:
ALTER PROCEDURE [GetTimeZoneGMT]
#TimeZone NVARCHAR(128)
AS
BEGIN
DECLARE #timeZoneNumber as INT = -20;
IF #TimeZone ='Pacific/Midway'
SET #timeZoneNumber = -11
ELSE IF #TimeZone ='Pacific/Niue'
SET #timeZoneNumber = -11
ELSE IF #TimeZone ='Pacific/Pago_Pago'
SET #timeZoneNumber = -11
SELECT 1 -- or whatever you need to have as result set
RETURN #timeZoneNumber;
END
The stored procedure ends with a (bogus) SELECT statement but also has a RETURN statement with the parameter set inside the SP logic.
Now from the C# side you could call it in this way (LinqPad example)
using (var connection = new SqlConnection("Data Source=(LOCAL);Initial Catalog=LinqPADTest;Integrated Security=True;"))
{
connection.Open();
SqlCommand cmd = new SqlCommand("GetTimeZoneGMT", connection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#TimeZone", SqlDbType.NVarChar).Value = "Asia/Kuala_Lumpur";
SqlParameter r = cmd.Parameters.Add("#p2", SqlDbType.BigInt);
r.Direction = ParameterDirection.ReturnValue;
DataTable dt = new DataTable();
dt.Load(cmd.ExecuteReader());
r.Value.Dump(); // Prints -20
dt.Dump(); // Prints a row with a single column with 1 as value
}
Hello everyone I am currently working on some testing project and I am having a little problem. Using selenium, I need to SendKey in specific element but instead of fixed value i need to use value (data) from my database. Can anyone help me with how to retrieve single value from database and store it in a variable so i can use it later.
Thank you and sorry for a noobish question - see code below:
SqlConnection conn = new SqlConnection();
SqlCommand command;
SqlDataReader dataReader;
conn.ConnectionString = "Server=******;Database=****;User ID=sqlserver;password=****;MultipleActiveResultSets=true;");
string query = "select RequestID, from AutomaticPayment where RequestID ='1230322'";
DataTable dt = new DataTable();
command = new SqlCommand(query, conn);
conn.Open();
dataReader = command.ExecuteReader();
dt.Load(dataReader);
driver.FindElement(By.Id("requestID")).SendKeys(VALUE FROM DATABASE);
You can use the following code
using (SqlConnection connection = new SqlConnection(_connectionString))
{
SqlDataAdapter sda = new SqlDataAdapter(query, connection);
connection.Open();
SqlCommand cmd = new SqlCommand(query, connection);
try
{
result = cmd.ExecuteScalar().ToString();
}
catch(NullReferenceException n)
{
result = "";
}
}
ExecuteScaler gets you the first column of the first row and additional columns are ignored. Use the value from result in your SendKeys()
Use conditions to limit the result:
Select data
SELECT TOP 1 RequestID FROM AutomaticPayment // Always returns 1 row
Or
SELECT RequestID FROM AutomaticPayment WHERE Id = 123 // Id must be unique to return 1 row
And maybe other ways.
Get value
var value = dt.Rows[0][1];
Or
var value = dt.Rows[0]["RequestID"];
From what i worked on with SqlCommand just do the following :
int yourId = 0;
dataReader = command.ExecuteReader()
while(dataReader.Read())
{
yourId = dataReader.GetInt32(0);
}
With that, you should have your value set to the first column of the dataReader. (that is selected thanks to your query, since you are requesting on a specific id, i guess it will return only one column
there is many other type available for Reader : Reader Microsoft Doc
And if you have in the futur many data to collect, use the ORM entity framework, work well for me
Source
EDIT :
Since you are only querying one data, maybe the solution of #work_ishaan is better than mine in this case, check it out.
I am updating a row in SQL DB using the code below. The loop works and it updates the row but the problem is that each time it goes through the loop, it only updates one value and the other values are overwritten. So at the end, it has updated but instead of multiple values being inputted to the table for the respective Project ID, it only puts one value for the respective Project ID. I am not receiving any errors for this. Any help is much appreciated.
for (int i = 0; i < cbAvailableEntities.Items.Count - 1; i++)
{
SqlConnection connection = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand("UpdateProjectEntity", connection);
using (connection)
{
connection.Open();
using (cmd)
{
if (cbAvailableEntities.Items[i].Selected)
{
cmd.CommandType = CommandType.StoredProcedure;
//the following is the Project ID for the row being updated.
SqlParameter paramPID = new SqlParameter("#ProjectID", nr.ProjectID);
cmd.Parameters.Add(paramPID);
nr.Entities = cbAvailableEntities.Items[i].Value;
cmd.Parameters.AddWithValue("#CorpID", nr.Entities);
cmd.ExecuteNonQuery();
}
}
}
}
Here is the SQL query for the Stored Procedure "UpdateProjectEntity"
ALTER PROCEDURE [dbo].[UpdateProjectEntity]
#ProjectID int,
#CorpID int
AS
BEGIN
UPDATE [dbo].[ProjectEntity]
SET
[CorpID] = #CorpID
WHERE
ProjectID = #ProjectID
END
Here are screenshots of inputs and results when I run the program.
These are the checkboxes I am saving to the DB
This is the result after I have saved to the DB
I changed the date to show that everything else works in this program.
I can see you save your enity INT, maybe you should save it as a Comma Separated String.
So instead of save 1, you can save 1,2,3
Of course you will have to add some logic before the save building and concat the string. And also need to do some parsing when you read from db doing the split by ,
The other aproach is creating a relation table to indicate with are the options selected.
But this is also have problem when you remove a selection and add new ones.
ProjectID CorpID
1 1
1 2
1 3
The way I resolved this without making any changes to the DB is I used a DELETE Statement to delete the rows with the ProjectID and then I used an insert stored procedure that I have used before. It was a lot faster than creating another table among all that is already in place. So the code looks like this
for (int i = 0; i < cbAvailableEntities.Items.Count - 1; i++) {
SqlConnection connection = new SqlConnection(connString);
SqlCommand com = new SqlCommand("InsertProjectEntity", connection);
SqlCommand dcm = new SqlCommand();
using(connection) {
//First time going through the loop, i = 0 is true.
if (i == 0) {
connection.Open();
using(com) {
//This will remove anything in the DB related to the ProjectID being edited.
dcm.Connection = connection;
dcm.CommandText = "DELETE FROM [dbo].[ProjectEntity] WHERE ProjectID = " + _pID;
dcm.ExecuteNonQuery();
//This will insert all items checked in the checkboxlist but will not insert the unchecked.
if (cbAvailableEntities.Items[i].Selected) {
com.CommandType = CommandType.StoredProcedure;
SqlParameter paramPID = new SqlParameter("#ProjectID", nr.ProjectID);
com.Parameters.Add(paramPID);
nr.Entities = cbAvailableEntities.Items[i].Value;
com.Parameters.AddWithValue("#CorpID", nr.Entities);
com.ExecuteNonQuery();
}
}
} else {
connection.Open();
using(com) {
//This will insert all items checked in the checkboxlist but will not insert the unchecked.
if (cbAvailableEntities.Items[i].Selected) {
com.CommandType = CommandType.StoredProcedure;
SqlParameter paramPID = new SqlParameter("#ProjectID", nr.ProjectID);
com.Parameters.Add(paramPID);
nr.Entities = cbAvailableEntities.Items[i].Value;
com.Parameters.AddWithValue("#CorpID", nr.Entities);
com.ExecuteNonQuery();
}
}
}
}
How to check if my table is empty from C#?
I have something like:
public MySqlConnection con;
public MySqlCommand cmd;
con = new MySqlConnection(GetConnectionString());
con.Open();
cmd = new MySqlCommand("SELECT * FROM data;", con);
Or I don't need to call SELECT statement?
You can use COUNT(*) with no WHERE close and see if exactly how many rows exist with the result.
Or you can do a SELECT (id) FROM tablename with no WHERE clause and if no rows are returned then the table is empty.
I'll give you an example in C# good luck
public bool checkEmptyTable(){
try
{
MySql.Data.MySqlClient.MySqlCommand com = new MySql.Data.MySqlClient.MySqlCommand();
conn = new MySql.Data.MySqlClient.MySqlConnection("YOUR CONNECTION");
com.Connection = conn;
com.CommandText = "SELECT COUNT(*) from data";
int result = int.Parse(com.ExecuteScalar().ToString());
return result == 0; // if result equals zero, then the table is empty
}
finally
{
conn.Close();
}
}
If 'data' might be a big table you would be better with this (where pkdata is your primary key field)
SELECT COUNT(*) FROM data WHERE pkdata = (SELECT pkdata FROM data LIMIT 1);
This will run very quickly whether you have 0 rows in 'data' or millions of rows. Using SELECT with no WHERE or ORDER BY means it just pulls the first row available, LIMIT 1 stops it getting more than 1.
Maybe something to look for if you have a program that ran very quickly six months ago but now runs like a dog in treacle!
SELECT COUNT(*)
FROM table
WHERE `col_name` IS NOT NULL
I'm creating a temporary table, #ua_temp, which is a subset of regular table. I don't get an error, but when I try to SELECT from #ua_temp in the second step, it's not found. If I remove the #, a table named ua_temp is created.
I've used the exact same technique from created the table with SELECT INTO elsewhere. It runs fine, so I don't think it has anything to do with database settings. Can anyone see the problem?
// Create temporary table
q = new StringBuilder(200);
q.Append("select policy_no, name, amt_due, due_date, hic, grp, eff_dt, lis_prem, lis_grp, lis_co_pay_lvl, ");
q.Append("lep_prem, lapsed, dn_code, [filename], created_dt, created_by ");
q.Append("into #ua_temp from elig_ua_response ");
q.Append("where [filename] = #fn1 or [filename] = #fn2 ");
sc = new SqlCommand(q.ToString(), db);
sc.Parameters.Add(new SqlParameter("#fn1", sFn));
sc.Parameters.Add(new SqlParameter("#fn2", sFn2));
int r = sc.ExecuteNonQuery();
MessageBox.Show(r.ToString() + " rows");
// Rosters
q = new StringBuilder(200);
q.Append("select policy_no,name,amt_due,due_date,hic,grp,eff_dt,");
q.Append("lis_prem,lis_grp,lis_co_pay_lvl,lep_prem,lapsed,dn_code,[filename] ");
q.Append("from #ua_temp where (lis_prem > 0.00 or lep_prem > 0.00) ");
q.Append("and [filename] = #fn order by name");
sc.CommandText = q.ToString();
sc.Parameters.Clear();
sc.Parameters.Add(new SqlParameter("#fn", sFn));
sda = new SqlDataAdapter(sc);
sda.Fill(ds, "LIS LEP Roster");
To answer some of the obvious questions: This program was running fine using the source table, elig_ua_response. The reason for introducing the temp table was that I want to delete some of the rows for this particular report. I put brackets around the column [filename] while testing to be sure it's not a key word issue. The second SELECT works fine if you replace #ua_temp with elig_ua_response. I've tried different names for the temp table. The MessageBox showing the number of rows was just for debugging purposes; it doesn't affect the problem.
Joe Zack's comment is what helped me understand what's happening here. A very clear and concise explanation. This should be an answer so that it's more visible to people arriving here from a google search.
SqlCommand calls sql with parameters via sp_executesql when there are parameters, which means your temp table gets created inside (and then clean up in) a stored procedure so it's not available to future calls - even when they share the same connection
I think the solution to your problem is to combine the creation of the temp table and selecting from that temp table into one query (see code snippet #3 below). Executing the command twice (as you do in the code in your question) seems to work ok if you are not using command parameters, but fails if they are introduced. I tested a few different approaches and here's what I found.
1) WORKS OK: Use same command object, no command parameters, execute command twice:
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
const string query = #"
CREATE TABLE #temp
([ID] INT NOT NULL, [Name] VARCHAR(20) NOT NULL)
INSERT INTO #temp VALUES(1, 'User 1')
INSERT INTO #temp VALUES(2, 'User 2')";
cmd.CommandType = CommandType.Text;
cmd.CommandText = query;
cmd.ExecuteNonQuery();
cmd.CommandText = "SELECT * FROM #temp";
using (var sda = new SqlDataAdapter(cmd))
{
var ds = new DataSet();
sda.Fill(ds);
foreach (DataRow row in ds.Tables[0].Rows)
Console.WriteLine("{0} - {1}", row["ID"], row["Name"]);
}
}
}
2) FAILS: Use same command object, command parameters, execute command twice:
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
const string query = #"
CREATE TABLE #temp
([ID] INT NOT NULL, [Name] VARCHAR(20) NOT NULL)
INSERT INTO #temp VALUES(1, #username1)
INSERT INTO #temp VALUES(2, #username2)
";
cmd.CommandType = CommandType.Text;
cmd.CommandText = query;
cmd.Parameters.Add("#username1", SqlDbType.VarChar).Value ="First User";
cmd.Parameters.Add("#username2", SqlDbType.VarChar).Value ="Second User";
cmd.ExecuteNonQuery();
cmd.Parameters.Clear();
cmd.CommandText = "SELECT * FROM #temp";
using(var sda = new SqlDataAdapter(cmd))
{
var ds = new DataSet();
sda.Fill(ds);
foreach(DataRow row in ds.Tables[0].Rows)
Console.WriteLine("{0} - {1}", row["ID"], row["Name"]);
}
}
}
3) WORKS OK: Use same command object, command parameters, execute command once only:
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
const string query = #"
CREATE TABLE #temp
([ID] INT NOT NULL, [Name] VARCHAR(20) NOT NULL)
INSERT INTO #temp VALUES(1, #username1)
INSERT INTO #temp VALUES(2, #username2)
SELECT * FROM #temp
";
cmd.CommandType = CommandType.Text;
cmd.CommandText = query;
cmd.Parameters.Add("#username1", SqlDbType.VarChar).Value ="First User";
cmd.Parameters.Add("#username2", SqlDbType.VarChar).Value ="Second User";
using (var sda = new SqlDataAdapter(cmd))
{
var ds = new DataSet();
sda.Fill(ds);
foreach (DataRow row in ds.Tables[0].Rows)
Console.WriteLine("{0} - {1}", row["ID"], row["Name"]);
}
}
}
This works. Apparently, if the SqlParameters are in the step that creates the table, the table is not left behind for the next step. Once the table is created, the SqlParameters can be used in a separate step for the INSERT.
// Create temporary file dropping members from termed groups.
q = new StringBuilder(500);
q.Append("create table #ua_param ");
q.Append("([ID] int not null, fn varchar(50) not null) ");
sc = new SqlCommand(q.ToString(), db);
sc.ExecuteNonQuery();
q = new StringBuilder(500);
q.Append("insert into #ua_param values(1,#fn1) ");
q.Append("insert into #ua_param values(2,#fn2) ");
sc = new SqlCommand(q.ToString(), db);
sc.Parameters.Add(new SqlParameter("#fn1", sFn));
sc.Parameters.Add(new SqlParameter("#fn2", sFn2));
sc.ExecuteNonQuery();
q = new StringBuilder(500);
q.Append("select policy_no, name, amt_due, due_date, hic, grp, eff_dt, lis_prem, lis_grp, lis_co_pay_lvl, ");
q.Append("lep_prem, lapsed, dn_code, [filename], created_dt, created_by ");
q.Append("into #ua_temp from elig_ua_response inner join #ua_param on [filename] = fn ");
sc.Parameters.Clear();
sc.CommandText = q.ToString();
sc.CommandTimeout = 1800;
sc.ExecuteNonQuery();
Its because the temp table is just that. Temporary. You might consider doing your operations in a stored procedure.
Beyond rolling it into a stored procedure as suggested by #Daniel A White, you can look at BOL article and search for global temporary tables. Also a brief write up on Temporary Tables. Either approach should keep the temporary table alive.
I had the same problem. I tried the SeaDrive solution and it works, however my tests make me believe that the query execution "flushes" something between "ADO.NET/SQLDriver" and the MS SQL Server.
So, you need to isolate the "CREATE TABLE" statement and submit it to the database before to use it with "INSERT INTO". Composed commands joining CREATE and INSERT in one unique statement doesn't work, unless you can give up the parameters.
#TEMP tables only are accessible within the same session or SPID. So if you want to reuse it you need to reuse the connection you used to generate it.
Working example with Dapper:
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
var expected = Guid.NewGuid();
// creating the temp table with NO PARAMETERS PASSED IN is the key part.
conn.Execute("CREATE TABLE #MyTemp (ID UNIQUEIDENTIFIER NOT NULL PRIMARY KEY);");
// now that the temp table is created, you can run queries with params as
// much as you want.
conn.Execute("INSERT INTO #MyTemp (ID) VALUES (#ID)", new { ID = expected });
var actual = conn.Query<Guid>("SELECT ID FROM #MyTemp;").Single();
Assert.Equal(expected, actual); // proof it worked
}
Using a stored proc makes sense for this sort of thing.
If for some reason that's not feasible, then make sure you are using the same connection for the temp table creation as you are for the temp table selection, else the temp table won't be visible. (it might be that you have this issue randomly if you're using connection pooling.) Alternately, use a real, physical table or even a global temp table (##global_tmp vs #local_tmp), but in either case you'll need to devise a scheme/protocol such that multiple processes aren't trying to create/delete/write to that table.
Again, I'll stress that a stored proc would be a good route, if possible.