I have been trying to use OleDbDataAdapter to update a DataTable but got confused about the commands.
Since I sometimes get info from diffrent tables I can't use a CommandBuilder.
So I have tried to create the commands on my on but found it hard with the parameters.
DataTable.GetChanges returns rows that needs to use an INSERT or an UPDATE command - I guess I can't distinct between them.
I need you to complete the following:
DataTable dt = new DataTable();
OleDbDataAdapter da = new OleDbDataAdapter();
// Here I create the SELECT command and pass the connection.
da.Fill(dt);
// Here I make changes (INSERT/UPDATE) to the DataTable (by a DataGridView).
da.UpdateCommand = new OleDbCommand("UPDATE TABLE_NAME SET (COL1, COL2, ...) VALUES (#newVal1, #newVal2, ...) WHERE id=#id"); // How can I use the values of the current row (that the da is updating) as the parameters (#newVal1, #newVal2, id....)?
Thank you very much!
The data adapter can work in conjunction with the datatable. As such, I've actually wrapped mine together into a class and works quite well. Aside from the complexities of my stuff, here's a snippet that might help you along. When adding a parameter, you can identify the column source that the data is coming from FROM the DataTable. This way, when a record is internally identified as "Added" or "Updated" (or "Deleted"), when you build your SQL Insert/Update/Delete commands, it will pull the data from the columns from the respective rows.
For example. Say I have a DataTable, primary Key is "MyID" and has columns "ColX, ColY, ColZ". I create my DataAdapter and build out my select, update, delete commands something like... (? is a place-holder for the parameters)
DataAdapter myAdapter = new DataAdapter()
myAdapter.SelectCommand = new OleDbCommand();
myAdapter.InsertCommand = new OleDbCommand();
myAdapter.UpdateCommand = new OleDbCommand();
myAdapter.DeleteCommand = new OleDbCommand();
myAdapter.SelectCommand.CommandText = "select * from MyTable where MyID = ?";
myAdapter.InsertCommand.CommandText = "insert into MyTable ( ColX, ColY, ColZ ) values ( ?, ?, ? )";
myAdapter.UpdateCommand.CommandText = "update MyTable set ColX = ?, ColY = ?, ColZ = ? where MyID = ?";
myAdapter.DeleteCommand.CommandText = "delete from MyTable where MyID = ?";
Now, each has to have their respective "Parameters". The parameters have to be addded in the same sequence as their corresponding "?" place-holders.
// Although I'm putting in bogus values for preparing the parameters, its just for
// data type purposes. It does get changed through the data adapter when it applies the changes
OleDbParameter oParm = new OleDbParameter( "myID", -1 );
oParm.DbType = DbType.Int32;
oParm.SourceColumn = "myID"; // <- this is where it looks back to source table's column
oParm.ParameterName = "myID"; // just for consistency / readability reference
myAdapter.SelectCommand.Parameters.Add( oParm );
do similar for rest of parameters based on their types... char, int, double, whatever
Again, I have like a wrapper class that handles managment on a per-table basis... in brief
public myClassWrapper
{
protected DataTable myTable;
protected DataAdapter myAdapter;
... more ...
protected void SaveChanges()
{
}
}
Its more complex than just this, but during the "SaveChanges", The datatable and dataAdapter are in synch for their own purposes. Now, flushing the data. I check for the status of the table and then you can pass the entire table to the dataAdapter for update and it will cycle through all changed records and push respective changes. You'll have to trap for whatever possible data errors though.
myAdapter.Update( this.MyTable );
As it finds each "changed" record, it pulls the values from the Column Source as identified by the parameter that is found in the table being passed to the adapter for processing.
Hopefully this has given you a huge jump on what you are running into.
---- COMMENT PER FEEDBACK ----
I would put your update within a try/catch, and step into the program to see what the exception is. The message adn/or inner exception of the error might give more info. However, try to simplify your UPDATE to only include a FEW fields with the WHERE "Key" element.
Additionally, and I oopsed, missed this from first part answer. You might have to identify the datatable's "PrimaryKey" column. To do so, its a property of the DataTable that expects and array of columns that represent the primary key for the table. What I did was...
// set the primary key column of the table
DataColumn[] oCols = { myDataTbl.Columns["myID"] };
myDataTbl.PrimaryKey = oCols;
I would comment out your full update string and all its parameters for your UPDATE. Then, build it with just as simple as my sample of only setting 2-3 columns and the where clause
myAdapter.UpdateCommand.CommandText = "update MyTable set ColX = ?, ColY = ? where MyID=?";
Add Parameter object for "X"
Add Parameter object for "Y"
Add Parameter object for "MyID"
Pick fields like int or char so they have the least probability of problems for data type conversions, then, once that works, try adding all your "int" and "character" columns... then add any others. Also, which database are you going against. SOME databases don't use "?" as placeholder in the command but use "named" parameters, some using
"actualColumn = #namedCol"
or even
"actualColumn = :namedCol"
Hope this gets you over the hump...
You could use the String.Format Method to replace the #newVal1, #newVal2, ... in your code, like this da.UpdateCommand = new OleDbCommand(String.Format("UPDATE TABLE_NAME SET (COL1, COL2, ...) VALUES ({0}, {1}, ...) WHERE id=#id",OBJECT_ARRAY_CONTAINING_VALUES_FROM_THEDG));
[Eidt per comment]
To handle the row[0], row[1] you need a loop like:
for(i=0; i<rows.Count; i++)
{
da.UpdateCommand = new OleDbCommand(String.Format("UPDATE...",row[i]);
da.Update(dt);
}
Related
I'm trying to save my data from datagridview row to my sql database. My problem is with my current code, it reads and updates all the rows regardless if it has any changes or not. I know this is going to be a problem especially if my table has large amounts of data.
These are what I did with my code so far:
//retrieve data from dbase
public void loadToDGV()
{
DBConn.DBConnect();
SqlDataAdapter sqlDA = new SqlDataAdapter("SELECT * from TableName", DBConnection.conn);
sqlDA.Fill(dataTable);
gridView.DataSource = dataTable;
}
Below is what's in my Save button.
foreach (DataGridViewRow row in gridView.Rows)
{
DBConn.DBConnect();
SqlCommand comm = new SqlCommand();
comm.Connection = DBConnection.conn;
comm = new SqlCommand("SPName", DBConnection.conn);
comm.CommandType = CommandType.StoredProcedure;
comm.Parameters.AddWithValue("#ID", row.Cells["ID"].Value == DBNull.Value ? "" : row.Cells["ID"].Value);
comm.ExecuteNonQuery();
}
I have another sample code where I can only save modified rows
changeTable = dataTable.GetChanges(DataRowState.Modified);
foreach (DataRow row in changeTable.Rows)
{
DBConn.DBConnect();
SqlCommand comm = new SqlCommand();
comm.Connection = DBConnection.conn;
comm = new SqlCommand("SPName", DBConnection.conn);
comm.CommandType = CommandType.StoredProcedure;
comm.Parameters.AddWithValue("#ID", row["ID"].ToString());
comm.ExecuteNonQuery();
}
What I wanted to do is save only BOTH the "New Rows" AND "Modified Rows".
Is there any way to only get newly added/edited rows? and not include all the rows from the gridview?
My Stored Procedure only checks if the ID is existing or not.
IF NOT EXISTS (SELECT 1 FROM TABLE WHERE ID = #ID)
-- INSERT QUERY
ELSE
-- UPDATE QUERY
You should not be using a loop at all, nor even referring to the grid. You obviously have a DataTable already. If you're not already doing so, populate it with a data adapter, bind it to a BindingSource and bind that to the grid. When it's time to save, call EndEdit on the BindingSource call Update on the data adapter and pass the DataTable. That's it, that's all. There's no need to call GetChanges.
Obviously you will have to configure the data adapter appropriately, with an InsertCommand to insert new records and an UpdateCommand to save modified records. I won't go into specifics as you haven't gone into specifics but there are plenty of examples around. You can find my own here.
EDIT:
The short answer to your question is that, if you want both modified and added rows, then specify both Modified and Added rows. The DataRowState enumeration has the Flags attribute, which means that you can create compound values. That would mean that this:
changeTable = dataTable.GetChanges(DataRowState.Modified);
would become this:
changeTable = dataTable.GetChanges(DataRowState.ModifiedData Or RowState.Added);
If you were going to then loop through those rows, you wouldn't create a new command object every iteration. You'd create one command and add the parameters once, then simply set the Value of each parameter in the loop. You would also need to call AcceptChanges on the original DataTable afterwards.
You should do any of that though. As I said, you should use the same data adapter as you used to retrieve the data to save the changes. Normally you'd use different SQL for the InsertCommand and UpdateCommand but, in your case, you can use the same. Just create a single command object with the appropriate parameters and assign it to both the InsertCommand and UpdateCommand properties, then call Update.
I have this code in C#, but I need it to select all columns EXCEPT the first column of the table (the identity column), so that when I insert the data into an identical table in a different database, the destination database assigns its own identity column values:
SqlCommand commandSourceData = new SqlCommand($"SELECT * FROM dbo.{tableName};", sourceConnection);
SqlDataReader reader = commandSourceData.ExecuteReader();
Is there a way to do this?
If you want a generic solution for every column in your database you can use this kind of code
public string GetColumnsWithoutIdentity(string tableName, SqlConnection con)
{
SqlDataAdapter da = new SqlDataAdapter($"SELECT * FROM dbo.{tableName} where 1=0", con);
DataTable dt = new DataTable();
da.FillSchema(dt, SchemaType.Source);
var cols = dt.Columns.Cast<DataColumn>().Where(x => !x.AutoIncrement).Select(x => x.ColumnName);
return string.Join(",", cols);
}
Now you can use the returned string to build an Sql statement without the autoincrement column.
Notice that this code is vulnerable to Sql Injection. You should be absolutely sure that the tableName parameter used to build the first query is not typed directly by your user. Let it choose from a whitelist (readonly) of predefined tables (and also this is not 100% safe)
Another drawback is the fact that you need to hit the database two times. Once to get the schema with the info about the AutoIncrement column and one to fill the datatable after that.
Note: my office doesn't allow me to view YouTube and several other sites that probably have the answer to this question on them (they are blocked), which is why Googling the answer hasn't yielded results.
ComboBox code reference: found here
On my C# Form, I have filled a ComboBox with tables from a database (see below code), which returns the appropriate values and functions correctly:
public Form1()
{
InitializeComponent();
// Connection
SqlConnection conn = new SqlConnection();
conn.ConnectionString = "CONNECTION STRING" // shortened for security and convenience
// Fill ComboBox with SQL Values
conn.Open();
SqlCommand cmbTables = new SqlCommand("SELECT name FROM sys.tables", conn);
SqlDataReader read = cmbTables.ExecuteReader();
DataTable cmbData = new DataTable();
cmbData.Columns.Add("name", typeof(string));
cmbData.Load(read);
cmb1.DisplayMember = "name";
cmb1.DataSource = cmbData;
conn.Close();
}
After the ComboBox loads the tables (which works), the application then selects a table and clicks a button that loads the table, which is selected. This is where the code errors:
private void button1_Click(object sender, EventArgs e)
{
using (var connection = Utilities.GetConnection())
{
string table = Convert.ToString(txt1.Text);
string cmb1Value = Convert.ToString(cmb1.SelectedItem);
// Stored Procedure
SqlCommand select = new SqlCommand("EXECUTE STOREDPROCEDURE" + cmb1Value, connection); // shortened for security and convenience
select.Parameters.Add(new SqlParameter(cmb1Value, table));
// Data View
SqlDataAdapter ad= new SqlDataAdapter(select);
ad.SelectCommand = select;
DataTable dt = new DataTable();
ad.Fill(dt); // this generates the error "Incorrect Syntax Near '.'"
BindingSource b = new BindingSource();
b.DataSource = dt;
dGrid.DataSource = b;
ad.Update(dt);
connection.Close();
}
}
Even though the ComboBox loads the appropriate values, from the above code, I may be missing something which attaches those values to the SELECT stored procedure (all it does is call SELECT statement through a variable passed to it). The error, "Incorrect Syntax Near '.'" looks like a SQL Server error that I've seen, but can't remember how I generate it (this is how I usually troubeshoot where the TSQL code went wrong).\
Stored Procedure Related code:
C#:
SqlCommand select = new SqlCommand("EXECUTE STOREDPROCEDURE " + cmb1Value, connection);
TSQL:
CREATE PROCEDURE [STOREDPROCEDURE]
#TableName VARCHAR(250)
AS
BEGIN
DECLARE #sql NVARCHAR(MAX)
SET #sql = N'SELECT TOP 100 *
FROM ' + #TableName
EXECUTE(#sql)
END
-- Note this works in SSMS without a problem.
The above code is incorrect, and when I tweak the TSQL code, I generate similar errors, telling me that somewhere I am missing a conversion, or another variable because SQL Server isn't seeing these table values returned by the SELECT (first block of code). I can ascertain this because I have a second ComboBox that uses similar code EXCEPT that I populated the ComboBox with manual values, and it connects to the tables in the database with no error. So, the ComboBox, which grabs values from the database, that you see above, does not function correctly.
For instance, if I only add the below line of code to the code, I receive an error that it can't find the database "EXECUTE STOREDPROCEDURE System'
select.CommandType = CommandType.StoredProcedure;
However, System isn't a part of anything, so where did that come from? It never errored with this code on the manual ComboBox, as it had no trouble finding the database (using the same connection string, server and database!).
If I try to use a TSQL parameter, such as:
SqlCommand select = new SqlCommand("EXECUTE stp_ReturnTable #p", scon);
select.Parameters.Add(new SqlParameter("#p", cmb1Value));
Suddenly, it can't find the stored procedure. Again, the connection strings are identical for the manual ComboBox and the dynamic ComboBox.
I think the code behind the dynamic ComboBox is wrong. When I'm out of the office, I'll review some videos with detailed demonstrations on how to create a dynamic ComboBox from a database and I have a hunch that a system object is in the way (based on the System error, which exists nowhere in my code, as well as it suddenly being unable to find the database or procedure).
The missing key point in your code is the CommandType.
Without the proper set of this property the default is CommandText and thus the Framework expects a statement that starts with SELECT/INSERT/UPDATE/DELETE etc....
using (var connection = Utilities.GetConnection())
{
string table = Convert.ToString(txt1.Text);
string cmb1Value = Convert.ToString(cmb1.SelectedItem);
// Stored Procedure
SqlCommand select = new SqlCommand("STOREDPROCEDURE", connection);
select.Parameters.Add(new SqlParameter("#TableName", cmb1Value));
// That's the key to let ADO.NET accept the previous CommandText as valid.
// If you omit this the CommandText is assumed to be a SELECT/UPDATE/DELETE etc..
select.CommandType = CommandType.StoredProcedure;
// Data View
SqlDataAdapter ad= new SqlDataAdapter(select);
DataTable dt = new DataTable();
ad.Fill(dt);
BindingSource b = new BindingSource();
b.DataSource = dt;
dGrid.DataSource = b;
}
EDIT Having seen the code of the SP then you could simply set the SqlParameter name to the constant #TableName and pass the value extracted from the combobox as the value to be used inside the SP
EDIT I have looked again at your code and I suspect that the culprit is the line
string cmb1Value = Convert.ToString(cmb1.SelectedItem);
Looking at how you have filled your combo, this line, doesn't return the tablename as you expect, but the generic string System.Data.DataRowView because the DataSource of the combo is a DataTable and not a string collection. You should try to change that line in this way
DataRowView rw = cmb1.SelectedItem as DataRowView;
if(rw != null)
{
string cmbValue1 = rw["name"].ToString();
....
And yes, your code should work also without the CommandType.StoredProcedure line because the text EXECUTE sp param is recognized as a valid sql commandtext (but why do you use it when a direct call to the storedprocedure could be optimized for reuse?)
I have a performance problem with displaying data from an SQL database in my application. The problem is that I have a large number of parameters that I need to display (customers personal data, his current statistics etc.).
So far I've used either SqlCommand.ExecuteScalar (for single parameters), or DataTable.Rows[].ItemArray.GetValue() (for multiple parameters - I fill the DataTable with SqlDataAdapter whose query withdraws the necessary data from the database) and assigned their values to the appropriate control. Assuming that command is an SqlCommand type:
For single parameter
command.CommandText = "SELECT Parameter1 FROM MyTable WHERE Condition = Value";
textBox1.Text = command.ExecuteScalar().ToString();
For multiple parameters (SDA is a SqlDataAdapter):
command.CommandText="SELECT Parameter1 - ParameterN FROM MyTable WHERE Condition = Value";
SDA.SelectCommand = command;
SDA.Fill(MyDataTable);
textBox1.Text = MyDataTable.Rows[0].ItemArray.GetValue(0).ToString();
comboBox1.Text = MyDataTable.Rows[0].ItemArray.GetValue(1).ToString();
/*
I repeat similar lines of code for each parameter and display it in the appropriate control.
*/
This approach works correctly but when I have a large number of parameters (20+), it works very slowly.
Is there a more efficient way to display these amounts of data, and how would I implement it?
Thank you
Probably, with the second example, a SqlDataReader will perform better because you read the values just one time, while with a DataAdapter, you need to load the DataTable and then loop over the rows of the table (Effectively reading data two times).
command.CommandText="SELECT Field1,...,FieldN FROM MyTable WHERE Condition = Value";
SqlDataReader reader = command.ExecuteReader();
while(reader.Read())
{
// Of course this works correctly just if your query returns one row....
textBox1.Text = reader.GetString(0);
comboBox1.Text = reader.GetString(n);
}
You could also try with the Field<T> extension for the DataRow
command.CommandText="SELECT Field1,...,FieldN FROM MyTable WHERE Condition = Value";
SqlDataAdapter SDA = new SqlDataAdapter(command);
SDA.Fill(MyDataTable);
textBox1.Text = MyDataTable.Rows[0].Field<string>("Field1");
comboBox1.Text = MyDataTable.Rows[0].Field<string>("FieldN");
However, I think that the real performance gain would be in the query that you submit to the database engine and in the correct working of indexes on your tables.
Try to retrieve the minimun number of rows possible, search on indexed fields and/or change to a stored procedure.
here i had write sample stored procedure in wich you can get idea...
you can pass as amny parameter as you can in xml format and insert into temp table...
now you have table with value Name/value pair means Paramater name /value....
now you can do your furteher work...
/*
EXEC wa_TempGetDaya '<SampleXML>
<tblXML><AccountID>3</AccountID><Code>11</Code><Description>Leptospiral infect NEC</Description></tblXML>
</SampleXML>'
*/
CREATE PROCEDURE wa_TempGetDaya
(
#ParaXML NVARCHAR(MAX)
)
AS
SET NOCOUNT ON
BEGIN
DECLARE #AccountID INT
DECLARE #MyXML XML
SET #MyXML = #ParaXML
IF OBJECT_ID('tempdb..#TempData') IS NOT NULL
DROP TABLE #TempData
SELECT * INTO #TempData
FROM (
SELECT
Parse.value('(AccountID)[1]', 'INT') AS 'AccountID',
Parse.value('(Code)[1]', 'Varchar(100)') AS 'Code',
Parse.value('(Description)[1]', 'varchar(1000)') AS 'Description'
FROM
#MyXML.nodes('/SampleXML/tblXML') AS YourData(Parse)
) AS tbl
declare #para1 varchar(20)
declare #para2 varchar(20)
declare #para3 varchar(20)
SELECT #para1 =AccountID ,#para2 =Code,#para3 =Description from #TempICD
END
I have a web form. There are 20 fields that correspond to the columns in a database table. Let's say there's one record that has a BIRTHDATE column and I change its value from 13-July-2000 to 12-FEB-1985. But I don't touch the rest of the columns. Is there a way in C# to run an update statement like this:
UPDATE TABLE1 SET BIRHDATE=NEWVALUE WHERE ID=1111
instead of updating all the columns of the row like this:
UPDATE TABLE1 SET COLUMN1=NEWVALUE1, COLUMN2=NEWVALUE2,......,BIRTHDATE=NEWVALU
I think it would be a waste of resource. Am I wrong? I think DataAdapters are for this purpose but I'm not sure.
You can send a direct update statement to the Oracle Engine in this way.
using (OracleConnection cnn = new OracleConnection(connString))
using (OracleCommand cmd = new OracleCommand("UPDATE TABLE1 SET BIRHDATE=:NewDate WHERE ID=:ID", cnn))
{
cmd.Parameters.AddWithValue(":NewDate", YourDateTimeValue);
cmd.Parameters.AddWithValue(":ID", 111);
cnn.Open();
cmd.ExecuteNonQuery();
}
EDIT:
If you don't know which fields are changed (and don't want to use a ORM Tool) then you need to keep the original DataSource (a datatable, dataset?) used to populate initially your fields. Then update the related row and use a OracleDataAdapter.
using(OracleConnection cnn = new OracleConnection(connString))
using (OracleCommand cmd = new OracleCommand("SELECT * FROM TABLE1 WHERE 1=0", cnn))
{
OracleAdapter adp = new OracleDataAdapter();
adp.SelectCommand = cmd;
// The OracleDataAdapter will build the required string for the update command
// and will act on the rows inside the datatable who have the
// RowState = RowState.Changed Or Inserted Or Deleted
adp.Update(yourDataTable);
}
Keep in mind that this approach is inefficient because it requires two trip to the database. The first to discover your table structure, the second to update the row/s changed. Moreover, for the OracleDataAdapter to prepare the UpdateCommand/InsertCommand/DeleteCommand required, it needs a primary key in your table.
On the contrary, this is handy if you have many rows to update.
The last alternative (and probably the fastest) is a StoredProcedure, but in this case you need to go back to my first example and adapt the OracleCommand to use a StoredProcedure, (Add all fields as parameters, change CommandType to CommandType.StoredProcedure and change the text of the command to be the name of the StoredProcedure). Then the StoredProcedure will choose which fields need to be updated.