Force autoinc field with MySqlDataAdapter/MySqlCommandBuilder - c#

With C# and MySQL ADO.NET connector, I use MySqlDataAdapter and MySqlCommandBuilder to update a table.
Is it possible to temporarily force an autoinc field ?
DataRow row = ...
row["Id"] = 90; // force autoinc value
using (MySqlDataAdapter adapter = ...)
{
MySqlCommandBuilder builder = new MySqlCommandBuilder(adapter);
int result = adapter.Update(new DataRow[] { row });
// The inserted ID is 20 and not 90...

Yes you can, so long as you explicitly specify the value as in the code example that you provided, it will work. Off course it will fail if a duplicate value is specified.
The auto increment is only applied to an insert if you omitted the value or the value is NULL or 0 or DEFAULT.

#A.Baudouin I think you meant to say "Unfortunately, as you can see in my example above, it does not work"! Did you ever find a solution? I had exactly the same problem but found that it works OK if you use MySql Workbench to modify the value. All the Workbench does is a normal UPDATE command, so I am guessing this is a problem of MySqlAdapter, or maybe a "feature"! I ended up updating the database directly like:
MySqlHelper.ExecuteNonQuery(myConnection, "UPDATE MyTable SET Id = 90 WHERE Id = 7", null);
(or however you want to identify the correct row with WHERE). This works OK.

Related

DataAdapter_RowUpdated Event's row changes aren't reflected in DataSet and DataTable

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 am trying to edit my database but have it edit a specific column. Through Visual Studio 2019 C#

I am trying to edit my database where I can just edit one part of it aka an item price. However, I am not sure how I can compare the data in my database to an ID i'd have in an int variable. So it can then compare and when it finds the correct row its runs the code (which I think I have)
sqlite_cmd.CommandText = "Update TblAddCarParts SET PartName = #PartName, PartQuantity = #PartQuantity, PartPrice = #PartPrice";
sqlite_cmd.Parameters.AddWithValue("#PartName", txtPartName.Text);
sqlite_cmd.Parameters.AddWithValue("#PartQuantity", txtAddQuantity.Text);
sqlite_cmd.Parameters.AddWithValue("#PartPrice", txtItemCost.Text);
Like the commentors already stated:
The where clause is probably what you need.
The Text should then be like this:
sqlite_cmd.CommandText = "Update TblAddCarParts SET PartName = #PartName, PartQuantity = #PartQuantity, PartPrice = #PartPrice WHERE ID=#TheID";
sqlite_cmd.Parameters.AddWithValue("#PartName", txtPartName.Text);
sqlite_cmd.Parameters.AddWithValue("#PartQuantity", txtAddQuantity.Text);
sqlite_cmd.Parameters.AddWithValue("#PartPrice", txtItemCost.Text);
sqlite_cmd.Parameters.AddWithValue("#TheID", txtID.Text);
You said you have an ID, which you have to insert after the "=" and delete "TheID". But Attention: I do not know how your ID's column name is, so if the ID has another name (like idk PartID) then you have to change the parameter before the "=" symbol too, I just guessed! With this clause you can only edit one row. I hope I could make it more clear for you!

Getting an exception writing a boolean to sql using a datatable and SqlBulkCopy

It works fine for all other datatypes, but I am unable to get it to work with a 'bit' column.
This is my SQL for doing the bulk write:
using (var bulk = new SqlBulkCopy(connectionString, SqlBulkCopyOptions.KeepIdentity & SqlBulkCopyOptions.KeepNulls))
{
bulk.BatchSize = 2000;
bulk.DestinationTableName = targetTable;
bulk.WriteToServer(dataTable);
}
This is my datatable:
DataTable dt = new DataTable();
dt.Clear();
dt.Columns.Add("MyBool", typeof(bool)); // Tried with and without typeof(bool)
return dt;
This is how I construct the row before adding it to the datatable.
personAssociationRow["MyBool"] = true;
The exception is thrown on the WriteToServer line, and depending on if the typeof(bool) is specified is either:
Cannot insert the value NULL into column 'MyBool', table but intellisense/debugger shows the value as true
or
The given value of type String from the data source cannot be converted to type int of the specified target column. which is when the value in the intellisense/debugger becomes "True" ie a string.
In the database, the column is defined as bit and does not allow nulls.
Any ideas how I can get my boolean to work?
Edit : Just found and tried SqlBoolean as a type too but that didn't work, it said The given value of type SqlBoolean from the data source cannot be converted to type int of the specified target column. which suggests int would work, but it does not seem to.
Edit: I suspect the problem lies in it thinking the underlying database is type int when it is clear as purple crayon that the type is bit, hence the error message about it not converting to underlying type int.
Encountered the same issue: setting bool column to true/false was ignored by bulk copy, all values were set to null. The previous answer helped to resolve it, but I had to add mappings for all columns to make it work:
foreach (DataColumn column in table.Columns) {
sqlBulk.ColumnMappings.Add(column.ColumnName, column.ColumnName);
}
sqlBulk.WriteToServer(table);
You say: SqlBulkCopyOptions.KeepIdentity & SqlBulkCopyOptions.KeepNulls
You meant: SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.KeepNulls
The first one evaluates to zero.
I've fixed it, turns out it was a mapping problem. For some reason, all 20 other tables mapped completely fine, but this table didn't map correctly until I added the following:
bulk.ColumnMappings.Add("Ex", "Ex");

How can I add a new row to a table filled by an IDataReader?

I'm trying to add a new entry to a DataTable after I load it with the info contained in an IDataReader object, a DbDataReader concretely. The loading goes fine and the query to the database is correct I think.
When trying to add a new row I get an ArgumentException with the following info:
Cannot set column 'ID'. The value violates the MaxLength limit of this column.
This is the code I have right now:
// Build the query:
DbCommand cmd = dbCon.CreateCommand();
cmd.CommandText = "SELECT ID, Name || ' ' || SurName AS FullName FROM Clients ORDER BY FullName;";
// Execute it:
DbDataReader reader = cmd.ExecuteReader();
// Construct the table out of the query result:
DataTable table = new DataTable();
table .Load(reader);
table.Rows.Add(0, "None");
In the database (SQLite) ID is of type INTEGER and both Name and SurName are VARCHARs. Am I doing something wrong? How could 0 violate the MaxLength limit?
Finally you've solved the issue by using one of my favourite debugger "tricks" to un-mistify strange DataTable exceptions. From comments:
You can always look what causes the ConstraintException by using the
debugger. Execute table.Load(reader) in a QuickWatch Dialog Box.
Afterwards execute table.GetErrors(). Then you can look into the
returned rows at their RowError property. VoilĂ !
Since i'm using DbDataAdapter.Fill(DataTable) in almost all cases, i've forgotten that DataTable.Load(IDataReader) infers the schema based on the result set from the imported IDataReader. But of course it needs at least one DataRow to do so. That was the reason why it works when the DataTable wasn't empty.
Check the MaxLength property on your ID column in your datatable
size = dataTables(0).Columns(0).MaxLength
http://forums.asp.net/t/306971.aspx

Updating a column of each row in a table and saving changes in the database

For the video player I'm building I have a database of .mp4 video files created in Visual Studio's server explorer. The database has a single "Videos" table consisting of the following columns:
ArtistName,
SongTitle,
hddPath,
songDuration.
I have filled all except the songDuration column manually. I'm using a component for my video player which has a method that returns a song's duration after it has been loaded in a player I have set. Don't bother with the player, just bear in mind that the call component.getduration(hddPath), where hddPath is a string, returns a string in the following format: hh:mm:ss.
I can loop through the table rows and get the duration of each song in a for each statement:
DataTable dt = myDataSet.Tables["Videos"];
foreach(DataRow row in dt.Rows)
{
component.loadsound(0, row["hddPath"].ToString()); // 0 represents the index
// of the player
string duration = component.getduration(0); // gets the duration of the song
// loaded in player 0
}
But how do I update each row's "hddPath" column with the duration string and save it in my database?
I think that your question should actually read: "how do I update each row's songDuration column".
If that is the case, then have you tried:
row["songDuration"] = component.getduration(0);
and then outside of the loop:
yourDataAdapter.Update(dt);
There is more information on how to update records in the database here.
If you
have a DataAdapter associated with the DataSet
and have update commands associated with it (e.g., via a CommandBuilder instance)
and have a column in the DataTable for the duration (and assign the new value to it)
then you can send the updates to the database via the DataAdapter:
myDataAdapter.Update(myDataSet);
If you didn't use a DataAdapter to fill up the data set (which is what it sounds like from the OP), then it would also be possible to execute commands directly via an SqlCommand object. It might look something like the following statements. Note that you would want to simply update the parameter value in the loop and run the ExecuteNonQuery method each iteration (as opposed to creating a new command object each time). I'm unsure what the primary key (pk in the example) would be for your specific case. If the only fields are the ones shown in the OP, then I suspect it would the hddpath, since that sounds as if it would be unique.
SqlConnection myConn = new SqlConnection();
SqlCommand myCmd = myConn.CreateCommand();
myCmd.CommandText = "update thetable set duration = #duration where pk=#pk";
myCmd.CreateParameter();
SqlParameter duration = new SqlParameter("#duration", 1234);
myCmd.Parameters.Add(duration);
SqlParameter pk = new SqlParameter("#pk", 1);
myCmd.Parameters.Add(pk);
myCmd.ExecuteNonQuery();
There are a number of overloads for the SqlParameter objects. Pick the appropriate one.
Try something like this:
Create your update method something like this:
private void UpdateDurationofMyHddPath(string hdpath,string duration) //Add other parameters that you need
{
// Your ado.net update sql stuff
//Update yourColum Set duration=#duration where hddpath=#hdpath
}
Add Method on your loop:
DataTable dt = myDataSet.Tables["Videos"];
foreach(DataRow row in dt.Rows)
{
component.loadsound(0, row["hddPath"].ToString()); // 0 represents the index
// of the player
string duration = component.getduration(0); // gets the duration of the song
// loaded in player 0
string hdpath = row["hddPath"].ToString();
//Update hdpath duration
UpdateDurationofMyHddPath(hdpath,duration);
}
Regards

Categories