I am trying to edit DataTable Filled by NpgsqlDataAdapter.
After calling Fill() method, I have only one row in DataTable. Then I changed value of one column only and tried to update as below.
Then I am getting this error:
DBConcurrencyException occured
My code is:
NpgsqlDataAdapter getAllData = new NpgsqlDataAdapter("SELECT sn,
code,product, unitprice, quantity, InvoiceNo, Date FROM stocktable WHERE Code='" + product + "'
ORDER BY EDate ASC", DatabaseConnectionpg);
DataTable ds1 = new DataTable();
ds1.Clear();
getAllData.Fill(ds1);
if (ds1.Rows.Count > 0)
{
ds1.Rows[0]["Quantity"] = qty;// calculated value
}
ds1 = ds1.GetChanges();
NpgsqlCommandBuilder cb = new NpgsqlCommandBuilder(getAllData);
//getAllData.RowUpdating += (sender2, e2) => { e2.Command.Parameters.Clear(); };
//cb.SetAllValues = false;
getAllData.DeleteCommand = cb.GetDeleteCommand();
getAllData.InsertCommand = cb.GetInsertCommand();
getAllData.UpdateCommand = cb.GetUpdateCommand();
int x = getAllData.Update(ds1);
if (x > 0)
{
ds1.AcceptChanges();
}
EDIT: I have three fields as primary keys and I am calling only two fields in select statement. Is it reason for DBConcurrency error? But I am able to update the table with same (three fields as primary key) parameters in SQL Server 2005.
UPDATE:
I found the solution and the solution is
I created and used second DataAdapter to update data.
I used getAllData(NpgSqlDataAdapter) To fill table as
NpgsqlDataAdapter getAllData = new NpgsqlDataAdapter("SELECT
code,product, unitprice, quantity, InvoiceNo, Date FROM stocktable WHERE Code='" + product + "'
ORDER BY EDate ASC", DatabaseConnectionpg);
And Also created next Adapter to update as
NpgsqlDataAdapter updateadap= new NpgsqlDataAdapter("SELECT sn, quantity FROM stocktable WHERE Code='" + product + "'
ORDER BY EDate ASC", DatabaseConnectionpg);
NpgsqlCommandBuilder cb = new NpgsqlCommandBuilder(updateadap);
//getAllData.RowUpdating += (sender2, e2) => { e2.Command.Parameters.Clear(); };
//cb.SetAllValues = false;
updateadap.DeleteCommand = cb.GetDeleteCommand();
updateadap.InsertCommand = cb.GetInsertCommand();
updateadap.UpdateCommand = cb.GetUpdateCommand();
int x = updateadap.Update(ds1);
if (x > 0)
{
......
}
I tried alot and found that NpgsqlDataAdapter had problem with Column Code. When i ommited it then it worked. DataType of column code is varchar. I don't know why this was happening. Anybody has idea about it?
This is because DataAdapter uses Optimistic Concurrency by default. This means that if you are trying to update a row that no longer exists in the database or changed, the update from the DataAdapter will fail with the exception above.
Possible scenarios:
Between you selecting the data into the client and sending the
update, another user is deleting or updating this row from his application.
It can be that you are deleting the data from somewhere else in your application.
For example:
You fill the DataTable that will be used for the update.
Deletes the row with Code = 1101 (for example) directly from the database, i.e. you do not use the DataTable here. This is emulating another user deleting the row with Code = 1101 from another application. Or some other part in your code deleting the row with Code = 1101.
Selects out the row with Code = 1101 from the DataTable, this is just to show that it is still there even though you have deleted it from the database itself.
Edits the Quantity column in the row with Code = 1101 in the DataTable. This has to be done, otherwise the call to Update will ignore this row when updating.
Executes the update, this will throw the exception since you are trying to update a row that (no longer) exists in the database.
If you want to implement Last Writer Wins, Add the following code:
cb.ConflictOption = ConflictOption.OverwriteChanges;
Also there is one more possible thing : if you have Decimal/numeric as columns in the DB they may cause this error even though the data looks the same. This is due to a decimal rounding error.
An important note:
You should always use parameterized queries by the way. This kind of string concatenations are open for SQL Injection.
Related
I'm populating a DGV with data from a database and everything works fine, with the exception of when 0 rows are returned to the DataTable that populates the DGV. And I'm not sure how exactly to handle that situation, which I do need to allow for due to the program requirements.
Here's the Form code that calls the data binding method:
public void PopulateDgvCategories()
{
string companyFilter = cboSelectCompany.Text;
string categoryFilter = cboSelectCategory.Text;
db categoriesData = new db();
if (categoryFilter == "All Categories")
{
string catsQuery = "SELECT id, category, old_value, old_desc, new_value, new_desc, reference1, reference2 " +
"FROM masterfiles.xref WHERE company_name = #company ORDER BY category, old_value";
this.dtCategories = categoriesData.GetDgvData(catsQuery, companyFilter, categoryFilter);
}
else
{
string catsQuery = "SELECT id, category, old_value, old_desc, new_value, new_desc, reference1, reference2 " +
"FROM masterfiles.xref WHERE company_name = #company and category = #category ORDER BY old_value";
this.dtCategories = categoriesData.GetDgvData(catsQuery, companyFilter, categoryFilter);
}
// Need to check this.dtCategories.Rows.Count
// If 0, then need to figure out how to handle that
dgvCategories.DataSource = this.dtCategories;
dgvCategories.Columns[0].Visible = false;
dgvCategories.Rows[0].Cells[0].Selected = false;
}
And here's how I'm retrieving the data and filling the DataTable:
public DataTable GetDgvData(string selectQuery, string companyFilter, string categoryFilter)
{
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
using (NpgsqlCommand cmd = new NpgsqlCommand(selectQuery, conn))
{
cmd.Parameters.Add(new NpgsqlParameter("company", companyFilter));
if (categoryFilter != "All Categories") cmd.Parameters.Add(new NpgsqlParameter("category", categoryFilter));
DataSet ds = new DataSet();
conn.Open();
using (NpgsqlDataAdapter da = new NpgsqlDataAdapter(cmd))
{
da.Fill(ds);
}
conn.Close();
return ds.Tables[0];
}
}
It is possible for the categoryFilter variable to cause the query to return 0 rows.
What is the best way to handle that? I'm thinking manually create the DGV with just the header row (column names) and display a MessageBox saying no rows were returned. Is that possible/feasible and would it be the best way to handle that?
There is absolutely no problem binding a datatable with zero rows - it will have a set of columns of the correct types regardless and will autocreate columns if the AutoGenerateColumns property is true
The problems come simply because you're trying to access a datagridview cell at 0,0 without making sure it exists, so you could put:
dgvCategories.DataSource = this.dtCategories;
dgvCategories.Columns[0].Visible = false;
it(dtCategories.Rows.Count > 0)
dgvCategories.Rows[0].Cells[0].Selected = false;
But you should just take this line out completely - it doesn't do anything anyway; it doesn't hide the blue selection highlight in the top left and a freshly bound datagridview's top left cell is already Selected=false so you're not achieving anything other than an exception with this line of code
If you want to hide the blue selection highlight when a DGV isn't focused, set the cell's Style.SelectionBackColor to be the same as its Style.BackColor, but do set it to something visible when the user focuses the grid or they will struggle to understand why they can't make that cell highlighted.
If you want to remove the ghost row that allows you to add new values, set AllowUserToAddRows to false
If you want to let the user know there were no rows from a recent search, place a label on top of the DGV with a "Your search 'whatever' returned no results" and set its visibility depending on whether there are rows:
lblNoResults.Text = $"Your search '{categoryFilter}' returned no rows";
lblNoResults.Visible = dtCategories.Rows.Count == 0;
Finally, you don't need to use a DataSet when all you want is a DataTable. A datadapter can fill a datatable; there is no need to go to the extra wasted effort of using a dataset - it's like using a List<string> and accessing it's 0th element when all you want is a string. You also don't need to open/close the connection - dataadapter knows how to do this itself. It is intelligent enough to not close a connection opened elsewhere (in case there is a transaction in progress):
DataTable dt = new DataTale();
using (NpgsqlDataAdapter da = new NpgsqlDataAdapter(cmd))
{
da.Fill(dt);
}
return dt;
I'm having a sudden and strange problem with DataTable. I'm using C# with MySQL database to develop a system, and I'm trying to export custom reports. The problem is that, somehow, my DataTable is getting only one result (I've tested my query on MySQL and should be something like 30 results on the xls file and the DataTable).
Strangely, these functions are used in other parts of the system to export other kinds of reports, and work perfectly. This is the select function that I'm using:
public DataTable selectBD(String tabela, String colunas) {
var query = "SELECT " + colunas + " FROM " + tabela;
var dt = new DataTable();
Console.WriteLine("\n\n" + query + "\n\n");
try
{
using (var command = new MySqlCommand(query, bdConn)) {
MySqlDataReader reader = command.ExecuteReader();
dt.Load(reader);
reader.Close();
}
}
catch (MySqlException) {
return null;
}
bdConn.Close();
return dt;
}
And this is my query:
SELECT
cpf_cnpj, nomeCliente, agenciaContrato, contaContrato,
regionalContrato, carteiraContrato, contratoContrato,
gcpjContrato, avalistaContrato, enderecoContrato,
telefoneContrato, dataChegadaContrato, dataFatoGerContrato,
dataPrimeiraParcelaContrato, dataEmissaoContrato, valorPlanilhaDebitoContrato
FROM
precadastro
INNER JOIN
contrato
ON precadastro.cpf_cnpj = contrato.FK_cpf_cnpj
LEFT JOIN faseprocessual
ON contrato.idContrato = faseprocessual.FK_idContrato
And that is the result of the query on SQLyog
I've tested and the DataTable returned by the function only receive the one row, and it's not the first row of the MySQL results. Someone had this kind of problem before?
DataTable load expects primary key from your data (supplied by DataReader) and tries to guess it from passed rows. Since there's no such key, Load method guesses it's the first column (cpf_cnpj). But, values in that column aren't unique so the each row gets overwritten by next one, and the result is just one row in your DataTable.
It's the issue that persist for years, and I'm not sure there's one solution to rule them all. :)
You can try:
change query so that some unique values get into first column (unfortunately, I can't see something unique in your screenshot) or concatenate two or more values to get unique value.
Prepare DataTable by yourself by creating columns (this mirroring structure of resultset) and then iterate through DataReader to copy data.
add some autoincrement value in your query (or make temporary table with auto_increment column then fill that table)
Last suggestion could be something like this (I haven't worked much with mySql, so this is some suggestion i have googled :)):
SELECT
#i:=#i+1 AS id,
cpf_cnpj, nomeCliente, agenciaContrato, contaContrato,
regionalContrato, carteiraContrato, contratoContrato,
gcpjContrato, avalistaContrato, enderecoContrato,
telefoneContrato, dataChegadaContrato, dataFatoGerContrato,
dataPrimeiraParcelaContrato, dataEmissaoContrato, valorPlanilhaDebitoContrato
FROM
precadastro
INNER JOIN
contrato
ON precadastro.cpf_cnpj = contrato.FK_cpf_cnpj
LEFT JOIN faseprocessual
ON contrato.idContrato = faseprocessual.FK_idContrato
CROSS JOIN (SELECT #i:= 0) AS i
here's answer on SO which uses auto number in query.
I'm currently using Mono on Ubuntu with MonoDevelop, running with a DataTable matching a table in the database, and should be attempting to update it.
The code following uses a Dataset loaded from an XML file, which was created from a Dataset.WriteXML on another machine.
try
{
if(ds.Tables.Contains(s))
{
ds.Tables[s].AcceptChanges();
foreach(DataRow dr in ds.Tables[s].Rows)
dr.SetModified(); // Setting to modified so that it updates, rather than inserts, into the database
hc.Data.Database.Update(hc.Data.DataDictionary.GetTableInfo(s), ds.Tables[s]);
}
}
catch (Exception ex)
{
Log.WriteError(ex);
}
This is the code for inserting/updating into the database.
public override int SQLUpdate(DataTable dt, string tableName)
{
MySqlDataAdapter da = new MySqlDataAdapter();
try
{
int rowsChanged = 0;
int tStart = Environment.TickCount;
da.SelectCommand = new MySqlCommand("SELECT * FROM " + tableName);
da.SelectCommand.Connection = connection;
MySqlCommandBuilder cb = new MySqlCommandBuilder(da);
da.UpdateCommand = cb.GetUpdateCommand();
da.DeleteCommand = cb.GetDeleteCommand();
da.InsertCommand = cb.GetInsertCommand();
da.ContinueUpdateOnError = true;
da.AcceptChangesDuringUpdate = true;
rowsChanged = da.Update(dt);
Log.WriteVerbose("Tbl={0},Rows={1},tics={2},", dt.TableName, rowsChanged, Misc.Elapsed(tStart));
return rowsChanged;
catch (Exception ex)
{
Log.WriteError("{0}", ex.Message);
return -1
}
I'm trying the above code, and rowsChanged becomes 4183, the number of rows I'm editing. However, when I use HeidiSQL to check the database itself, it doesn't change anything at all.
Is there a step I'm missing?
Edit: Alternatively, being able to overwrite all rows in the database would work as well. This is a setup for updating remote computers using USB sticks, forcing it to match a source data table.
Edit 2: Added more code sample to show the source of the DT. The DataTable is prefilled in the calling function, and all rows have DataRow.SetModified(); applied.
Edit 3: Additional information. The Table is being filled with data from an XML file. Attempting fix suggested in comments.
Edit 4: Adding calling code, just in case.
Thank you for your help.
The simplest way which you may want to look into might be to TRUNCATE the destination table, then simply save the XML import to it (with AI off so it uses the imported ID if necessary). The only problem may be with the rights to do that. Otherwise...
What you are trying to do can almost be handled using the Merge method. However, it can't/won't know about deleted rows. Since the method is acting on DataTables, if a row was deleted in the master database, it will simply not exist in the XML extract (versus a RowState of Deleted). These can be weeded out with a loop.
Likewise, any new rows may get a different PK for an AI int. To prevent that, just use a simple non-AI PK in the destination db so it can accept any number.
The XML loading:
private DataTable LoadXMLToDT(string filename)
{
DataTable dt = new DataTable();
dt.ReadXml(filename);
return dt;
}
The merge code:
DataTable dtMaster = LoadXMLToDT(#"C:\Temp\dtsample.xml");
// just a debug monitor
var changes = dtMaster.GetChanges();
string SQL = "SELECT * FROM Destination";
using (MySqlConnection dbCon = new MySqlConnection(MySQLOtherDB))
{
dtSample = new DataTable();
daSample = new MySqlDataAdapter(SQL, dbCon);
MySqlCommandBuilder cb = new MySqlCommandBuilder(daSample);
daSample.UpdateCommand = cb.GetUpdateCommand();
daSample.DeleteCommand = cb.GetDeleteCommand();
daSample.InsertCommand = cb.GetInsertCommand();
daSample.FillSchema(dtSample, SchemaType.Source);
dbCon.Open();
// the destination table
daSample.Fill(dtSample);
// handle deleted rows
var drExisting = dtMaster.AsEnumerable()
.Select(x => x.Field<int>("Id"));
var drMasterDeleted = dtSample.AsEnumerable()
.Where( q => !drExisting.Contains(q.Field<int>("Id")));
// delete based on missing ID
foreach (DataRow dr in drMasterDeleted)
dr.Delete();
// merge the XML into the tbl read
dtSample.Merge(dtMaster,false, MissingSchemaAction.Add);
int rowsChanged = daSample.Update(dtSample);
}
For whatever reason, rowsChanged always reports as many changes as there are total rows. But changes from the Master/XML DataTable do flow thru to the other/destination table.
The delete code gets a list of existing IDs, then determines which rows needs to be deleted from the destination DataTable by whether the new XML table has a row with that ID or not. All the missing rows are deleted, then the tables are merged.
The key is dtSample.Merge(dtMaster,false, MissingSchemaAction.Add); which merges the data from dtMaster with dtSample. The false param is what allows the incoming XML changes to overwrite values in the other table (and eventually be saved to the db).
I have no idea whether some of the issues like non matching AI PKs is a big deal or not, but this seems to handle all that I could find. In reality, what you are trying to do is Database Synchronization. Although with one table, and just a few rows, the above should work.
I have 2 DataTables: 1 that is filled with all the data from a table in my database called Ticket_Report, and a second one that is filled with new tickets.
I am searching the Ticket_report datatable for duplicates from the new ticket DataTable (and deleting them). Then I merge the new tickets datatable into the Ticket_report datatable.
I then try and Update the Ticket_Report table in SQL Server with the newly edited Ticket_report datatable. I am expecting the Update method to Update the table in SQL Server with rows that have matching primary keys from the datatable, and Insert rows that don't exist.
I am not getting any errors, however it doesn't seem like the rows are being updated/inserted.
con.Open();
DataTable ticketReportDT = new DataTable();
ticketReportDT = newTicketdt.Clone();
ticketReportDT.Columns[0].Unique = true;
SqlCommand cmd2 = new SqlCommand("SELECT * FROM ticket_report", con);
SqlDataAdapter da = new SqlDataAdapter(cmd2);
da.Fill(ticketReportDT);
da.UpdateCommand = new SqlCommandBuilder(da).GetUpdateCommand();
int currentTicketCount = ticketReportDT.Rows.Count;//3234
int newTicketCount = newTicketdt.Rows.Count; //339
//Removing Duplicate tickets in ticketReportDt
foreach (DataRow NewTicketsRow in newTicketdt.Rows)
{
foreach (DataRow currentTicketsRow in ticketReportDT.Select())
{
if (currentTicketsRow.Field<string>(ticketReportDT.Columns[0]) == NewTicketsRow.Field<string>(newTicketdt.Columns[0]))
{
currentTicketsRow.Delete();
}
}
}
ticketReportDT.AcceptChanges();
int currentTicketCount2 = ticketReportDT.Rows.Count;//2903, is less than last count because duplicates removed
ticketReportDT.Merge(newTicketdt);//add new tickets to ticketReportDT
ticketReportDT.AcceptChanges();
int currentTicketCount3 = ticketReportDT.Rows.Count;//3242, count went up because new tickets added
da.Update(ticketReportDT);//apply changes to database table Ticket_Report
con.Close();
Console.WriteLine("Ticket Transfer Complete");
This runs without error. To test, I did a count query in SQL Server before and after this update.
Before update:
select count(*) from TICKET_REPORT
// = 3234
After Update:
select count(*) from TICKET_REPORT
// = 3234 <-Should be 3242 (row count of ticketReportDT)
It seems like the Datatable has the correct amount of rows, but when I do the update, this does not reflect in SQL Server. Am I doing something wrong?
Since you have called ticketReportDT.AcceptChanges() before updating database it is not going to update anything in the database.
I have the following method created and previously stock1Label to stock3Label were able to output the correct values from the database however after i added more rows to my ProductsTable, source.Rows[0][0], [1][0], etc. seems to be taking values from row 8 onwards of my table instead of row 1, anyone know why this is happening?
private void UpdateStocks()
{
string query = "SELECT pQty FROM ProductsTable";
OleDbDataAdapter dAdapter = new OleDbDataAdapter(query, DBconn);
DataTable source = new DataTable();
dAdapter.Fill(source);
stock1Label.Text = source.Rows[0][0].ToString();
stock2Label.Text = source.Rows[1][0].ToString();
stock3Label.Text = source.Rows[2][0].ToString();
stock4Label.Text = source.Rows[3][0].ToString();
stock5Label.Text = source.Rows[4][0].ToString();
stock6Label.Text = source.Rows[5][0].ToString();
}
Most (all?) database systems do not have defined orders.
You will receive rows in non-determinstic storage order, not in the order you inserted them.
To receive a meaningful consistent ordering, you need to add an ORDER BY clause.