I working on windows appliaction which uses Microsoft access as database with oldedb data provider.
In this project I used to import xml file and write the data to database.
I want do bulk insert instead of inserting one record at one time.
So I tried with DAO approch but sometimes ended up with the exception like
"Currently locked unable to update"
Here is the code I used.
using TEST = Microsoft.Office.Interop.Access.Dao;
Pubic void Insert()
{
string sBaseDirectory = (AppDomain.CurrentDomain.BaseDirectory).ToString();
string sODBPath = sBaseDirectory + #"\TEST.accdb";
TEST.DBEngine dbEngine = new TESt.DBEngine();
TEST.Database db = dbEngine.OpenDatabase(sODBPath);
TEST.Recordset rsTest = db.OpenRecordset("dtTest");
for(int i=0;i<1000;i++)
{
rsTest.AddNew();
rsTest.Fields["ID"].Value =i;
rsTest.Fields["Name"].Value ="Test";
rsTest.update();
}
rsTest.close();
db.close();
}
With Oldedb:
DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
dt.Columns.Add("Name", typeof(string));
string TableSQl = "Select * from dtTest where ID=0";
OleDbDataAdapter dataAdapter=new OleDbDataAdapter(TableSQl,ConnectionString);
dataAdapter.InsertCommand = new OleDbCommand(INSERT);
OleDbConnection OleConn = new OleDbConnection(ConnectionString);
for(int i=0;i<1000;i++)
{
dataAdapter.InsertCommand.Parameters.Add("ID", OleDbType.BigInt, 8,i.ToString());
dataAdapter.InsertCommand.Parameters.Add("Name", OleDbType.BigInt, 8, "test");
}
dataAdapter.InsertCommand.Connection = OleConn;
dataAdapter.InsertCommand.Connection.Open();
dataAdapter.update(dt);
dataAdapter.InsertCommand.Connection.Close();
here its not inserting the records in table.
Please guide what is wrong with this code and good approach as well.
See http://msdn.microsoft.com/en-us/library/bbw6zyha(v=vs.80).aspx - 'Using Parameters with a DataAdapter'
The Add method of the Parameters collection takes the name of the
parameter, the DataAdapter specific type, the size (if applicable to
the type), and the name of the SourceColumn from the DataTable.
So you need to create the parameters correctly, populate the DataTable, and then call Update.
Is there any documentation to suggest that this approach will batch the inserts rather than doing them one at a time anyway?
Related
I'm using sqlite database which holds next table.
car ((int) vin_number, (int) number_plate, (string) name, (string) release_date)
What I'm trying to do is to fill DataTable with data from table and later on put it in datagridview in windows forms. This is what I have so far.
try
{
string query = "SELECT * FROM car";
using (SQLiteConnection sqlite_conn = new SQLiteConnection("Data Source=car.db;Version=3;New=True;Compress=True;"))
{
sqlite_conn.Open();
using (SQLiteDataAdapter da = new SQLiteDataAdapter(query, sqlite_conn))
{
DataTable dt = new DataTable();
da.Fill(dt);
data_grid_item.DataSource = dt;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
This works fine. The problem is, that sqlite only support a few data types. Since it also has no suppport for date, I decided to put attribute release date as string in format dd/MM/YYYY. Is there a way I can convert it to DateTime object in C# when filling DataTable so that my DataGridView can sort this column properly aswell?
If you want "strong" data-types...you can add a computed aka a calculated column to the data-table.
So when you run the query, some of the things are just gonna be "strings".
https://learn.microsoft.com/en-us/dotnet/api/system.data.datacolumn.expression?view=net-6.0
One use of the Expression property is to create calculated columns.
Pseudo Coding it:
DataColumn totalColumn = new DataColumn();
totalColumn.DataType = System.Type.GetType("System.Date");
totalColumn.ColumnName = "ReleaseDateAsDate";
totalColumn..Expression="Convert(ReleaseDateStringRaw, 'System.Date')"
Untested code.
Obviously, that is not considering "nulls" and strings that are not actually dates. That is a problem whenever people use string-db-columns for primitive data types that are not strings. (I know you are not doing this on purpose, this is a sqllite limitation in this case)
Side Notes.
Do not use "Select *". Specify your columns.
SQLiteConnection and MessageBox in the code snipplet.
That means you are not "layering" your application.
I strongly recommend internet-searching for "c# layered architecture"
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.
My application is extracting data from one DB and inserting the results into a different DB using MySqlDataAdapters and DataSets. I created a unique key in the target DB and want to update values in cases where I recalculated them.
I am hoping to use the data adapter Update method to handle this since there is a variable number of rows to INSERT/UPSERT each time. I also want the update to be as fast as possible since this is a repeating process.
If I exclude the possibility of update and just do inserts my code is functional as follows:
... // dsDSErrors is the dataset returned from my initial query
MySqlCommand cmdLocal = new MySqlCommand(queryLocal, myLocal);
MySqlDataAdapter daLocal = new MySqlDataAdapter(cmdLocal);
daLocal.MissingSchemaAction = MissingSchemaAction.AddWithKey;
DataSet dsLocal = new DataSet();
daLocal.Fill(dsLocal);
foreach (DataRow drError in dsDSErrors.Tables[0].Rows)
{
DataRow curRow = dsLocal.Tables[0].NewRow();
foreach (DataColumn hdr in dsDSErrors.Tables[0].Columns)
curRow[hdr.ColumnName] = drError[hdr.ColumnName];
dsLocal.Tables[0].Rows.Add(curRow);
}
MySqlCommandBuilder bldDSErrors = new MySqlCommandBuilder(daLocal);
daLocal.Update(dsLocal);
I understand that I can use daLocal.InsertCommand = new MySqlCommand(insert command) but was wondering if it is necessary to then loop through the results and add them to the VALUES section of the insert statement or if there was a method I'm not aware of that will handle this.
Barring built in functionality, I assume its best to use string builder to build up the insert query with all my values rows?
I have a table which has some 100-200 records.
I have fetch those records into a dataset.
Now i am looping through all the records using foreach
dataset.Tables[0].AsEnumerable()
I want to update a column for each record in the loop. How can i do this. Using the same dataset.
I'm Assumng your using a Data Adapter to Fill the Data Set, with a Select Command?
To edit the data in your Data Table and save changes back to your database you will require an Update Command for you Data Adapter. Something like this :-
SQLConnection connector = new SQLConnection(#"Your connection string");
SQLAdaptor Adaptor = new SQLAdaptor();
Updatecmd = new sqlDbCommand("UPDATE YOURTABLE SET FIELD1= #FIELD1, FIELD2= #FIELD2 WHERE ID = #ID", connector);
You will also need to Add Parameters for the fields :-
Updatecmd.Parameters.Add("#FIELD1", SQLDbType.VarCHar, 8, "FIELD1");
Updatecmd.Parameters.Add("#FIELD2", SQLDbType.VarCHar, 8, "FIELD2");
var param = Updatecmd.Parameters.Add("#ID", SqlDbType.Interger, 6, "ID");
param.SourceVersion = DataRowVersion.Original;
Once you have created an Update Command with the correct SQL statement, and added the parameters, you need to assign this as the Insert Command for you Data Adapter :-
Adaptor.UpdateCommand = Updatecmd;
You will need to read up on doing this yourself, go through some examples, this is a rough guide.
The next step is to Enumerate through your data table, you dont need LINQ, you can do this :-
foreach(DataRow row in Dataset.Tables[0].Rows)
{
row["YourColumn"] = YOURVALUE;
}
One this is finished, you need to call the Update() method of yout Data Adapter like so :-
DataAdapter.Update(dataset.Tables[0]);
What happens here, is the Data Adapter calls the Update command and saves the changes back to the database.
Please Note, If wish to ADD new rows to the Database, you will require am INSERT Command for the Data Adapter.
This is very roughly coded out, from the top of my head, so the syntax may be slightly out. But will hopefully help.
You should use original DataAdapter (adapter in code below) that was used to fill DataSet and call Update method, you will need CommandBuilder also, it depends what DB you are using, here is the example for SQL server :
SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
adapter.UpdateCommand = builder.GetUpdateCommand();
adapter.Update(dataset);
dataset.AcceptChanges();
Here is the good example :
http://support.microsoft.com/kb/307587
The steps would be something like:
- create a new DataColumn[^]
- add it to the data table's Columns[^] collection
- Create a DataRow [^] for example using NewRow[^]
- Modify the values of the row as per needed using Item[^] indexer
- add the row to Rows[^] collection
- after succesful modifications AcceptChanges[^]
Like this:
DataTable table = new DataTable();
table.Columns.Add("ID", typeof(int));
table.Columns.Add("ProductName");
table.Rows.Add(1, "Chai");
table.Rows.Add(2, "Queso Cabrales");
table.Rows.Add(3, "Tofu");
EnumerableRowCollection<DataRow> Rows = table.AsEnumerable();
foreach (DataRow Row in Rows)
Row["ID"] = (int)Row["ID"] * 2;
Add the column like below.
dataset.Tables[0].Columns.Add(new DataColumn ("columnname"));
Update the columns values like below.
for (int i = 0; i <= ds.Tables[0].Rows.Count - 1; i++)
{
dataset.Tables[0].Rows[i]["columnname"] = "new value here";
}
Update Database
dataset.AcceptChanges();
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.