Inserting a value into a sql database by using a sql query - c#

This is my first row where I create a track (THIS WORKS)
String TrackAdd = "INSERT INTO Track (Titel) VALUES (#Titel)";
using (SqlCommand sqlCmd = new SqlCommand(TrackAdd, connection))
{
sqlCmd.Parameters.AddWithValue("#Titel", textBoxTitel.Text);
sqlCmd.ExecuteNonQuery();
}
Then I want to use the ID which was just created for this Track, I need it so I can link it to something else (in this case a genre. I have specified the names for genres in a different table and need their IDs)
I'm now trying this but its not working and I don't really know what to do.
using (var connection = Database.connection)
{
String Track1Genre = "INSERT INTO TrackGenre (TrackId, GenreId) VALUES (#TrackId, #GenreId)";
string Genre = listBoxGenre.GetItemText(listBoxGenre.SelectedItem);
using (SqlCommand sqlCmd = new SqlCommand(Track1Genre, connection))
{
sqlCmd.Parameters.AddWithValue("#TrackId", "Select Id from Track WHERE Titel = textBoxTitel.Text");
sqlCmd.Parameters.AddWithValue("#GenreId", "Select Id from Genre where Genre = Genre");
sqlCmd.ExecuteNonQuery();
}
}
NOTE: There's nothing wrong with the connection or anything, I just need help how to get the ID's out of the database and insert them into a different table

