This question already has answers here:
How to get last inserted id?
(16 answers)
Closed 7 years ago.
I have an C# application utilizing a SQL Server database. After inserting a row, I need the ID of the row that was created. I tried select max(id) from mytable, but the result from this is incorrect if another user has inserted a row in the meantime.
This is my code:
public string ConnectionString = #"Data Source=.;Initial Catalog=n_hesabdata;Integrated Security=True";
public void ExecuteNonQuery(string CommandText)
{
try {
connection.ConnectionString = ConnectionString;
connection.Open();
command.CommandText = CommandText;
command.Connection = connection;
command.ExecuteNonQuery();
command.Dispose();
connection.Close();
} catch (Exception error) {
connection.Close();
}
}
ExecuteNonQuery("insert into Orders (Ctm_phone, Ctm_FullName) VALUES (" + Ctm_phone.text + ", N'" + Ctm_name.Text + "')");
How do I get the ID of the freshly inserted row?
You should change your code to something like this
ConnectionString = #"Data Source=.;Initial Catalog=n_hesabdata;Integrated Security=True";
public int ExecuteInsertWithIdentity(string CommandText, List<SqlParameter> prms)
{
using(SqlConnection connection = new SqlConnection(ConnectionString))
using(SqlCommand cmd = new SqlCommand(CommandText +
";SELECT SCOPE_IDENTITY();", connection))
{
connection.Open();
if(prms != null && prms.Count > 0)
cmd.Parameters.AddRange(prms.ToArray());
int lastID = Convert.ToInt32(cmd.ExecuteScalar());
return lastID;
}
}
....
List<SqlParameter> prms = new List<SqlParameter>();
SqlParameter p1 = new SqlParameter() {ParameterName = "#phone",
SqlDbType = SqlDbType.NVarChar,
Value = Ctm_phone.text};
SqlParameter p2 = new SqlParameter() {ParameterName = "#Name",
SqlDbType = SqlDbType.NVarChar,
Value = Ctm_name.Text};
prms.AddRange(new SqlParameter[] {p1, p2});
int lastOrderID = ExecuteInsertWithIdentity(#"insert into Orders
(Ctm_phone,Ctm_FullName)
VALUES (#phone, #name)", prms);
In this way, the code that executes the query requires a parameter collection of values. This removes the possibility of Sql Injection. Now the problem of getting back the last ID inserted by the database engine in your Orders table is simply resolved appending the SELECT SCOPE_IDENTITY() to your command.
The SqlClient SqlCommand class is capable to execute multiple statements in a single trip to the database and returns the last statement. In this case is the value of the IDENTITY column of the row added to your Orders table from your connection (and not from others connections)
Normally you should insert new entries using the entitymanager. When the id attribute it annotated as #Id it should receive the next free value upon MERGEing the object into the table when the value was NULL before.
But when you really want to INSERT explicitly using SQL, then you should split the INSERT into 1.) getting a new id from the sequence 2.) writing then the data into the table (with the sequence nimber from step 1.)
Related
I've created a UWP app with a sqlite database using the Microsoft.Data.Sqlite library. After inserting a new row, I need to know the autoincrement value assigned to the new row. The code below uses the additional query "select last_insert_rowid()", but is there a better way to obtain this value?
public static int AddMovie(Movie movie)
{
string dbpath = Path.Combine(ApplicationData.Current.LocalFolder.Path, DB_FILENAME);
using (SqliteConnection db = new SqliteConnection($"Filename={dbpath}"))
{
db.Open();
SqliteCommand cmd = new SqliteCommand();
cmd.Connection = db;
cmd.CommandText = "INSERT INTO Movie VALUES (NULL, #Title, #Rating)";
cmd.Parameters.AddWithValue("#Title", movie.Title);
cmd.Parameters.AddWithValue("#Rating", movie.Rating);
cmd.ExecuteReader();
// Is there a better way to get this value?
cmd = new SqliteCommand("SELECT last_insert_rowid()", db);
SqliteDataReader query = cmd.ExecuteReader();
var newId = 0;
if (query.Read())
{
newId = query.GetInt32(0);
}
db.Close();
return newId;
}
}
You could combine both queries into a single command, and instead of calling cmd.ExecuteReader() call cmd.ExecuteScalar() and convert the returned value to int:
public static long AddMovie(Movie movie)
{
string dbpath = Path.Combine(ApplicationData.Current.LocalFolder.Path, DB_FILENAME);
using (SqliteConnection db = new SqliteConnection($"Filename={dbpath}"))
{
db.Open();
SqliteCommand cmd = new SqliteCommand();
cmd.Connection = db;
cmd.CommandText = "INSERT INTO Movie VALUES (NULL, #Title, #Rating); SELECT last_insert_rowid();";
cmd.Parameters.AddWithValue("#Title", movie.Title);
cmd.Parameters.AddWithValue("#Rating", movie.Rating);
return (long)cmd.ExecuteScalar();
}
}
Note: Raycoon's answer points out some drawbacks of this solution.
Your initial way of retrieving the rowid (by separating the insert and select command) is more appropriate and more "secure". Combining the INSERT and SELECT commands will ignore unique constraint violations when inserting data in your table (in case you have a unique index). You will simply get "0" as new rowid - and no exception will be thrown!
Using separate commands results in SqliteExceptions when data cannot properly be inserted in your table. Therefore I wouldn't use the second, "correct" approach. Better use your initial code - but ExecuteNonQuery for INSERT and ExecuteScalar for SELECT last_insert_rowid().
I am inserting a data row into my SQL Server database and then I want to query the data to get the unique identifier from the inserted row but my SqlDataReader is returning an empty dataset. I am thinking it maybe that the transaction hasn't been committed or something like that but I am not sure. I do not get an error.
Here is my code:
try
{
strQuery = "INSERT INTO clientnames VALUES(NEWID(),'" + txtACLastName.Text + "','" + txtACFirstName.Text + "'," + 1 + ")";
using (SqlCommand sqlInsertCmd = new SqlCommand(strQuery, sqlConn))
{
intQueryResult = sqlInsertCmd.ExecuteNonQuery();
if (intQueryResult == 0)
{
blnSuccess = false;
goto InsertClientNamesError;
}
else
{
blnSuccess = true;
}
sqlInsertCmd.Dispose();
}
if (blnSuccess)
{
strQuery = "select clientID from clientnames where firstname = '" + txtACFirstName.Text + "' and lastname = '" + txtACLastName.Text + "'";
using (SqlCommand sqlSelectCmd = new SqlCommand(strQuery, sqlConn))
{
SqlDataReader sqlDataRead = sqlSelectCmd.ExecuteReader();
while (sqlDataRead.Read())
{
strClientID = sqlDataRead.ToString();
}
sqlDataRead.Close();
sqlSelectCmd.Dispose();
}
}
}
catch (Exception exQuery)
{
System.Windows.MessageBox.Show("InsertClientNames: Error, " + exQuery.Message + ", has occurred.");
}
You are not getting the desired result because perhaps the SqlConnection is not opened explicitly (just a guess hard to tell without having full code). But this link shows you how to read from reader --> https://msdn.microsoft.com/en-us/library/haa3afyz(v=vs.110).aspx
But I suggest that you Please do not do it this way. Reason is you are making Two round trips to the DB Server when only one would have done the job for you IF you were using stored procedures. Also you are exposing yourselves to SQL Injection attacks as you are not parameterizing your queries.
Stored procedure:
CREATE PROCEDURE dbo.INS_clientnames
(
#FirstName varchar(100),
#LastName varchar(100),
#NewID int out
)
AS
BEGIN
Declare #Err int
set #NewID = NewID() -- Get the New ID and store it in the variable ( #NewID ) that the SP will return back to the caller
INSERT INTO clientnames values (#NewID , #FirstName , #LastName)
SET #Err = ##ERROR
IF #Error <> 0 -- Check If there was an error
Begin
SET #NewID = -1 -- Indicates that there was an error. You could log this into a Log Table with further details like error id and name.
END
RETURN
END
C# code to execute the above stored procedure and get the NewID:
using(SqlConnection conn = new SqlConnection(connectionString ))
{
using(SqlCommand cmd = new SqlCommand("dbo.INS_clientnames", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
// set up the parameters that the Stored Procedure expects
cmd.Parameters.Add("#FirstName", SqlDbType.VarChar, 100);
cmd.Parameters.Add("#LastName" , SqlDbType.VarChar, 100);
cmd.Parameters.Add("#NewId" , SqlDbType.Int).Direction = ParameterDirection.Output;
// set parameter values that your code will send to the SP as parameter values
cmd.Parameters["#FirstName"].Value = txtACFirstName.Text ;
cmd.Parameters["#LastName"].Value = txtACLastName.Text ;
// open connection and execute stored procedure
conn.Open();
cmd.ExecuteNonQuery();
// read output value from #NewId
int NewID = Convert.ToInt32(cmd.Parameters["#NewId"].Value);
}
}
Add the following line to your stored procedure that inserts the record
SELECT SCOPE_IDENTITY()
This will return the last identity value inserted in that table.
And use cmd.ExecuteScalar() instead of ExecuteNonQuery()
ExecuteScalar() executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored. [More info][1]
I see two approaches to do this:
either you generate the new GUID on the client side in your C# code and pass it into the query - then you already know what the new id is going to be, so you don't need to do a second query to get it:
you create your GUID on the server side and return it to the caller using the OUTPUT clause in your query
Approach #1:
// define connection string and query
string connStr = "--your connection string here--";
string query = "INSERT INTO dbo.Clients(ClientID, FirstName, LastName) VALUES(#ID, #First, #Last);";
using (SqlConnection conn = new SqlConnection(connStr))
using (SqlCommand cmd = new SqlCommand(query, conn))
{
// create the GUID in C# - this is the ID - no need to go get it again - this *IS* the id
Guid id = Guid.NewGuid();
// set the parameters
cmd.Parameters.Add("#ID", SqlDbType.UniqueIdentifier).Value = id;
cmd.Parameters.Add("#First", SqlDbType.VarChar, 50).Value = "Peter";
cmd.Parameters.Add("#Last", SqlDbType.VarChar, 50).Value = "Miller";
// open connection, execute query, close connection
conn.Open();
cmd.ExecuteNonQuery();
conn.Close();
}
Approach #2:
// define connection string and query
string connStr = "--your connection string here--";
// query has an "OUTPUT" clause to return a newly inserted piece of data
// back to the caller, just as if a SELECT had been issued
string query = "INSERT INTO dbo.Clients(ClientID, FirstName, LastName) OUTPUT Inserted.ClientID VALUES(NEWID(), #First, #Last);";
using (SqlConnection conn = new SqlConnection(connStr))
using (SqlCommand cmd = new SqlCommand(query, conn))
{
// set the parameters - note: you do *NOT* send in a GUID value - the NEWID() will create one automatically, on the server
cmd.Parameters.Add("#First", SqlDbType.VarChar, 50).Value = "Frank";
cmd.Parameters.Add("#Last", SqlDbType.VarChar, 50).Value = "Brown";
// open connection
conn.Open();
// execute query and get back one row, one column - the value in the "OUTPUT" clause
object output = cmd.ExecuteScalar();
Guid newId;
if (Guid.TryParse(output.ToString(), out newId))
{
//
}
conn.Close();
}
I have 2 table in an access database
now I want to select from one table and insert them into another one.
this is my code but it shows an exception in line Cmd.ExecuteNonQuery();
{"Syntax error (missing operator) in query expression 'System.Object[]'."}
the code is :
public static void SetSelectedFeedIntoDB(Form2 frm2)
{
string StrCon = System.Configuration.ConfigurationManager.ConnectionStrings["FeedLibraryConnectionString"].ConnectionString;
OleDbConnection Connection = new OleDbConnection(StrCon);
OleDbDataAdapter DataA = new OleDbDataAdapter("Select * from FeedLibrary where ID=" + frm2.FeedSelectListBox.SelectedValue, Connection);
DataTable DTable = new DataTable();
DataA.Fill(DTable);
OleDbCommand Cmd = new OleDbCommand();
Cmd.Connection = Connection;
Connection.Open();
foreach (DataRow DR in DTable.Rows)
{
Cmd.CommandText = "insert into SelectedFeeds Values(" + DR.ItemArray + ")";
Cmd.ExecuteNonQuery();
}
Connection.Close();
}
what should I do to fix this?
Your error is caused by the fact that you are concatenating the ItemArray property of a DataRow to a string. In this case the ItemArray (that is an instance of an object[]) has no method that automatically produces a string from its values and thus returns the class name as a string "object[]" but of course this produces the meaningless sql string
"insert into SelectedFeeds Values(object[])";
But you could simply build a SELECT .... INTO statement that will do everything for you without using DataTables and Adapters
string cmdText = #"SELECT FeedLibrary.* INTO [SelectedFeeds]
FROM FeedLibrary
where ID=#id";
using(OleDbConnection Connection = new OleDbConnection(StrCon))
using(OleDbCommand cmd = new OleDbCommand(cmdText, Connection))
{
Connection.Open();
cmd.Parameters.Add("#id", OleDbType.Integer).Value = Convert.ToInt32( frm2.FeedSelectListBox.SelectedValue);
cmd.ExecuteNonQuery();
}
However, the SELECT ... INTO statement creates the target table but gives error if the target table already exists. To solve this problem we need to discover if the target exists. If it doesn't exist we use the first SELECT ... INTO query, otherwise we use a INSERT INTO ..... SELECT
// First query, this creates the target SelectedFeeds but fail if it exists
string createText = #"SELECT FeedLibrary.* INTO [SelectedFeeds]
FROM FeedLibrary
where ID=#id";
// Second query, it appends to SelectedFeeds but it should exists
string appendText = #"INSERT INTO SelectedFeeds
SELECT * FROM FeedLibrary
WHERE FeedLibrary.ID=#id";
using(OleDbConnection Connection = new OleDbConnection(StrCon))
using(OleDbCommand cmd = new OleDbCommand("", Connection))
{
Connection.Open();
// Get info about the SelectedFeeds table....
var schema = Connection.GetSchema("Tables",
new string[] { null, null, "SelectedFeeds", null});
// Choose which command to execute....
cmd.CommandText = schema.Rows.Count > 0 ? appendText : createText;
// Parameter #id is the same for both queries
cmd.Parameters.Add("#id", OleDbType.Integer).Value = Convert.ToInt32( frm2.FeedSelectListBox.SelectedValue);
cmd.ExecuteNonQuery();
}
Here we have two different queries, the first one create the SelectedFeeds table as before, the second one appends into that table.
To discover if the target table has already been created I call Connection.GetSchema to retrieve a datatable (schema) where there is a row if the table SelectedFeeds exists or no row if there is no such table.
At this point I set the OleDbCommand with the correct statement to execute.
This is our code to prevent the same data from being added into SQL from our C# program but only the first same data will not be added in. The remaining ones adds the same data into SQL despite our prevention in our C# program. Can somebody help us troubleshoot?
in order not to duplicate data in database usually you set some constraints to your database. By having a unique field in database you can prevent multiple addition to your db.
Currently you are also fetching data from db to check if it exist already and that creates extra cost, just manipulate the design of db so that it won't accept the same column input twice
Count the value of data that is inserted
string constr = ConfigurationManager.ConnectionStrings["connstr"].ToString();
SqlConnection con = new SqlConnection(constr);
string sql1 = "SELECT COUNT (client_id) FROM client WHERE client_id = '" + txtid.Text + "' ";
SqlCommand cmd = new SqlCommand(sql1, con);
con.Open();
int temp = Convert.ToInt32(cmd.ExecuteScalar().ToString());
if (temp >0)
{
//show error message
}
You could check for the record you want to add, and if it doesn't exists, then add it to the table:
SqlConnection _cnt = new SqlConnection();
_cnt.ConnectionString = "Your Connection String";
SqlCommand _cmd = new SqlCommand();
_cmd.Connection = _cnt;
_cmd.CommandType = System.Data.CommandType.Text;
_cmd.CommandText = "SELECT id FROM myTable where Category=#Name";
_cmd.Parameters.Add("#Name", string);
_cmd.Parameters["#Name"].Value = newCatTitle;
_cnt.Open();
var idTemp = _cmd.ExecuteScalar();
_cmd.Dispose();
_cnt.Close();
_cnt.Dispose();
if (idTemp == null)
{
//Insert into table
}
else
{
//Message it already exists
}
runtime error 'there is already an open datareader associated with this command which must be closed first'
objCommand = new SqlCommand("SELECT field1, field2 FROM sourcetable", objConn);
objDataReader = objCommand.ExecuteReader();
while (objDataReader.Read())
{
objInsertCommand = new SqlCommand("INSERT INTO tablename (field1, field2) VALUES (3, '" + objDataReader[0] + "')", objConn);
objInsertCommand.ExecuteNonQuery();//Here is the error
}
objDataReader.Close();
I cannot define any stored procedure here.
Any help would we appreciated.
No need to do all that, just turn on MARS and your problem will get solved. In your connection string just add MultipleActiveResultSets=True;
You can't perform an action on that connection while it's still working on reading the contents of a data reader - the error is pretty descriptive.
Your alternatives are:
1) Retrieve all your data first, either with a DataSet or use the reader to populate some other collection, then run them all at once after the initial select is done.
2) Use a different connection for your insert statements.
How about pulling the data into a DataSet via Fill and then iterate through that to perform your insert via NonQuery?
IDbDataAdapter da;
IDbCommand selectCommand = connection.CreateCommand();
selectCommand.CommandType = CommandType.Text;
selectCommand.CommandText = "SELECT field1, field2 FROM sourcetable";
connection.Open();
DataSet selectResults= new DataSet();
da.Fill(selectResults); // get dataset
selectCommand.Dispose();
IDbCommand insertCommand;
foreach(DataRow row in selectResults.Tables[0].Rows)
{
insertCommand = connection.CreateCommand();
insertCommand.CommandType = CommandType.Text;
insertCommand.CommandText = "INSERT INTO tablename (field1, field2) VALUES (3, '" + row["columnName"].ToString() + "'";
}
insertCommand.Dispose();
connection.Close();
Your best bet would be to read the information you need into a list and then iterating the list to perform your inserts like so:
List<String> values = new List<String>();
using(SqlCommand objCommand = new SqlCommand("SELECT field1, field2 FROM sourcetable", objConn)) {
using(SqlDataReader objDataReader = objCommand.ExecuteReader()) {
while(objDataReader.Read()) {
values.Add(objDataReader[0].ToString());
}
}
}
foreach(String value in values) {
using(SqlCommand objInsertCommand = new SqlCommand("INSERT INTO tablename (field1, field2) VALUES (3, '" + value + "')", objConn)) {
objInsertCommand.ExecuteNonQuery();
}
}
INSERT INTO tablename (field1, field2)
SELECT 3, field1 FROM sourcetable
A single SQL statement instead of one per insert. Not sure if this will work for your real-life problem, but for the example you provided, this is a much better query than doing them one at a time.
On a side note, make sure your code uses parameterized queries instead of accepting strings as-is inside the SQL statement - your sample is open to SQL injection.
Several suggestions have been given which work great, along with recommendations for improving the implementation. I hit the MARS limit due to existing code not cleaning up a reader so I wanted to put together a more respectable sample:
const string connectionString = #"server=.\sqlexpress;database=adventureworkslt;integrated security=true";
const bool useMARS = false;
using (var objConn = new System.Data.SqlClient.SqlConnection(connectionString + (useMARS ? ";MultipleActiveResultSets=True" : String.Empty)))
using (var objInsertConn = useMARS ? null : new System.Data.SqlClient.SqlConnection(connectionString))
{
objConn.Open();
if (objInsertConn != null)
{
objInsertConn.Open();
}
using (var testCmd = new System.Data.SqlClient.SqlCommand())
{
testCmd.Connection = objConn;
testCmd.CommandText = #"if not exists(select 1 from information_schema.tables where table_name = 'sourcetable')
begin
create table sourcetable (field1 int, field2 varchar(5))
insert into sourcetable values (1, 'one')
create table tablename (field1 int, field2 varchar(5))
end";
testCmd.ExecuteNonQuery();
}
using (var objCommand = new System.Data.SqlClient.SqlCommand("SELECT field1, field2 FROM sourcetable", objConn))
using (var objDataReader = objCommand.ExecuteReader())
using (var objInsertCommand = new System.Data.SqlClient.SqlCommand("INSERT INTO tablename (field1, field2) VALUES (3, #field2)", objInsertConn ?? objConn))
{
objInsertCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("field2", String.Empty));
while (objDataReader.Read())
{
objInsertCommand.Parameters[0].Value = objDataReader[0];
objInsertCommand.ExecuteNonQuery();
}
}
}
Option 1:
Must execute query and load data before running another query.
Option 2:
Add MultipleActiveResultSets=true to the provider part of your connection string. See the example below:
<add name="DbContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=dbName;Persist Security Info=True;User ID=userName;Password=password;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
What version of SQL Server are you using? The problem might be with this:
(from http://msdn.microsoft.com/en-us/library/9kcbe65k.aspx)
When you use versions of SQL Server before SQL Server 2005, while the SqlDataReader is being used, the associated SqlConnection is busy serving the SqlDataReader. While in this state, no other operations can be performed on the SqlConnection other than closing it. This is the case until the Close method of the SqlDataReader is called.
So, if this is what's causing your problem, you should first read all the data, then close the SqlDataReader, and only after that execute your inserts.
Something like:
objCommand = new SqlCommand("SELECT field1, field2 FROM sourcetable", objConn);
objDataReader = objCommand.ExecuteReader();
List<object> values = new List<object>();
while (objDataReader.Read())
{
values.Add(objDataReader[0]);
}
objDataReader.Close();
foreach (object value in values)
{
objInsertCommand = new SqlCommand("INSERT INTO tablename (field1, field2) VALUES (3, '" + value + "')", objConn);
objInsertCommand.ExecuteNonQuery();
}
Adding this to connection string should fix the problem.
MultipleActiveResultSets=true
Try something like this:
//Add a second connection based on the first one
SqlConnection objConn2= new SqlConnection(objConn.connectionString))
SqlCommand objInsertCommand= new SqlCommand();
objInsertCommand.CommandType = CommandType.Text;
objInsertCommand.Connection = objConn2;
while (objDataReader.Read())
{
objInsertCommand.CommandText = "INSERT INTO tablename (field1, field2) VALUES (3, '" + objDataReader[0] + "')";
objInsertCommand.ExecuteNonQuery();
}
Best Solution:
There is only problem with your "CommandText" value. Let it be SP or normal Sql Query.
Check 1: The parameter value which you are passing in your Sql Query
is not changing and going same again and again in your ExecuteReader.
Check 2: Sql Query string is wrongly formed.
Check 3: Please create simplest code as follows.
string ID = "C8CA7EE2";
string myQuery = "select * from ContactBase where contactid=" + "'" + ID + "'";
string connectionString = ConfigurationManager.ConnectionStrings["CRM_SQL_CONN_UAT"].ToString();
SqlConnection con = new SqlConnection(connectionString);
con.Open();
SqlCommand cmd = new SqlCommand(myQuery, con);
DataTable dt = new DataTable();
dt.Load(cmd.ExecuteReader());
con.Close();
In order for it to be disposed easily i use the following coding-template :
`using (SqlConnection connection = new SqlConnection("your connection string"))
{
connection.Open();
using (SqlCommand cmd = connection.CreateCommand())
{
cmd.CommandText = "Select * from SomeTable";
using (SqlDataReader reader = cmd.ExecuteReader())
{
if(reader.HasRows)
{
while(reader.Read()){
// assuming that we've a 1-column(Id) table
int id = int.Parse(reader[0].ToString());
}
}
}
}
connection.Close()
}`