I'm trying to update a database table with values from listbox as well as textbox values however and exception was thrown
Exception.
Procedure or function expects parameter, which was not supplied.
thrown even after supplying the parameter. I wanted the courses selected to be shown in the same column of database table separated by comma.
This is what I have so far.
using (SqlCommand com = new SqlCommand("updateStudent", connection))
{
com.CommandType = CommandType.StoredProcedure;
com.Parameters.AddWithValue("#studentNum", studentNum.Text);
com.Parameters.Add("#studentName", SqlDbType.NVarChar).Value = studentName.Text;
foreach (ListItem item in lstCourse.Items)
{
if (item.Selected)
{
com.Parameters.Add("#courses", SqlDbType.NVarChar).Value = " " + item.Text + " ";
}
}
com.ExecuteNonQuery();
com.Parameters.Clear();
}
}
The Error is normal
You can not send more than once a parameter with the same name for the invocation of a stored procedure.
If you need to save a list of courses to the student entity, you must change the structure of the database, and use a new table with a foreign key to the student table.
Check this documentation
Foreing Key ,
Store Procedure
I think #Vinit had the right answer to do what you want to do. In place of your foreach (ListItem item in lstCourse.Items) you want this instead.
// create comma separated list of selected courses
var courses = string.Join(",", lstCourse.Items
.Where(item=>item.Selected)
.Select(s=>s.Text)
.ToList());
// add courses as parameter
com.Parameters.Add("#courses", SqlDbType.NVarChar).Value = courses;
It builds the list of comma separated course names and then puts that into the command as one argument #courses.
However!! After having done this for around 20 years I can tell you you're not really going to like it this way. You're going to want the courses that the student selected to be in separate records. Each time you have to deal with these courses you're going to deal with this collection of course names.
What I have done in this case is create a separate table to join Student to Course in a "Many to Many" relationship.
BTW - I don't see the place in your code where you tell the command what SP to execute.
Mike
Instead of foreach loop, you should do
// create comma separated list of selected courses
var courses = string.Join(",", lstCourse.Items
.Where(item=>item.Selected)
.Select(s=>s.Text)
.ToList());
// add courses as parameter
com.Parameters.Add("#courses", SqlDbType.NVarChar).Value = courses;
UPDATE
After reading through your one of the comments where you have shared the procedure code below.
update Student
set studentNum=#studentNum,studentName=#studentName,courses=#courses
where studentId=#studentId`
it seems you are not passing the #studentId as parameter. that might be the reason for error message
Procedure or function expects parameter, which was not supplied.
So you should add this parameter to sql command
com.Parameters.Add("#studentId", SqlDbType.Int).Value = studentId; -- update the dbtype and variable as per your system.
Related
I want to create simple database in runtime, fill it with data from internal resource and then read each record through loop. Previously I used LiteDb for that but I couldn't squeeze time anymore so
I choosed SQLite.
I think there are few things to improve I am not aware of.
Database creation process:
First step is to create table
using var create = transaction.Connection.CreateCommand();
create.CommandText = "CREATE TABLE tableName (Id TEXT PRIMARY KEY, Value TEXT) WITHOUT ROWID";
create.ExecuteNonQuery();
Next insert command is defined
var insert = transaction.Connection.CreateCommand();
insert.CommandText = "INSERT OR IGNORE INTO tableName VALUES (#Id, #Record)";
var idParam = insert.CreateParameter();
var valueParam = insert.CreateParameter();
idParam.ParameterName = "#" + IdColumn;
valueParam.ParameterName = "#" + ValueColumn;
insert.Parameters.Add(idParam);
insert.Parameters.Add(valueParam);
Through loop each value is inserted
idParameter.Value = key;
valueParameter.Value = value.ValueAsText;
insert.Parameters["#Id"] = idParameter;
insert.Parameters["#Value"] = valueParameter;
insert.ExecuteNonQuery();
Transaction commit transaction.Commit();
Create index
using var index = transaction.Connection.CreateCommand();
index.CommandText = "CREATE UNIQUE INDEX idx_tableName ON tableName(Id);";
index.ExecuteNonQuery();
And after that i perform milion selects (to retrieve single value):
using var command = _connection.CreateCommand();
command.CommandText = "SELECT Value FROM tableName WHERE Id = #id;";
var param = command.CreateParameter();
param.ParameterName = "#id";
param.Value = id;
command.Parameters.Add(param);
return command.ExecuteReader(CommandBehavior.SingleResult).ToString();
For all select's one connection is shared and never closed. Insert is quite fast (less then minute) but select's are very troublesome here. Is there a way to improve them?
Table is quite big (around ~2 milions records) and Value contains quite heavy serialized objects.
System.Data.SQLite provider is used and connection string contains this additional options: Version=3;Journal Mode=Off;Synchronous=off;
If you go for performance, you need to consider this: each independent SELECT command is a roundtrip to the DB with some extra costs. It's similar to a N+1 select problem in case of parent-child relations.
The best thing you can do is to get a LIST of items (values):
SELECT Value FROM tableName WHERE Id IN (1, 2, 3, 4, ...);
Here's a link on how to code that: https://www.mikesdotnetting.com/article/116/parameterized-in-clauses-with-ado-net-and-linq
You could have the select command not recreated for every Id but created once and only executed for every Id. From your code it seems every select is CreateCommand/CreateParameters and so on. See this for example: https://learn.microsoft.com/en-us/dotnet/api/system.data.idbcommand.prepare?view=net-5.0 - you run .Prepare() once and then only execute (they don't need to be NonQuery)
you could then try to see if you can be faster with ExecuteScalar and not having reader created for one data result, like so: https://learn.microsoft.com/en-us/dotnet/api/system.data.idbcommand.executescalar?view=net-5.0
If scalar will not prove to be faster then you could try to use .SingleRow instead of .SingleResult in your ExecuteReader for possible performance optimisations. According to this: https://learn.microsoft.com/en-us/dotnet/api/system.data.commandbehavior?view=net-5.0 it might work. I doubt that but if first two don't help, why not try it too.
I've a DataGrid which uses a list object. I would like to prevent users from entering duplicate values into database.
My stored procedure already prevents duplicate data from entering into database but I would like to show a more descriptive message to the users explaining they can't enter duplicate values.
Below is my code where I'm inserting values via Stored Procedure:
using (var cn = ConnectionManager<SqlConnection>.GetManager(Database))
using (var cmd = new SqlCommand("Save", cn.Connection))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#TableId", ReadProperty(TableIdProperty));
cmd.Parameters.AddWithValue("#Q1", ReadProperty(Q1Property).ToUpper());
cmd.Parameters.AddWithValue("#Q2", ReadProperty(Q2Property));
using (var dr = new SafeDataReader(cmd.ExecuteReader()))
{
//Would like to capture duplicates here if a record already exists with same Q1 and Q2 values
if (dr.Read())
LoadProperties(dr);
else
dr.NextResult(); // sproc error happens here
}
}
If I got it right you have an Application where users can Insert new rows and you want to prevent duplicated entries.
If this is right you could add a Database Function that returns an ErrorCode and you could call this function using SqlCommand from your code.
I have been given the task of rewriting and old work application from classic .asp to ASP.NET that includes a database table that does not have an auto incremented primary key. We want to continue to use this table to maintain database integrity (it also has 80,000+ records!). The problem that I am running into is that I need to be able to pull the last item from the ID column of the database table regardless of how old the record is, increment that number and then include it in the new record to be inserted as the new record's ID number. How would I go about doing this? I have tried the ListItem, DataReader, DataTables, Generic Lists (as objects), and ArrayLists. I can pull the information and store it, but I cannot get the last item in the collection by itself. Any suggestions would be appreciated.
protected void GetPrimaryKey()
{
string strSQL = "";
try
{
OleDbConnection dbConn = new OleDbConnection();
dbConn.ConnectionString = System.Web.Configuration.WebConfigurationManager.ConnectionString["ConnectionString"].ToString();
strSQL = "SELECT observationID FROM Observation";
OleDbCommand myCmd = new OleDbCommand(strSQL, dbConn);
OleDbReader reader;
ListItem item;
if (dbConn.State == ConnectionState.Colsed) dbConn.Open();
reader = myCmd.ExecuteReader();
while (reader.Read())
{
item = new ListItem();
item.Text = reader["observationID"].ToString();
}
reader.Close();
dbConn.Close();
myCmd.Dispose();
}
}
Populating the list is where this code is at. The last item still needs to be found then incremented, and the returned to the submit button event handler that starts this whole process. I know this code is missing a lot, but I didn't want to send my entire commented mess. Again, any help is appreciated. Thank You.
SELECT TOP 1 ObservationId FROM Observarion ORDER BY ObservationId DESC
This will return the last row id
If more than one person try to get this value to insert, you will run into an issue where you end up with the same Ids, unless that column is unique and will throw an error.
To minimize issues, you can do an inline select in your insert statement.
INSERT INTO Observation (ObservationId) VALUES(SELECT TOP 1 (ObservationId + 1) As NewObservationId FROM Observation ORDER BY ObservationId DESC)
Not sure if my syntax is completely correct but it should lead you in the right direction.
Try get the max observation ID in sql statement:
SELECT MAX(observationID) FROM Observation
Then increment it.
SELECT MAX(observationID) FROM Observation
will always return the max value regardless of how old the record is
just ask for next value autoincrement id from table:
SELECT IDENT_CURRENT('table_name');
enjoy xD.
Why don't you do your query like this?:
SELECT Top 1 observationID FROM Observation order by desc
If you have some sort of parameters or configuration table, I suggest you store the last value there and retrieve/update it each time you do an insert. This will prevent any issues in case you have 2 or more clients trying to insert a new record at the same time.
I tried everything I could knew on this, basically I have a table of Clients ( Sql Server )
The table looks like this
create table Client
(
Name nvarchar(100),(I want to be null)
EmployeesNo nvarchar(50),
CompanyCapital int
)
This simple table, the problem I encounter is when I want to update.
The problem is I can have multiple data with same values in Name so how to differentiate between the Values in Name to make them unique, Update looks like:
using(SqlConnection con = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["Data"].ToString())
{
if(con.State.ToString() == "Closed")
{
con.Open();
}
using(SqlCommand com = new SqlCommand("Update Client set Name = #name, EmployeesNo = #emp, CompanyCapital = #com where Name = #name2 (Should I update by some another thing that it will make it unique ???)",con))
{
com.Parameters.Add(new SqlParameter("#name2",Mod));(Mod = string I initialized in constructor from another form;)
com.Parameters.Add(new SqlParameter("#name",textBox1.Text));
com.Parameters.Add(new SqlParameter("#emp",textBox2.Text));
com.Parameters.Add(new SqlParameter("#com",textBox3.Text));
com.ExecuteNonQuery();
}
}
So when i update or user updates those values if i have multiple data with same values it will overwrite all of them and i don't want that, remember also that values are inserted and updated from textboxes.
I tried thinking of using Primary key but how do i know where to update at wich primary key, cause im using textboxes and i don't want the user to type in the ID of something, thanks.
You should use a primary key (like ID) if you need to uniquely identify your records.
In order to know the ID of the record that needs to be updated, just store the ID in your form as a hidden and readonly field.
The second parameter of Parameters.Add method, its SqlDbType. Not the Parameter Value.
instead use Parameters.AddWithValue
or put value in object initializer,
com.Parameters.Add(new SqlParameter("#name2", SqlDbType.VarChar) {Value = Mod});
i would like to know what is the standard/best way of doing the following:
i have a form web app in asp.net and using C#
the user will enter data into the form and click INSERT and it will insert data into 4 different tables.
the fields are:
primarykey, animal, street, country
the form allows for multiple animals, multiple streets and multiple countries per primarykey. so when i have data like this:
[1],[rhino,cat,dog],[luigi st, paul st], [russia,israel]
i need it inserted into tables like this:
table1:
1,rhino
1,cat
1,dog
table2:
1,luigi st
1, paul st
table3:
1,russia
1,israel
questions
I'm at a total loss on how to do this. if i just had one table and one set of data per primary key i would just use the InsertQuery and do it this way, but since it is multiple tables i don't know how to do this??
what control(s) should i use in order to allow user to input multiple values? currently i am just using textboxes and thinking of separating the entries by semi colons, but that's probably not the right way.
I wanted to recommend that you take advantage of the new multirow insert statement in SQL 2008 so that you can just pass a sql statement like this:
INSERT INTO table1(id,animal_name) values (1,cat),(1,dog),(1,horse)...
To your SqlCommand but I don't know how to build a statement like that w/o risking being victim of a SQL Injection Attack.
Another alternative is to define data table types in your sql database:
And then construct a DataTable in C# that matches your datatable type definition:
DataTable t = new DataTable();
t.Columns.Add("id");
t.Columns.Add("animal_name");
foreach(var element in your animals_list)
{
DaraRow r = t.NewRow();
r.ItemArray = new object[] { element.id, element.animal_name };
t.Rows.Add(r);
}
// Assumes connection is an open SqlConnection.
using (connection)
{
// Define the INSERT-SELECT statement.
string sqlInsert = "INSERT INTO dbo.table1 (id, animal_name) SELECT nc.id, nc.animal_name FROM #animals AS nc;"
// Configure the command and parameter.
SqlCommand insertCommand = new SqlCommand(sqlInsert, connection);
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("#animals", t);
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.AnimalTable";
// Execute the command.
insertCommand.ExecuteNonQuery();
}
Read more here.
Or if you are familiar with Stored Procedures, same as previous suggestion but having the stored procedure receive the DataTable t as parameter.
If none of the above work for you, create a SqlTranscation from the Connection object and iterate through each row of each data set inserting the record in the appropriate table and finally commit the transaction. Example here.
Use Checkboxes on the front end. Have a service/repository to save the user data. Something like the following:
public void UpdateUserAnimals(Guid userId, string[] animals)
{
using (SqlConnection conn = new SqlConnection("connectionstring..."))
{
using (SqlCommand cmd = new SqlCommand("Insert Into UserAnimals(UserId, Animals) values (#UserId, #Animal)"))
{
conn.Open();
cmd.Parameters.AddWithValue("#UserId", userId);
foreach(string animal in animals)
{
cmd.Parameters.AddWithValue("#Animal", animal);
cmd.ExecuteNonQuery();
}
}
}
}
There are more complex solutions, but this is a simple one.