Inserting a List<> into SQL Server table - c#

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.

Related

Get id from the last entry

I need to insert a product into a ProductDB table and at the same time get the id from the product I just inserted, so I can use it in the next query as a Foreign Key I have been looking at different methods like "select last_insert_rowid()" and "SCOPE_IDENTITY()" but I can't get it to work, how do I get it to work
public static void SaveProduct(ProductModel product)
{
using (IDbConnection cnn = new SQLiteConnection(LoadConnectionString()))
{
cnn.Execute("INSERT INTRO ProductDB (Name, Price) VALUES (#Name, #Price);",
product);
string ForeignKey = "the id from the last entry from the query above";
cnn.execute("INSERT INTO ImageDB (Filename, Path, FK_Product) VALUES (#Filename, #Path," + ForeignKey + " )");
}
}
In SQL Server has 3 functions (methods) for getting the last inserted id from the table.
IDENT_CURRENT() - returns the last-inserted identity value for a given table.
SCOPE_IDENTITY() - returns the last identity value inserted into an identity column in any table in the current session and current scope.
##IDENTITY - returns the last inserted identity value in any table in the current session, regardless of scope.
We need SCOPE_IDENTITY(), so ATTENTION!!!
This function must be used in the current scope, which located insert command.
Example:
declare
#new_id integer;
INSERT INTRO ProductDB (Name, Price) VALUES (#Name, #Price);
SET #new_id = SCOPE_IDENTITY();
If you would use System.Data.SQLite you could use the LastInsertRowId property.
Example:
using (SQLiteConnection conn = new SQLiteConnection(#"Data Source=.\Test.db"))
{
conn.Open();
SQLiteCommand cmd = conn.CreateCommand();
cmd.CommandText = $"INSERT INTO Test (name) values ('{Guid.NewGuid().ToString()}');";
cmd.ExecuteNonQuery();
id = conn.LastInsertRowId;
conn.Close();
}

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;
}

Inserting a value into a sql database by using a sql query

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

Edit existing data in SQL Server database

I have a table SupplierMaster in a SQL Server database with a column SUPPLIERNAME.
I want to edit saved supplier name using stored procedure with below query
ALTER PROCEDURE [dbo].[usp_SupplierMasterUpdateDetails]
(
#SUPPLIERNAME NVARCHAR(50)
)
AS
BEGIN
UPDATE [dbo].[SupplierMaster]
SET [SUPPLIERNAME] = #SUPPLIERNAME
WHERE [SUPPLIERNAME] = #SUPPLIERNAME
END
and I run the BELOW code through "UPDATE BUTTON" to update the data.
string connString = ConfigurationManager.ConnectionStrings["dbx"].ConnectionString;
using (SqlConnection conn = new SqlConnection(connString))
{
using (SqlCommand cmd = new SqlCommand("usp_SupplierMasterUpdateDetails", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
// Parameter
cmd.Parameters.AddWithValue("SUPPLIERNAME", AddSupplierTextBox.Text);
// Open Connection
conn.Open();
// ExecuteReader (Select Statement)
// ExecuteScalar (Select Statement)
// ExecuteNonQuery (Insert, Update or Delete)
cmd.ExecuteNonQuery();
MessageBox.Show("SUCCESSFULLY UPDATED", "Successful", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
But its not updating the selected data.
Please advice and assist me to correct the code for proper work.
You have multiple issues there.
First you need to fix your update query just as Thomas Levesque suggested.
a SQL Server table needs a primary key to be able to uniquely identify a record, for updates for example.
The easiest thing you could do is set that primary key to be identity of type int and make it self generating. Your supplier table could look like this :
SupplierID int, Primary Key, identity
SupplierName nvarchar(100)
Now, when you do an update, you would do it like this:
Update SupplierMaster
Set SupplierName = #supplierName
Where SupplierID = #suplierID
Such a SQL statement will return an int value. This return value will tell you how many SQL rows this update statement has changed. If it says 0 then it means that the SQL statement could not find that id you passed through and nothing changed. If it says 1, then the record was found and updated, if you get more than 1 you have an issue with the SQL statement and multiple rows were updated.
In your code check for this return value and that's how you determine if your update statement was successful or not.

A better way to achieve INSERT without hitting the database multiple times

I have the following, I could make it work as I want to but I think i'm doing it the wrong way, could you please explain how this could be done in a more efficient way ? While also looping on Categories and doing the same as with Districts within the same Insert() Method.
Thanks in advance.
#region Methods
public int Insert(List<District> Districts, List<Category> Categories)
{
StringBuilder sqlString = new StringBuilder("INSERT INTO Stores (name, image) VALUES (#Name, #Image);");
using (SqlConnection sqlConnection = new
SqlConnection(ConfigurationManager.ConnectionStrings["OahuDB"].ConnectionString))
{
SqlCommand sqlCommand = new SqlCommand(sqlString.ToString(), sqlConnection);
sqlCommand.Parameters.AddWithValue("#Name", this.Name);
sqlCommand.Parameters.AddWithValue("#Image", this.Image);
sqlConnection.Open();
int x = (int)sqlCommand.ExecuteScalar();
sqlString.Clear();
sqlCommand.Parameters.Clear();
foreach (District item in Districts)
{
sqlString.AppendLine("INSERT INTO districts_has_stores (district_id, store_id) VALUES (#DistrictID, #StoreID);");
sqlCommand.CommandText = sqlString.ToString();
sqlCommand.Parameters.AddWithValue("#DistrictID", item.ID);
sqlCommand.ExecuteNonQuery();
}
return x;
}
}
EDIT
Is is wrong to achieve the above by doing the following ?
sqlString.Clear();
sqlCommand.Parameters.Clear();
sqlString.AppendLine("INSERT INTO districts_has_stores (district_id, store_id) VALUES (#DistrictID, #StoreID);");
sqlCommand.CommandText = sqlString.ToString();
sqlCommand.Parameters.AddWithValue("#StoreID", x);
foreach (District item in Districts)
{
sqlCommand.Parameters.AddWithValue("#DistrictID", item.ID);
sqlCommand.ExecuteNonQuery();
}
sqlString.Clear();
sqlCommand.Parameters.Clear();
sqlString.AppendLine("INSERT INTO categories_has_stores (category_id, store_id) VALUES (#CategoryID, #StoreID);");
sqlCommand.CommandText = sqlString.ToString();
sqlCommand.Parameters.AddWithValue("#StoreID", x);
foreach (Category item in Categories)
{
sqlCommand.Parameters.AddWithValue("#CategoryID", item.ID);
sqlCommand.ExecuteNonQuery();
}
The first obvious thing is to move the invariant part of the sqlCommand out of the loop
sqlCommand.Parameters.Clear();
sqlString.Clear();
sqlString.AppendLine("INSERT INTO districts_has_stores (district_id, store_id) VALUES (#DistrictID, #StoreID);");
sqlCommand.CommandText = sqlString.ToString();
sqlCommand.Parameters.AddWithValue("#DistrictID", 0); // as dummy value
sqlCommand.Parameters.AddWithValue("#StoreID", x); // invariant
foreach (District item in Districts)
{
sqlCommand.Parameters["#DistrictID"].Value = item.ID;
sqlCommand.ExecuteNonQuery();
}
But this doesn't answer your fundamental problem. How to avoid hitting the database multiple times.
You could build a query with multiple inserts like this
sqlString.Clear();
sqlString.Append("INSERT INTO districts_has_stores (district_id, store_id) VALUES (");
foreach(District item in Districts)
{
sqlString.Append(item.ID.ToString);
sqlString.Append(", ")
sqlString.Append(x.ToString());
sqlString.Append("),");
}
sqlString.Length--;
sqlCommand.CommandText = sqlString.ToString()
But string concatenation is really a bad practice and I present this solution just as an example and I don't want to suggest this kind of approach.
The last possibility are Table-Valued Parameters (Only from SqlServer 2008).
First you need to create a Sql Type for the table you will pass in
CREATE TYPE dbo.DistrictsType AS TABLE
( DistrictID int, StoreID int )
and a StoredProcedure that will insert the data from the datatable passed in
CREATE PROCEDURE usp_InsertDistricts
(#tvpNewDistricts dbo.DistrictsType READONLY)
AS
BEGIN
INSERT INTO dbo.Districts (DistrictID, StoreID)
SELECT dt.DistrictID, dt.StoreID FROM #tvpNewDistricts AS dt;
END
then, back to your code you pass the district into the storedprocedure
(Probably you need to convert your List in a DataTable)
DataTable dtDistricts = ConvertListToDataTable(Districts);
SqlCommand insertCommand = new SqlCommand("usp_InsertDistricts", sqlConnection);
SqlParameter p1 = insertCommand.Parameters.AddWithValue("#tvpNewDistricts", dtDistricts);
p1.SqlDbType = SqlDbType.Structured;
p1.TypeName = "dbo.DistrictsType";
insertCommand.ExecuteNonQuery();
Well, if you look back at the link above, you will find other ways to pass your data in a single step to the database backend.... (Scroll to the end and you will find also a method that doesn't require a stored procedure on the database)
Assuming Stores has an identity column, in SQL Server, create a table type and a table-valued parameter to take advantage of it:
CREATE TYPE dbo.DistrictsTVP AS TABLE
(
DistrictID INT -- PRIMARY KEY? I hope so.
);
GO
CREATE PROCEDURE dbo.InsertStoreAndDistricts
#Name NVARCHAR(255),
#Image <some data type???>,
#Districts dbo.DistrictsTVP READONLY
AS
BEGIN
SET NOCOUNT ON;
DECLARE #StoreID INT;
INSERT dbo.Stores(name, [image]) SELECT #Name, #Image;
SET #StoreID = SCOPE_IDENTITY();
INSERT dbo.district_has_stores(district_id, store_id)
SELECT DistrictID, #StoreID
FROM #Districts;
END
GO
Then in C#, you can pass your List in directly without any looping:
using (...)
{
SqlCommand cmd = new SqlCommand("dbo.InsertStoreAndDistricts", sqlConnection);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvparam = cmd.Parameters.AddWithValue("#Districts", Districts);
tvparam.SqlDbType    = SqlDbType.Structured;
// other params here - name and image
cmd.ExecuteNonQuery();
}
Recently in my project i used XML as a data type in my stored proc and did insert update and delete in just one shot instead of hitting the database multiple times .
Sample Stored proc
ALTER PROCEDURE [dbo].[insertStore]
#XMLDATA xml,
#name varchar(50),
#image datatype
AS
Begin
INSERT INTO Store
(name
,image
)
Select XMLDATA.item.value('#name[1]', 'varchar(10)') AS Name,
XMLDATA.item.value('#image[1]', 'yourData type') AS Image
FROM #XMLDATA.nodes('//Stores/InsertList/Store') AS XMLDATA(item)
END
Similarly you can write for update and delete .In C# u need to create the xml
public string GenerateXML(List<District> Districts)
var xml = new StringBuilder();
var insertxml = new StringBuilder();
xml.Append("<Stores>");
for (var i = 0; i < Districts.Count; i++)
{ var obj = Districts[i];
insertxml.Append("<Store");
insertxml.Append(" Name=\"" + obj.Name + "\" ");
insertxml.Append(" Image=\"" + obj.Image + "\" ");
insertxml.Append(" />");
}
xml.Append("<InsertList>");
xml.Append(insertxml.ToString());
xml.Append("</InsertList>");
SqlCommand cmd= new SqlCommand("insertStore",connectionString);
cmd.CommandType=CommandType.StoredProcedure;
SqlParameter param = new SqlParameter ();
param.ParameterName ="#XMLData";
param.value=xml;
paramter.Add(param);
cmd.ExecuteNonQuery();
Personally, I would create a stored procedure for the insert and pass in a Table-Valued param, which would allow you to do
INSERT tbl (f1, f2, ... fN)
SELECT * FROM #TVP
http://msdn.microsoft.com/en-us/library/bb510489.aspx
Unless you're using SQL 2005, then I would use an XML param in my stored proc and Serialize a collection to be inserted.
Think about your system design. Where is the data that you need to insert coming from? If it's already in the database, or another database, or some other kind of data store, you should be able to achieve a more bulk kind of transfer, simply inserting from one database to the other in a loop in stored procedure.
If the data is coming from a user, or some incompatible data store, like say an export from some third party program, then you basically have to realize that to get it into the database will involve quite of few round-trips to the database. You can use some tables, or XML or such , but those are actually closer to doing a bulk insert using other methods.
The bottom line is that SQL databases are designed to do inserts one at a time. This is 99% of the time OK because you are never asking users using the UI to type in thousands of things at one time.

Categories