Inserting into two tables and Identity_Scope() - asp.net

I am building a forum and I have two tables:
Threads
-------
ThreadID
UsersID
Date
ThreadTitle
ThreadParagraph
ThreadClosed
Topics
-----
TopicsID
Theme
Topics
Date
The ThreadID is connected to the users table with a primary key:
Topics.TopicsID(PK)==Threads.TopicID(FK)
First i insert into the Topics table and then to the Threads table. My goal is to obtain the ID of Topics.TopicID with Identity_Scope() and pass it to the second insert which is Threads.TopicID
Here is what i have done, but i am not sure if it is correct:
StringBuilder insertCommand = new StringBuilder();
insertCommand.Append("DECLARE #TopicsID int");
insertCommand.Append("INSERT INTO Topics(Theme,Topics,Date)");
insertCommand.Append("VALUES('#topic,#subTopic,GETDATE()')");
insertCommand.Append("SET #TopicsID = SCOPE_IDENTITY()");
insertCommand.Append("INSERT INTO Threads(UsersID,TopicsID,Date,ThreadTitle,ThreadParagraph,ThreadClosed)");
insertCommand.Append("VALUES('#uniqueIdentifier,#TopicsID,GETDATE(),#questionTitle,#questionParagraph,0')");
I have got all the otehr parameters obtained from the controls the users presses or feeds information into, so dont worry about them. All i am worried about is passing the same TopicID from the Topic table to Thread table (Column name: TopicID).

