I'm trying to take advantage of the DataTable.Update method to update a SQL data source. Below is the code for the method that performs the update.
string connString = "SQL connection string...this works.";
string sqlSelect = "SELECT Payment, Amount, Date, Month, StartDate, EndDate, FROM Payment";
private void updateDataSource(DataTable dt) {
SqlDataAdapter da = new SqlDataAdapter(sqlSelect, connString);
SqlCommandBuilder cb = new SqlCommandBuilder(da);
int result = 0; // store the result of dt.Update
// copy over any rows from dt that have a payment date
DataTable temp = dt.Clone();
foreach (DataRow dr in dt.Rows) {
if (dr.ItemArray[5].ToString() != "") // if payment date is not empty
temp.ImportRow(dr);
}
da.ContinueUpdateOnError = true; // this forces an insert but does not update any other records
try {
result = da.Update(temp);
} catch (DBConcurrencyException dbce) {
alertUser(
#"There was an error updating the database.\n" +
dbce.Message + #"\n" +
#"The payment type id for the row was: " + dbce.Row.ItemArray[1] + #"\n" +
#"There were " + temp.Rows.Count + #" rows in the table to be updated.\n");
}
if (result == temp.Rows.Count) {
alertUser("Successful update."); // alert the user
btnSearchCancel_Click(null, null);
}
// store updated data in session variable to store data between posts to server
Session["gv"] = dt;
}
The above method is called when the user clicks an 'Update Table' button.
What is happening is before I included the da.ContinueUpdateOnError = true the try catch would throw the DBConcurrencyException giving Concurrency violation: the UpdateCommand affected 0 of the expected 1 records. And no records would be updated/inserted in the table.
After I added da.ContinueUpdateOnError = true the da.Update() would continue without error but, the first row of DataTable dt would still not be updated, however, the second row of dt would be inserted.
Even more strange is that when I am calling the update passing a table of ~20 rows the update executes perfectly, updating 2, or 3 rows and inserting 2 or three rows. If I call the update passing a table of 2 rows the exception is thrown. The two different tables have the same structure.
This error only occurs when, to quote MSDN
An attempt to execute an INSERT, UPDATE, or DELETE statement resulted
in zero records affected.
To get this error means that the database has changed since the DataTable was created.
The error tells you that
the UpdateCommand affected 0 of the expected 1 records
One of the records that was trying to be updated was not there anymore or had changed and no longer matches the expected signature.
For reference: DBConcurrencyException and DbDataAdapter.Update Method, a little more explanation.
It seems that there may be some other code that is changing the database after the DataTable is created, or you are running on a production DB and other users are making changes.
Related
My situation involves batch updates to individual tables in an SQLite database through ADO.NET objects. I use the DataAdapter.Update() method to push the changes which works well:
DataTable changes = dataset.Tables[table].GetChanges();
if (changes == null) return 0;
SQLiteCommandBuilder scb = new SQLiteCommandBuilder(adapter);
scb.ConflictOption = ConflictOption.CompareRowVersion;
int cnt = adapter.Update(changes);
return cnt;
However each time a record is inserted I also want the local DataSet tables to reflect with the newly inserted row id. For this I use the adapter_RowUpdated event :
static void adapter_RowUpdated(object sender,
System.Data.Common.RowUpdatedEventArgs e)
{
if (e.StatementType == StatementType.Insert)
{
SQLiteCommand cmd = new SQLiteCommand("select last_insert_rowid();", conn);
e.Row["id"] = cmd.ExecuteScalar();
}
}
The above fetches last_insert_rowid() because I'm able to see it when I debug by putting a breakpoint. However, the assignment statement to e.Row["id"] isn't working. The id change isn't reflected in my original DataSet and DataTable objects. For example when I test the following value (N refers to the specific row index), it still has a DBNull value. What is going wrong here? How can I ensure that the specific row which just got inserted is updated with its corresponding id field value?
dataset.Tables["projects"].row[N]["id"];
After a little experimenting, I found the solution to this myself.
As strange as it may sound but it looks like adapter.Update() requires a dataset along with the actual table name in order for this to work. I was passing the table object (DataTable.GetChanges()) so far which did the job of updating the database but failed only in this particular scenario. The moment I did that, the inserted id started reflecting in rows all over the dataset!
//int cnt = adapter.Update(changes); // doesn't work
int cnt = adapter.Update(dataset, tableName); // works perfectly!
edit
Lo and Behold! It even works when I just pass the table like this instead of entire dataset. It was only causing problem when I was just passing the changes table (got from dataset.Tables[tableName].GetChanges()).
int cnt = adapter.Update(dataset.Tables[tableName]); // works perfectly!
I have a relational database connecting meal_ingredients and ingredient nutritional values (see here for further information), utilising PostgreSQL. Within a WinForms application, there is a button that takes values from a DataGridView and then places each row into an array.
Each row in the DataGridView is an ingredient with its nutritional values. Above the DataGridView, a textbox takes a string for the meal name. Upon clicking the button (code below), the array values do one of two things:
If the meal name (meal_name being the PK) already exists in the meal_ingredients table, all rows in the database containing this string are removed. The rows from the DataGridView are then inserted, effectively 'overwriting' the ingredients for that meal.
If the meal name does not exist in the meal_ingredients table, the rows plus the meal name, entered into the textbox, are simply appended to the table.
In my code, as you can see once the data is placed into the array, a connection is made with the database, and the results from the SELECT query loaded into a DataTable.
The loop which follows triggers a MessageBox if the meal_name field matches the string value in the textbox. This works fine.
My issue is as follows. For however many rows exist in the DataGridView, the MessageBox will fire off that many times; so with two rows, I will see two MessageBoxes, for example. This, per se, is not a problem, unless replacing this MessageBox with DELETE and INSERT statements would throw an error.
In place of MessageBox.Show("test");, I would instead place a SQL statement to remove any records where meal_name == txtMealName.Text and then a second SQL statement to insert new records based upon the DataGridView rows. Of course, if the MessageBox fires off according to the number of rows, I expect the SQL would also occur that many times. Again, this is fine in principle. But I am just wondering if this would cause a conflict of any kind (that is, for example, the SQL throwing an exception because there are no remaining rows to delete)?
private void btnMealAdd_Click(object sender, EventArgs e)
{
if (txtMealName.Text != "")
{
foreach (DataGridViewRow row in dgvMealIngredient.Rows)
{
List<string> macroList = new List<string>();
macroList.Add(row.Cells[0].Value.ToString());
macroList.Add(row.Cells[9].Value.ToString());
macroList.Add(txtMealName.Text);
String[] str = macroList.ToArray();
NpgsqlConnection conn = new NpgsqlConnection(Globals.connectionString());
conn.Open();
NpgsqlCommand comm = new NpgsqlCommand();
comm.Connection = conn;
comm.CommandType = CommandType.Text;
comm.CommandText = "SELECT * FROM meal_ingredients";
NpgsqlDataReader dr = comm.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(dr);
foreach (DataRow dataRow in dt.Rows)
{
if (dataRow[0].ToString() == txtMealName.Text)
{
MessageBox.Show("test");
}
Debug.WriteLine(dataRow[0]);
}
}
}
else
{
MessageBox.Show("error: enter a meal name");
}
}
A simplified form of the database relation (note that qty is one of the fields in the DataGridView):
just whether replacing that with INSERT or DELETE will throw an error
An SQL DELETE can be typically run multiple times without error. There either will be some rows for it to delete or there will not but it will only normally result an error if there are dependent records in another table and no arrangement for them to be deleted or disconnected in cascade fashion. It is not an error for a DELETE statement to affect 0 rows
An SQL INSERT can typically only be run multiple times when it is not subsequently(after the first run) inhibited by the presence of a unique constraint on one or more of the columns. As most tables you design should really have a primary key, you can only insert a row with a unique value for the key column. If you aren't devolving generation of the value to the database then re-running an identical INSERT will fail on the second run. If the table depends on another table to have a related row and a foreign key constraint backs this up, then an insert that doesn't relate to a row in the parent table will fail on first run
I have a c# application with an SQLite database. It has a table called "Results_to_Risks" which I am trying to update. I first remove some rows in my local datatable, after which I use my update table function to update the table in the database.
foreach(var row in temp)
{
allResToRisks.Rows.Remove(row);
}
_databaseController.UpdateTable(allResToRisks, "Results_to_Risks");
My database controller update table method:
// Updates an entire database table.
public void UpdateTable(DataTable datatable, string table, string condition = "")
{
using (SQLiteConnection dbConnection = new SQLiteConnection(conString))
{
using (var sqliteAdapter = new SQLiteDataAdapter("SELECT * FROM '" + #table + "'" + #condition, dbConnection))
{
dbConnection.Open();
using(var builder = new SQLiteCommandBuilder(sqliteAdapter))
{
sqliteAdapter.Update(datatable);
}
}
}
}
In this particular case, I remove just one row. The datatable ends up empty (Rows.Count == 0), because this was the only row in that datatable. So nothing goes wrong there. However my table in the database still has the one row inside it.
What am I doing wrong?
I saw this on the MDSN website:
It is important to understand the difference between deleting a row in a DataTable and removing the row. When you call the Remove or RemoveAt method, the row is removed immediately. Any corresponding rows in the back end data source will not be affected if you then pass the DataTable or DataSet to a DataAdapter and call Update. When you use the Delete method, the row remains in the DataTable and is marked for deletion. If you then pass the DataTable or DataSet to a DataAdapter and call Update, the corresponding row in the back end data source is deleted.
So i tried to use Delete() instead of Remove(). However, this made no difference.
as there is so many of them, examples.. i was researching online, though they do not show use of SqlBulkCopy, in the folowing scenario :
i have used a query in order to fetch existing Data from SqlServer(2008), into a DataTable,
so i could sort the data locally, and avoid hitting database while processing.
so now, at that stage, i already have the option to clone the source dataTable Schema
using localDataTable = DataTableFromOnlineSqlServer.Clone();
by doing that Clone(), i now have all the columns, and each of the column-dataType.
then in next stage of the program, i am filling the Cloned-From-Db, - that Local (yet Empty) new DataTable with some new data .
so by now i have a populated DataTable, and it's ready to be stored in sql server .
using this code below yeld no results
public string UpdateDBWithNewDtUsingSQLBulkCopy(DataTable TheLocalDtToPush, string TheOnlineSQLTableName)
{
// Open a connection to the AdventureWorks database.
using (SqlConnection connection = new SqlConnection(RCLDBCONString))
{
connection.Open();
// Perform an initial count on the destination table.
SqlCommand commandRowCount = new SqlCommand("SELECT COUNT(*) FROM "+TheOnlineSQLTableName +";", connection);
long countStart = System.Convert.ToInt32(commandRowCount.ExecuteScalar());
var nl = "\r\n";
string retStrReport = "";
retStrReport = string.Concat(string.Format("Starting row count = {0}", countStart), nl);
retStrReport += string.Concat("==================================================", nl);
// Create a table with some rows.
DataTable newCustomers = TheLocalDtToPush;
// Create the SqlBulkCopy object.
// Note that the column positions in the source DataTable
// match the column positions in the destination table so
// there is no need to map columns.
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
bulkCopy.DestinationTableName = TheOnlineSQLTableName;
try
{
// Write from the source to the destination.
bulkCopy.WriteToServer(newCustomers);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
// Perform a final count on the destination
// table to see how many rows were added.
long countEnd = System.Convert.ToInt32(
commandRowCount.ExecuteScalar());
retStrReport += string.Concat(string.Format("Ending row count = {0}", countEnd), nl);
retStrReport += string.Concat("==================================================", nl);
retStrReport += string.Concat(string.Format("{0} rows were added.", countEnd - countStart),nl);
retStrReport += string.Concat("New Customers Was updated successfully", nl, "END OF PROCESS !");
Console.ReadLine();
return retStrReport;
}
}
now The problem is, that No data was inserted at all.
i have made some resarch and there is no solution for me
i also checked to make sure that :
all the columns of source and destination are aligned (although
it is a clone so no wories)
that there is a PK set as IDENTITY
column on the Sql server table
what am i missing here ?
...the "report" i have made, in order to calculate inserted rows tells
" 0 rows were added "
and thats it no Errors or exeptions reported /thrown .
This test code is really straightforward
var addedRows1 = (securityDataTable.GetChanges(DataRowState.Added));
MessageBox.Show(addedRows1.Rows[1].RowState.ToString());
MessageBox.Show(addedRows1.Rows.Count.ToString());
addedRows1.Rows[1].AcceptChanges();
var addedRows2 = (securityDataTable.GetChanges(DataRowState.Added));
MessageBox.Show(addedRows2.Rows[1].RowState.ToString());
MessageBox.Show(addedRows2.Rows.Count.ToString());
The 4 MessageBox show, in order, the following messages:
Added
3
Added
3
I would expect the count to return 2 on the last message. Why isn't that the case and can this be fixed by any mean? Note: The DataTable is not linked to a table nor a particular data source.
EDIT: Note that the RowState is ok (set to Unchanged) if I don't requery the GetChanges() the second time
GetChanges returns a copy of the rows. Are you using a data adapter to fill your data table? MSDN recommends calling AcceptChanges on the DataAdapter'
private void UpdateDataTable(DataTable table,
OleDbDataAdapter myDataAdapter)
{
DataTable xDataTable = table.GetChanges();
// Check the DataTable for errors.
if (xDataTable.HasErrors)
{
// Insert code to resolve errors.
}
// After fixing errors, update the database with the DataAdapter
myDataAdapter.Update(xDataTable);
}
Edit
Since you are just using a datatable, you could create a query for the rows that are added and call AcceptChanges on that row:
DataRow[] addedRows = datatable.Select(null, null, DataViewRowState.Added);
foreach (DataRow _ddr in addedRows)
{
_ddr.AcceptChanges();
}