Two ways to do it:
Output Inserted.Id
INSERT INTO Track (Titel) VALUES output INSERTED.ID (#Titel)
and in C#, use:
int lastId =(int)cmd.ExecuteScalar();
Identity_Insert On
SET IDENTITY_INSERT dbo.Track ON;
INSERT INTO Track (Id, Titel) VALUES (1, #Titel)
SET IDENTITY_INSERT dbo.Track OFF;
In this method you already know what Id you are inserting so you can just use this to update your TrackGenre table. But, yes, you have to track your Ids or may be before executing check for last id using select max(id) from Track

You're close. You can't inject SQL the way you intended. You have to move that your insert statement so the query will do a select for each value based on your parameters.
Let's try to workout what the SQL query that gets executed will look like, when you start with this:
String Track1Genre = "INSERT INTO TrackGenre (TrackId, GenreId) VALUES (#TrackId, #GenreId)";
sqlCmd.Parameters.AddWithValue("#TrackId", "Select Id from Track WHERE Titel = textBoxTitel.Text");
sqlCmd.Parameters.AddWithValue("#GenreId", "Select Id from Genre where Genre = Genre");
Remember that whatever (string) value is set on a parameter will be used, without any evaluation. So the above statement will lead to this sql being executed:
INSERT INTO TrackGenre (TrackId, GenreId)
VALUES ('Select Id from Track WHERE Titel = textBoxTitel.Text',
'Select Id from Genre where Genre = Genre');
So it nicely did prevent a SqlInjection attack but it didn't return Id's to tables Track and Genre either, in fact it would bark about a conversion failed to data type int.
Instead you can pass the selection parameters as is and then use those in the queries to get the id's of the rows you're interested in.
Staying as close as possible to what you currently have, this would work:
using (var connection = Database.connection)
{
String Track1Genre = #"
INSERT
INTO TrackGenre(TrackId, GenreId)
VALUES (
(SELECT id FROM track WHERE titel = #titel), /* do a select for the ID */
(SELECT id FROM genre WHERE genre = #genre))";
string Genre = listBoxGenre.GetItemText(listBoxGenre.SelectedItem);
using (SqlCommand sqlCmd = new SqlCommand(Track1Genre, connection))
{
// caution, better use Add, see the note
sqlCmd.Parameters.AddWithValue("#titel", textBoxTitel.Text);
sqlCmd.Parameters.AddWithValue("#Genre", Genre);
sqlCmd.ExecuteNonQuery();
}
}
Keep in mind that the insert statement will fail if you have tracks with the same title, or genre's with same name. But that is up to you to handle or prevent from happening upstream.
If you want to be 100% sure that the Id you get from the insert of Track, refer to the Output Inserted.Id option in the answer of Sunil. You'll have to bring the lastid variable over to the code I've shown here, replacing the first parameter with it (and adapt the insert query accordingly).
Note
There is a potential issue with the use of AddWithValue as explained in this blogpost from JCoohorn: https://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/
It is better to prevent the SqlParameter class guess your type wrong. Consider to use
sqlCmd.Parameters.Add("#titel", SqlDbType.NVarChar, 250).Value = textBoxTitel.Text;
instead.
If you want to experiment with the insert query first to get the feel what it is doing, fork this SEDE query.

I dont know if it is a typo or genuine mistake which should be corrected
this line from your code
sqlCmd.Parameters.AddWithValue("#TrackId", "Select Id from Track WHERE Titel = textBoxTitel.Text");
should be written like this
sqlCmd.Parameters.AddWithValue("#TrackId", "Select Id from Track WHERE Titel = " + textBoxTitel.Text);
same problem is with Genre tooo. if your code written is correct then your query is actually searching for 'textBoxTitel.Text' instead of trackid

Related

Return ID of newly inserted row on a PostgreSQL database using C# and Npgsql?

I'm building a WinForms project in C# using a PostgreSQL database and the Npgsql framework.
For inserting a record, I need to return the ID of the new record. This SO question says to add SELECT SCOPE_IDENTITY() to the query string passed to cmd. So my query string looks like this:
string insertString = "INSERT INTO sometable (company_name, category, old_value, old_desc, new_value, new_desc, reference1, reference2) VALUES (#comp, #cat, #oldValue, #oldDesc, #newValue, #newDesc, #ref1, #ref2); SELECT SCOPE_IDENTITY();";
and then get the ID with something like
int modified = cmd.ExecuteNonQuery();
But that's likely SQL Server-specific. If I use that method, I get an exception at the above line saying, "fuction scope_identity() does not exist".
I wasn't able to find anything that seemed to address this on the Npgsql documentation.
Per the linked SO question and Denis' suggestions I've tried adding both
RETURNING id;
and
CURRVAL(pg_get_serial_sequence('my_tbl_name','id_col_name'))
to the query string, replacing SELECT SCOPE_IDENTITY(); with those statements in the code above. In both cases they work as intended in DBeaver on an insert, but in my C# code in my WinForm project, modified was set to "1".
NOTE: I re-titled the question and added more information about what I've done.
Add "returning idcolumn" to the end of the sql query, then run the command with the ExecuteScalar() method instead of ExecuteNonQuery(). It should return with an int.
string insert = "insert into table1 (col1) values (something) returning idcol";
int id = cmd.ExecuteScalar();
All the comments above were almost nearly spot on and got me to a solution but didn't exactly wrap it in a bow -- so I thought i'd post my implementation that works (with silly fake example tables of course).
private int? InsertNameIntoNamesTable(string name)
{
int? id = null;
using (var dbcon = new NpgsqlConnection(_connectionString))
{
dbcon.Open();
StringBuilder sb = new StringBuilder();
var sql = $#"
insert into names_table
(name)
values
({name})
returning id;
";
sb.Append(sql);
using (var cmd = new NpgsqlCommand(sql, dbcon))
{
id = (int)cmd.ExecuteScalar();
}
dbcon.Close();
}
return id;
}

Adding (the latest added Id + 1) when creating a new entry in the database using SQL query

Note: I am aware that this question has been asked before, and I am sorry for such a dumb re-post. However, none of the solutions that I've found actually helped me. I might have looked in the wrong place - excuse me for that!
Right, so I am trying to add a new entry in the Releases table in my database. The following shows how my code looks like.
[HttpPost]
public IActionResult Create(Release release)
{
var userId = this.User.FindFirstValue(ClaimTypes.NameIdentifier);
string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
using (SqlConnection connection = new SqlConnection(connectionString))
{
string sql = $"Insert Into Releases (Id, Name, StartDate, EndDate, OwnerId) Values ('(SELECT MAX(Id) + 1 FROM dbo.Releases)', '{release.Name}','{release.StartDate}','{release.EndDate}','{userId}')";
using (SqlCommand command = new SqlCommand(sql, connection))
{
command.CommandType = CommandType.Text;
connection.Open();
command.ExecuteNonQuery();
connection.Close();
}
}
return View();
}
In my code snippet, I use SELECT MAX(Id) + 1 in order to insert the new entry with the value of column Id + 1 the latest entry. This has worked before for me, but now, for some reason, I am getting the following error:
System.Data.SqlClient.SqlException: 'Conversion failed when converting the varchar value '(SELECT MAX(Id) + 1 FROM dbo.Releases)' to data type int.'
While this solution might not be quite ideal - I am looking for a proper solution to my problem.
In answer to your question '(SELECT MAX(Id) + 1 FROM dbo.Releases)' should not be enclosed in quotes; (SELECT MAX(Id) + 1 FROM dbo.Releases) would return an integer value that is required by your id column.
However, you should really be using an auto-increment column for your id, as simultaneous requests could try to insert the same id twice.
You should also be using parameterised queries rather than string interpolation, as this is opening up your database to SQL injection.

how to get the ID from two different select statement on single query?

i am having hard time running this code, i have two select statement on a single query with different "statement", all i want is to insert it on a database just the "ID" only and here is the code,
sqc.Open();
cmd = sqc.CreateCommand();
cmd.CommandText = "Select DepID from UserDept where Department =#depe and Position =#posi union all Select SalaryID from Salaries where Gradenumber =#gn and StepNumber =#sn";
cmd.Parameters.AddWithValue("#depe", cmbDept.SelectedItem.ToString());
cmd.Parameters.AddWithValue("#posi", cmbPos.SelectedItem.ToString());
cmd.Parameters.AddWithValue("#gn", cmbgrade.SelectedItem.ToString());
cmd.Parameters.AddWithValue("#sn", cmbstep.SelectedItem.ToString());
sda = new SqlDataAdapter(cmd);
dt = new DataTable();
sda.Fill(dt);
foreach (DataRow rr in dt.Rows)
{
cmd = sqc.CreateCommand();
cmd.CommandText = "insert into UserInfo2 (AccountID,DeptID,SalaryGrade,DateEmp,DateHired) Values (#idd,#dep,#sal,#emp,#hired)";
cmd.Parameters.AddWithValue("#idd", "crap");
cmd.Parameters.AddWithValue("#dep", rr[0].ToString());
cmd.Parameters.AddWithValue("#sal", rr[1].ToString());
cmd.Parameters.AddWithValue("#emp", dtpEmployed.Value.ToShortDateString());
cmd.Parameters.AddWithValue("#hired", dtpPromoted.Value.ToShortDateString());
cmd.ExecuteNonQuery();
MessageBox.Show("Information successfully saved!", "Information!", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
sqc.Close();
what im try to do is that if column name DEPID the subitem must be dep232 and the SALARYID is sal 232
i try this code on INSERT statement
cmd.Parameters.AddWithValue("#dep", rr[0].ToString());
cmd.Parameters.AddWithValue("#sal", rr[0].ToString());
the output is column name DEPID it has subitem sal243, it should be on SALARYID only
and there is another data which is DEPID is dep243 but the salaryID has dep243 also.
Ok, so you have 2 queries and they're giving you the dept ID and the salary grade.. if either one of these two queries only returns one row then you can get away with doing this:
cmd.CommandText = #"select * from
(
Select DepID
from UserDept
where Department =#depe and Position =#posi
) a cross join
(
Select SalaryID
from Salaries
where Gradenumber =#gn and StepNumber =#sn
) b";
The # at the start of the string lets it run over multiple lines, handy for formatting and making an sql easier to read.
If both your queries return more than one row, do not do this. You'll have to inner join the two tables together. There isn't enough information in your question to be sure how to do that; the relationship between the tables is not apparent
There are good reasons to avoid using AddWithValue, but that's a blog post (you can search for it) rather tHan a one liner I can type here. Caution though; you basically call to string on all your combo selected items without really considering what is the type of data in the database. If in the database things like gradenumber is stored as a number, you should at least be calling addwithvalue and passing a value that is a number (i.e. Convert.ToInt32(combo.selecteditem) ) rather than a string ( your current .ToString()ing method)

SQL Server database last_rowID

I have a C# app with have to use SQL Server database with 1 table (All_Data) and 5 columns (ID, Name, Surename, Age,Location)
Before inserting a new row how can I find out or get the value of the last ID in the table
I have a following code but it,a not work well
string query = "SELECT MAX(ID) FROM All_Data";
SqlCommand comSelect;
comSelect = new SqlCommand(query, connection);
int ID = (int)comSelect.ExecuteScalar();
ERROR message:
ExecuteScalar: Connection property has not been initialized
Please help me
First, from your code it is not clear what is the value of the variable connection.
From the error message it seems that you don't have initialized this variable and thus you get the error. (connection = new SqlConnection(....);)
However, this is not the correct way to handle this scenario.
You need to make the ID column an IDENTITY column and then don't try to retrieve its value before executing any INSERT.
An IDENTITY column receives its value directly from the database when there is a new record to insert. And letting the database code work on this data it is the best option if you want to be safe from concurrency issues.
If you need to retrieve the ID value after an INSERT query because you need it as a Foreign Key in other tables or for your own code, then you could simply use the T-SQL command
SELECT SCOPE_IDENTITY()
For example, suppose you have to insert a record in that table, and you want to know the IDENTITY value assigned to the ID column
string query = #"INSERT INTO All_Data(Name,Surename,Age,Location)
VALUES(#name, #surname, #age, #loc);
SELECT SCOPE_IDENTITY()";
using(SqlConnection connection = new SqlConnection(connectionString))
using(SqlCommand cmd = new SqlCommand(query, connection))
{
connection.Open();
cmd.Parameters.AddWithValue("#name", yourNameValue);
.... other parameters ...
int newID = Convert.ToInt32(cmd.ExecuteScalar());
}
As you can see, this code doesn't try to pass a value for the ID column. It pass just the other fields with a parameterized query. But at the end of the first query there is a call to SELECT SCOPE_IDENTITY() and this returns whatever value the database has assigned to the column ID (of course you should have set the IDENTITY property on the field).
This will work correctly in multiuser and concurrent scenario
The error fires when the command doesn't have a connection. Please check connection is open.
Error saysExecuteScalar: Connectio property has not been initialized
double Check your connection string whether it is defined properly. You can check here to know how to define connection string.
you have not opened connection so open it before use :
comSelect = new SqlCommand(query, connection);
connection.Open();
int ID = (int)comSelect.ExecuteScalar();
connection.Close();

Inserting a List<> into SQL Server table

I have an entity Report whose values I want to insert into a database table. The following attributes of Report have to be inserted:
reportID - int
RoleID - int
Created_BY = SYSTEM(default)
CURRENT_TIMESTAMP
Now the problem is with the 2nd attribute. I have a report with the LIST<ROLES> attributes. ROLES is a well defined entity which has an ID and a NAME. From this list I have to extract every role and insert each role's ID into the table.
So my query presently looks as below :
INSERT INTO REPORT_MARJORIE_ROLE(REPORT_ID, ROLE_ID, CREATED_BY, CREATED)
VALUES({0}, {1}, 'SYSTEM', CURRENT_TIMESTAMP)
The C# code from where I am parsing these values is as follows :
try
{
StringBuilder _objSQL = new StringBuilder();
_objSQL.AppendFormat(Queries.Report.ReportQueries.ADD_NEW_ROLES, report.ID, "report.MarjorieRoles.Add(MarjorieRole"));
_objDBWriteConnection.ExecuteQuery(_objSQL.ToString());
_objDBWriteConnection.Commit();
_IsRolesAdded = true;
}
So please guide me how to add roles from C# function
I'm assuming you say SQL (structured query language) and you really mean Microsoft SQL Server (the actual database product) instead - right?
You cannot insert a whole list as a whole into SQL Server - you need to insert one row for each entry. This means, you need to call the INSERT statement multiple times.
Do it like this:
// define the INSERT statement using **PARAMETERS**
string insertStmt = "INSERT INTO dbo.REPORT_MARJORIE_ROLE(REPORT_ID, ROLE_ID, CREATED_BY, CREATED) " +
"VALUES(#ReportID, #RoleID, 'SYSTEM', CURRENT_TIMESTAMP)";
// set up connection and command objects in ADO.NET
using(SqlConnection conn = new SqlConnection(-your-connection-string-here))
using(SqlCommand cmd = new SqlCommand(insertStmt, conn)
{
// define parameters - ReportID is the same for each execution, so set value here
cmd.Parameters.Add("#ReportID", SqlDbType.Int).Value = YourReportID;
cmd.Parameters.Add("#RoleID", SqlDbType.Int);
conn.Open();
// iterate over all RoleID's and execute the INSERT statement for each of them
foreach(int roleID in ListOfRoleIDs)
{
cmd.Parameters["#RoleID"].Value = roleID;
cmd.ExecuteNonQuery();
}
conn.Close();
}
let say lstroles is your LIST<ROLES>.
lstroles.ForEach(Role =>
{
/* Your Insert Query like
INSERT INTO REPORT_MARJORIE_ROLE(REPORT_ID, ROLE_ID, CREATED_BY, CREATED)
VALUES(REPORT_ID, Role.ID, {0}, {1}, 'SYSTEM', CURRENT_TIMESTAMP);
Commit you query*\
});
On a personal note: Beware of SQL Injection.

Categories