Both Magnus & Damien_The_Unbeliever are right - you have few syntax errors (or typos). Correct insert command should be something like
insertCommand.Append(#"
DECLARE #TopicSID int
INSERT INTO Topics(Theme,Topics,Date)
VALUES(#topic,#subTopic,GETDATE())
SET #TopicSID = SCOPE_IDENTITY()
INSERT INTO Threads(UsersID,TopicsID,Date,ThreadTitle,ThreadParagraph,ThreadClosed)
VALUES(#uniqueIdentifier,#TopicSID ,GETDATE(),#questionTitle,#questionParagraph,0)
");

Related

Stored Procedure for inserting text field values that is created dynamically to the same id using asp.net C#

Im new to ASP.net webforms.Im having a event page,in which i have a field to add sales channel heads mail id.when i click on the plus button i will be able to add more than one sales channels head.
For inserting the form values into the database im using Stored procedure.and its inserting the records with one sales channel head email id.
I want to know how i can write a stored procedure for inserting dynamic textbox values into sql server for the same record(That is the id and event name should be same).
This is my stored procedure
CREATE PROCEDURE SPInsertEvent
#eventName varchar(200),
#eventDate date,
#costPerHead varchar(200),
#totalCost varchar(200),
#salesChannelHeads varchar(200),
#salesManagers varchar(200),
#eventCreationTime datetime
AS
BEGIN
SET NOCOUNT ON
-- Insert statements for procedure here
INSERT INTO dbo.hp_event
(event_name, event_date, cost_per_head, total_cost, sales_channel_head, sales_manager, event_creation_time)
VALUES
(#eventName, #eventDate, #costPerHead, #totalCost, #salesChannelHeads, #salesManagers, #eventCreationTime)
END
This is my ASP.net function
SqlCommand cmd = new SqlCommand("SPInsertEvent", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("EventName", txtEventName.Text);
cmd.Parameters.AddWithValue("EventDate", Convert.ToDateTime(txtEventDate.Text));
cmd.Parameters.AddWithValue("CostPerHead", txtTotCostPerHead.Text);
cmd.Parameters.AddWithValue("TotalCost", txtTotalCostEvent.Text);
cmd.Parameters.AddWithValue("SalesChannelHead", txtSalesChannelHead.Text);
cmd.Parameters.AddWithValue("SalesManager", txtSalesManagers.Text);
cmd.Parameters.AddWithValue("EventCreationTime", DateTime.Now);
conn.Open();
int k = cmd.ExecuteNonQuery();
if (k != 0)
{
string message = "Event added successfully.";
string script = "window.onload = function(){ alert('";
script += message;
script += "')};";
ClientScript.RegisterStartupScript(this.GetType(), "SuccessMessage", script, true);
}
conn.Close();
Instead of storing all the list of email ids for the given event in one table, I would suggest you to store them in separate table and you can reference them from the hp_event table whenever you need. So your database design should be some thing like below where eventid of hp_eventSalesManagers references eventId of hp_event -
To make this design work you can make use of Table Valued Parameters in ADO.NET and follow the below steps:
Create a User Defined Type -
CREATE TYPE [dbo].[ChannelHeads] As Table
(
EmailIds VARCHAR(50)
)
Whenever you click button populate a new Data Table(I am using Session to keep track
of the previous data), below is the sample code:
protected void btnAdd_Click(object sender, EventArgs e)
{
if (Session["DataTable"] == null)
{
dataTable = new DataTable();
dataTable.Columns.Add("EmailIds", typeof(string));
Session.Add("DataTable", dataTable);
}
else
{
//If yes then get it from current session
dataTable = (DataTable)Session["DataTable"];
}
DataRow dt_row;
dt_row = dataTable.NewRow();
dt_row["EmailIds"] = name.Text;
dataTable.Rows.Add(dt_row);
}
When submitting to data base add the below parameter(See the way I am passing the data table to DB):
SqlParameter parameterSalesChannelHeads = new SqlParameter();
parameterSalesChannelHeads.ParameterName = "#salesChannelHeads";
parameterSalesChannelHeads.SqlDbType = System.Data.SqlDbType.Structured;
parameterSalesChannelHeads.Value = (DataTable)Session["DataTable"];
parameterSalesChannelHeads.TypeName = "dbo.ChannelHeads";
cmd.Parameters.Add(parameterSalesChannelHeads);
Change all your parameters in above format just to make sure you are using
Parameters.Add instead of Parameters.AddWithValue as mentioned here
Finally change the procedure as below to populate the tables, below is one of the way,
you can enable error handling and improve the code:
ALTER PROCEDURE SPInsertEvent
#eventName varchar(200),
#eventDate datetime,
#costPerHead varchar(200),
#totalCost varchar(200),
#salesChannelHeads As [dbo].[ChannelHeads] Readonly,
#salesManagers varchar(200),
#eventCreationTime datetime
AS
BEGIN
SET NOCOUNT ON
DECLARE #eventID INT
-- Insert statements for procedure here
INSERT INTO dbo.hp_event
(event_name, eventDate, costPerHead, totalCost, eventCreationTime,
salesManagers)
VALUES
(#eventName, #eventDate, #costPerHead, #totalCost,#eventCreationTime,
#salesManagers)
SET #eventID = SCOPE_IDENTITY()
INSERT INTO dbo.hp_eventSalesManagers
(eventid,event_name,salesChannelHeads)
SELECT #eventID, #eventName, EmailIds
FROM
#salesChannelHeads
END
Finally change the data types of the fields accordingly as mentioned in the comment section for better clarity and usages.
You said in the comments "What i need is a stored procedure for inserting saleschannel heads email id(txtSalesChannelHead,txtSalesChannelHead1,txtSalesChannelHead2) into the sql server table with same id,that is there will be duplicate rows in the table". Handling a dynamic number of inputs like that is not best done in a stored procedure, from what i can see of your scenario. The easier way is to run the insert procedure from your .NET code once for each textbox. Now I don't know how your textboxes are being added, so I can't tell you how to get the set of textbox values, but once you have it, a simple foreach loop will let you run the stored procedure once for each of them.

Unique serial number generation - Entity Framework ASp.net MVC

I am developing a complaint management in which I have to generate unique serial number for each complaint like 00001/20 {Serial number/year}.
I am using repository pattern and i am generating this complaint number using the following code snippet but problem is if two user try to lodge a complaint at the same time it will generate a same complaint no and that thrown an error as I am keeping a serial number in a separate table which is also mentioned below for reference. Let me know the best way to achieve this
int serialNo = repository.serialNo.Find(c => c.Year == DateTime.Now.Year).FirstOrDefault().TicketCounter;
string complaintNo = string.Format("{0}", serialNo.ToString().PadLeft(5, '0'));
model.Id = repository.complaintRepo.GetMaxPK(c => c.Id);
I am using repository pattern.
I guess, one of the solutions is to setup the table so that it generates required ID automatically on every new row. This ensures that the ID is always unique.
CREATE SEQUENCE MySequence
AS int
START WITH 1
INCREMENT BY 1;
CREATE TABLE Complaint
(
Id char(8) CONSTRAINT [DF_Complaint_ID]
DEFAULT FORMAT((NEXT VALUE FOR MySequence), '0000#')
+'/'+RIGHT(YEAR(GETDATE()),2),
Foo int,
Bar int,
CONSTRAINT [PK_MyTable] PRIMARY KEY (Id)
);
Demo: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=18a5d0fec80a3985e30cef687d3c8e49
So there will be no need to assign the id manually and your code could look like
var c = repository.Insert(new model
{
Foo = ...
Bar = ...,
...
});
repository.Save();
// you can get id after inserting data in the database
string id = c.Id;

Android SQLITE Insert into table with values coming from a subquery

In my db-driven app I need to perform insert into queries in which the value for one or more field comes from a subquery.
The insert into statement may look like the following example:
INSERT INTO MyTable (field_1, field_2)
VALUES('value for field 1', (SELECT field_x FROM AnotherTable WHERE ...))
At present I am doing it manually building the query:
String MyQuery = "INSERT INTO mytable (field_1, field_2)
VALUES('value for field 1', (SELECT field_x FROM AnotherTable WHERE ...))"; // Of course my query is far more complex and is built in several steps but the concept is safe, I end up with a SQL String
SQLiteDatabase= db = getWritableDatabase();
db.execSQL(MyQuery); // And it works flawlessy as it was a Swiss Clock
What i would like to do instead is:
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put("field_1", "value for field 1");
values.put("field_2", ThisIsAQuery("(SELECT field_x FROM AnotherTable WHERE ...)"));
db.insert("MyTable", null, values);
db.close();
Where the fake method ThisIsAQuery(...) is the missing part, something that should tell the query builder that "SELECT.." is not a value but a query that should be embedded in the insert statement.
Is there a way to achieve this?
The whole point of the ContentValues container is to be able to safely use strings without interpreting them as SQL commands.
It is not possible to use subqueries with insert(). The only way to get a value from another table is by executing a separate query; in this case, ThisIsAQuery() would be stringForQuery() or longForQuery().

SQLite Schema Information Metadata

I need to get column names and their tables in a SQLite database. What I need is a resultset with 2 columns: table_name | column_name.
In MySQL, I'm able to get this information with a SQL query on database INFORMATION_SCHEMA. However the SQLite offers table sqlite_master:
sqlite> create table students (id INTEGER, name TEXT);
sqlite> select * from sqlite_master;
table|students|students|2|CREATE TABLE students (id INTEGER, name TEXT)
which results a DDL construction query (CREATE TABLE) which is not helpful for me and I need to parse this to get relevant information.
I need to get list of tables and join them with columns or just get columns along with table name column. So PRAGMA table_info(TABLENAME) is not working for me since I don't have table name. I want to get all column metadata in the database.
Is there a better way to get that information as a result set by querying database?
You've basically named the solution in your question.
To get a list of tables (and views), query sqlite_master as in
SELECT name, sql FROM sqlite_master
WHERE type='table'
ORDER BY name;
(see the SQLite FAQ)
To get information about the columns in a specific table, use PRAGMA table_info(table-name); as explained in the SQLite PRAGMA documentation.
I don't know of any way to get tablename|columnname returned as the result of a single query. I don't believe SQLite supports this. Your best bet is probably to use the two methods together to return the information you're looking for - first get the list of tables using sqlite_master, then loop through them to get their columns using PRAGMA table_info().
Recent versions of SQLite allow you to select against PRAGMA results now, which makes this easy:
SELECT
m.name as table_name,
p.name as column_name
FROM
sqlite_master AS m
JOIN
pragma_table_info(m.name) AS p
ORDER BY
m.name,
p.cid
where p.cid holds the column order of the CREATE TABLE statement, zero-indexed.
David Garoutte answered this here, but this SQL should execute faster, and columns are ordered by the schema, not alphabetically.
Note that table_info also contains
type (the datatype, like integer or text),
notnull (1 if the column has a NOT NULL constraint)
dflt_value (NULL if no default value)
pk (1 if the column is the table's primary key, else 0)
RTFM: https://www.sqlite.org/pragma.html#pragma_table_info
There are ".tables" and ".schema [table_name]" commands which give kind of a separated version to the result you get from "select * from sqlite_master;"
There is also "pragma table_info([table_name]);" command to get a better result for parsing instead of a construction query:
sqlite> .tables
students
sqlite> .schema students
create table students(id INTEGER, name TEXT);
sqlite> pragma table_info(students);
0|id|INTEGER|0||0
1|name|TEXT|0||0
Hope, it helps to some extent...
Another useful trick is to first get all the table names from sqlite_master.
Then for each one, fire off a query "select * from t where 1 = 0". If you analyze the structure of the resulting query - depends on what language/api you're calling it from - you get a rich structure describing the columns.
In python
c = ...db.cursor()
c.execute("select * from t where 1=0");
c.fetchall();
print c.description;
Juraj
PS. I'm in the habit of using 'where 1=0' because the record limiting syntax seems to vary from db to db. Furthermore, a good database will optimize out this always-false clause.
The same effect, in SQLite, is achieved with 'limit 0'.
FYI, if you're using .Net you can use the DbConnection.GetSchema method to retrieve information that usually is in INFORMATION_SCHEMA. If you have an abstraction layer you can have the same code for all types of databases (NOTE that MySQL seems to swich the 1st 2 arguments of the restrictions array).
Try this sqlite table schema parser, I implemented the sqlite table parser for parsing the table definitions in PHP.
It returns the full definitions (unique, primary key, type, precision, not null, references, table constraints... etc)
https://github.com/maghead/sqlite-parser
The syntax follows sqlite create table statement syntax: http://www.sqlite.org/lang_createtable.html
This is an old question but because of the number of times it has been viewed we are adding to the question for the simple reason most of the answers tell you how to find the TABLE names in the SQLite Database
WHAT DO YOU DO WHEN THE TABLE NAME IS NOT IN THE DATABASE ?
This is happening to our app because we are creating TABLES programmatically
So the code below will deal with the issue when the TABLE is NOT in or created by the Database Enjoy
public void toPageTwo(View view){
if(etQuizTable.getText().toString().equals("")){
Toast.makeText(getApplicationContext(), "Enter Table Name\n\n"
+" OR"+"\n\nMake Table First", Toast.LENGTH_LONG
).show();
etQuizTable.requestFocus();
return;
}
NEW_TABLE = etQuizTable.getText().toString().trim();
db = dbHelper.getWritableDatabase();
ArrayList<String> arrTblNames = new ArrayList<>();
Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE
type='table'", null);
if (c.moveToFirst()) {
while ( !c.isAfterLast() ) {
arrTblNames.add( c.getString( c.getColumnIndex("name")) );
c.moveToNext();
}
}
c.close();
db.close();
boolean matchFound = false;
for(int i=0;i<arrTblNames.size();i++) {
if(arrTblNames.get(i).equals(NEW_TABLE)) {
Intent intent = new Intent(ManageTables.this, TableCreate.class
);
startActivity( intent );
matchFound = true;
}
}
if (!matchFound) {
Toast.makeText(getApplicationContext(), "No Such Table\n\n"
+" OR"+"\n\nMake Table First", Toast.LENGTH_LONG
).show();
etQuizTable.requestFocus();
}
}

Numbering comments in ASP.NET and SQL Server

I've just thought about best way to store comments in database with appropriate numbers according to the article.
The idea is to store comments with composite primary key (commentId, articleId) where commentId is generated according to the given articleId. The system of generating should has same principle as IDENTITY generated columns in SQL Server, because if someone delete the comment, the number will be never used again. I guess there is not any functionality in Microsoft SQL Server to do that with composite PK, so I am asking about some replacement for this solution.
First thought was to use transaction to get MAX(commentId) + 1, but I am looking for something more abstract (maybe INSTEAD OF trigger), something that could be used for example in LINQ with no knowledge of the background, just insert to the appropriate table all required values (so no commentId) and save it.
I would use an autogenerated identity column for the commentId and have it be the primary key alone. I'd create an index on the articleId for look ups. I would also have createdDate column that is autopopulated with the current date on insertion -- mark it as db generated and readonly in LINQ so it doesn't require or try to insert/update the value. To get a numbering -- if showing them by date isn't enough -- I'd order by createdDate inversed and assign a numeric value in the select using Row_Number() or a numbering on the client side.
I would use an identity column as the key for the comments, why do you need a numbering for the comments stored in the database?
Thank you for responses, I wanted something with numbered comments because of referencing in the text of comments. I did not want to make reaction by names, sometimes one person reacts more times, so with this system, I will know to which one the person is replying.
So today I made up this INSTEAD OF INSERT trigger:
CREATE TRIGGER InsertComments ON Comments
INSTEAD OF INSERT
AS
DECLARE #Inserted TABLE
(
ArticleId INT NOT NULL,
UserId INT NOT NULL,
CommentDate DATETIME NOT NULL,
Content NVARCHAR(1000) NOT NULL,
RowNumber INT NOT NULL
)
INSERT INTO #Inserted
SELECT ArticleId, UserId, CommentDate, Content, ROW_NUMBER() OVER (ORDER BY CommentDate) AS RowNumber
FROM INSERTED
DECLARE #NumberOfRows INT = (SELECT COUNT(*) FROM #Inserted)
DECLARE #i INT = 1
WHILE (#i <= #NumberOfRows)
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
DECLARE #CommentId INT = (SELECT ISNULL(MAX(CommentId), 0)
FROM Comments WHERE ArticleId = (SELECT ArticleId
FROM #Inserted WHERE RowNumber = #i)) + 1
INSERT INTO Comments(CommentId, ArticleId, UserId, CommentDate, Content)
SELECT #CommentId, ArticleId, UserId, CommentDate, Content
FROM #Inserted WHERE RowNumber = #i
COMMIT
SET #i = #i + 1
END
I know this is not the perfect solution, but it works exactly how I needed. If any of you has some comments, I'll be happy to read them.

Resources