Comparing DataRows, adding table values dynamically - c#

I think it best I explain my scenario first.
I bring all my data back from a sql database, using SqlDataAdapters, within on transaction.
In an example, I have a college. I want open this college and add modules, and at the same time I wish to add students to these new modules.
These modules and students are saved to their respective DataTable, and the student table has a column relating to it's parent module, "moduleid".
My problem is that I need a way to save both of these in the same transaction, adding the new moduleid to it's child rows. I can create the new modules, and their own moduleid in it's datatable is updated, however when I now need to save the students to this module, I need to add it's moduleid, otherwise it's added to the database without one.
This is my effort so far but I feel I'm barking up the wrong tree.
DataTable dt_new_modules = ds_College.Tables["module"].GetChanges(DataRowState.Added);
da_modules.Update(ds_College.Tables["module"]);
ds_College.Tables["module"].AcceptChanges();
DataTable dt_added = ds_College.Tables["student"].GetChanges(DataRowState.Added);
if (dt_added != null)
{
if (dt_new_modules != null)
{
foreach (DataRow new_module in dt_new_modules.Rows)
{
foreach (DataRow updated_module in ds_College.Tables["module"].Rows)
{
if (updated_module.Equals(new_module))
{
foreach (DataRow new_student in dt_added.Rows)
{
if ((int)new_student["moduleid"] == (int)new_module["moduleid"])
new_student["moduleid"] = (int)updated_module["moduleid"];
}
}
}
}
}
da_student.Update(dt_added);
dt_added.AcceptChanges();
}
DataTable dt_modified = ds_College.Tables["student"].GetChanges(DataRowState.Modified);
if (dt_modified != null)
{
da_student.Update(dt_modified);
dt_modified.AcceptChanges();
}
I am trying to loop through all the added users and if the datarow is the same as the one before it was given it's new moduleid, then get the new id and give it to the user, however I feel there must be a more efficient way to do this.

If I get it right, your problem is with inserting child records for a parent module which was not yet inserted to the DB. I had somewhat the same issue, and using SqlCommandBuilder instead made it work.
Create SqlCommandBuilder (System.Data.SqlClient) objects for each table you are changing, passing the corresponding sqlAdapter as a parameter to the constructor.
It creates the INSERT, UPDATE and DELETE commands automatically, and will handle all the changes made in memory back to the database.
After the command builder objects are created, just call "Update" on the data adapter you had for the parent table (modules), and afterwards "Update" on the children table data adapter.
Hope this solves it.

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!

Insert current row from gridview into database

I'm supposed to create a master-detail form and I must add the details straight on the datagridview which is binded to the database. So I have a form with two tables: intrari (master) and intrari_detaliu (detail).
When I use the binding navigator to select a row in table intrari which is the parent table, I also get the corresponding details in table intrari_detaliu. I use text boxes/combox to add value in table intrari.
So how do I insert values straight into data grid view?
image for the form visual structure
What I tried:
First try:
DataClasses1DataContext db = new DataClasses1DataContext();
private void btnadd_Click(object sender, EventArgs e)
{
int ID; int id_intrari = 0; int id_produs = 0; decimal cantitate = 0; decimal valoare = 0;
for (int i = 0; i <tbl_intrari_detaliuDataGridView.RowCount - 1; i++)
{
if row
ID = Convert.ToInt32(tbl_intrari_detaliuDataGridView.Rows[i].Cells[0].Value.ToString());
id_intrari = Convert.ToInt32(tbl_intrari_detaliuDataGridView.Rows[i].Cells[1].Value.ToString());
id_produs = Convert.ToInt32(tbl_intrari_detaliuDataGridView.Rows[i].Cells[2].Value.ToString());
cantitate = Convert.ToDecimal(tbl_intrari_detaliuDataGridView.Rows[i].Cells[3].Value.ToString());
valoare = Convert.ToDecimal(tbl_intrari_detaliuDataGridView.Rows[i].Cells[4].Value.ToString());
var st = new tbl_intrari_detaliu
{
ID= ID,
id_intrari = id_intrari,
id_produs = id_produs,
cantitate = cantitate,
valoare = valoare,
};
db.tbl_intrari_detalius.InsertOnSubmit(st);
db.SubmitChanges();
}
}
but this first copies every row above and then it adds the new row. E.g.: I already have inserted row A and B, and when I want to insert row C, it first inserts row A and B again and inserts then C.
Second try:
public partial class Intrari2 : Form
{
SqlConnection con;
SqlDataAdapter adap;
DataSet ds;
SqlCommandBuilder cmbl;
private void Intrari2_Load(object sender, EventArgs e)
{
con = new SqlConnection();
con.ConnectionString = #"Data Source=DESKTOP-KVMM566;Initial
Catalog=MyDatabase;Integrated Security=True";
con.Open();
adap = new SqlDataAdapter("select ID,id_intrari,id_produs,cantitate,valoare from tbl_intrari_detaliu", con);
ds = new System.Data.DataSet();
adap.Fill(ds, "tbl_intrari_detaliu");
tbl_intrari_detaliuDataGridView.DataSource = ds.Tables[0];
this.tbl_intrari_detaliuTableAdapter.Fill(this.myDatabaseDataSet.tbl_intrari_detaliu);
this.tbl_IntrariTableAdapter.Fill(this.myDatabaseDataSet.tbl_Intrari);
//Add button------------
private void btnadd_Click(object sender, EventArgs e)
{
cmbl = new SqlCommandBuilder(adap);
adap.Update(ds, "tbl_intrari_detaliu");
//the Fill method doesn't work anymore because of the code above
this.tbl_intrari_detaliuTableAdapter.Fill(this.myDatabaseDataSet.tbl_intrari_detaliu);
}
}
This one updates rows correctly, but when I select a row in table intari, I don't get the corresponding details (according to the id) in table intrari_detaliu. I get the details for all rows in the parent table. I know it's because of the code I added to select the data from table using SqlAdapter, but the code doesn't work without that statement.
So, I need to insert/update/delete a selected row from "intrari_detaliu" and still be able to get the corresponding details when I select a row in the parent table "intrari".
Can you help me, please?
It's so much easier than that. Throw all that code away (genuinely, all of it). Here is a demo using two of my test tables. 1 Car (parent) has many Clowns (child)
Here's how your data (should) get into your parent grid. You have a query in your TA that selects stuff based on something you want to search by, eg Name:
You give the queries GOOD NAMES, not just Fill - one day you'll have many queries, don't do Fill, Fill1, Fill2. Name them well now:
In your child tableadapter you provide a query that does a lookup by Parent ID:
Or you do a query that looks up children of the same thing you just made a parent query for, like CarName here:
Or you can do both/more/whatever. You can have many queries per TA:
To get grids onto your form you drag them out of Data Sources (View menu, Other Windows). This will do all the data binding setup for you; you can examine how visual studio has done it if you ever want to replicate it manually; the parent grid binds through a binding source, to the table in the dataset. The child grid binds through a binding source to a data relation on the parent binding source
To get related data behavior you drag the CHILD NODE Clowns, not the top level Clowns out of data sources
In your code, to fill the grids with data you just actually fill the tables; the grids will update themselves. You must fill the parent first, then the child. You fill the child either using the query that selects a lot of children based on the parent criteria (like my ...ByCarName),
this.carsTableAdapter.FillByCarName(this.dataSet1.Cars, "Billy"); //fill parent first
this.clownsTableAdapter.FillByCarName(this.dataSet1.Clowns, "Billy"); //then fill the children by the same
or you can enumerate the parent after you fill, and fill the children by the parent ID (like my ...ByCarId does):
this.carsTableAdapter.FillByCarName(this.dataSet1.Cars, "Billy"); //fill parent first
clownsTableAdapter.ClearBeforeFill = false; //turn this off otherwise every call to FillBy.. erases the previous rows
foreach (var r in this.dataSet1.Cars) //for each parent
clownsTableAdapter.FillByCarId(this.dataSet1.Clowns, r.CarId); //fill the children
Saving
Dragging the grids wrote this:
this.Validate();
this.carsBindingSource.EndEdit();
this.tableAdapterManager.UpdateAll(this.dataSet1);
Add a line:
this.Validate();
this.carsBindingSource.EndEdit();
this.clownsBindingSource.EndEdit();
this.tableAdapterManager.UpdateAll(this.dataSet1);
That is all you need to do; click the button that runs that code; the tableadapter manager saves any changes you made to the data set, like adding a new row by writing in the bottom of the grid...
..or if you did it in code:
Regardless how it got there, it's a new row with the RowState of Added
When the tableadaptermanager.UpdateAll is called, or if you call an individual tableadapter.Update:
carsTableAdapter.Update(this.dataset1.Cars); //update doesn't just UPDATE, it runs INSERT and DELETE too
then the changes you made locally are saved. If someone else changed the data while you were editing it, and you have Optimistic Concurrency turned on (see above "Refresh the.." in the screenshot above) then you get an exception at the point of save. You can choose what to do to and write code that overwrites the new data, merges it, or discards the changes; ask the user what they would like to do
Note, if the DB is calculating your parent and child ID values, right clic on the relation line between your datatables, and ensure that Update is set to cascade:
This way when you add a new car and two new related clowns, and the car has -1 ID (generated locally by the dataset, pre-save), and the clowns use it too:
Then when the DB calculates the real ID, and "Refresh the dataset" option...
... causes the new ID to be retreived:
Then the changing of CarId -1 in the parent to e.g. 15 (what the DB calculated) causes the child Clowns' CarIds to be updated automatically too so the relation is preserved
TableAdapter.Update will save every kind of change; edits, deletions and inserts (new rows)

Programatically erasing a row in SQL within a foreach loop?

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

C# Manually added dataset, how to retrieve data to text columns

I have added a dataset to the solution (Windows Form) by "Add"-> New item ->DataSet & created a new tableadapter query that fetches desired data against passed parameter in the design time.
Now I want to assign the data filled in the tableadapter to few textboxes while a button is clicked.
How I can achieve this?
I think I found an answer specific to my situation. I am not sure whether it is the best or the standard, as I am not getting much help with searches, I am accepting my own finding as a solution.
private void getnameid2()
{
PersonDataSet newPersonDataSet = new PersonDataSet(); //PersonDataSet is the manually created dataset
PersonDataSetTableAdapters.L_PEOPLETableAdapter newPersonDataSetTableAdapter = new PersonDataSetTableAdapters.L_PEOPLETableAdapter();
DataTable mytable = new DataTable();
mytable = newPersonDataSetTableAdapter.GetData(decimal.Parse(this.civilidTextbox.Text.ToString()));
//foreach (DataRow row in newPersonDataSetTableAdapter.GetData(decimal.Parse(this.civilidTextbox.Text.ToString()))
foreach (DataRow row in mytable.Rows)
{
nameTextBox.Text = row["FIRST_NAME"].ToString();
personidTextBox.Text = row["PERSON_ID"].ToString();
}
// if (mytable.Rows.Count > 0) { MessageBox.Show(mytable.Rows.Count.ToString()); }
}
Now I am calling the procedure while cell is validating to avoid the already saved transactions being updated while browsing the records, I am checking the transaction id column and calling a return to avoid.
Hope this helps someone else out there or brings the attention of experts who can device better approach :)

Get Inserted Row after .Net Dataset Row Added

I have a .Net dataset and am adding a row to a table. This works and the record is saved to the database. How do I get the updated version of my row after the insert. Or, alternatively, how do I know the ID of the item that was added (so that I can then use it in a subsequent child table insert.
MyDataSet.ProjectRow r = dsMyDataSet.Projects.AddProjectRow(txtTitle.Text);
m_daProjects.Update(dsMyDataSet.Projects);
// What is the ID of the new item here?
If the column is an identity column you can find the new ID's in the inserted rows.
You: "thanks. which object maintains a list of inserted rows?"
You can use DataTable.GetChanges(DataRowState.Added) to get a DataTable with all DataRows which are going to be added. You need to use it before AcceptChanges was called. If i remember correctly TableAdapter.Update calls AcceptChanges at the end. Then you need to use it before m_daProjects.Update(dsMyDataSet.Projects):
DataTable addedRows = ds.modModel.GetChanges(DataRowState.Added);
MyDataSet.ProjectRow r = dsMyDataSet.Projects.AddProjectRow(txtTitle.Text);
m_daProjects.Update(dsMyDataSet.Projects);
now addedRows contains all DataRows with the new identity value in each row
foreach(DataRow addedRow in addedRows.Rows)
Console.WriteLine("New ID: {0}", addedRow.Field<int>("IdColumn"));
Update: However, in your case it's simpler. You have already the single row that you want to insert. So you dont need to call DataTable.GetChanges at all.
You can see the new identity value in the (typed DataRow) ProjectRow r after Update.
Thanks to Tim Schmelter. In the link he posted there's a reference to an article on Beth Massi's blog with a complete walkthrough of the solution. It worked for me.
http://blogs.msdn.com/bethmassi/archive/2009/05/14/using-tableadapters-to-insert-related-data-into-an-ms-access-database.aspx
The basic steps are:
1) Add RowUpdated event handler on the strongly typed table adapter. This event handler issues a new OleDBCommand to the database to retrieve ##Identity and then assigns the integer to the member column of the table.
public void _adapter_RowUpdated(dynamic sender, System.Data.OleDb.OleDbRowUpdatedEventArgs e)
{
HMUI.Classes.AccessIDHelper.SetPrimaryKey(this.Connection, e);
}
public static void SetPrimaryKey(OleDbConnection trans, OleDbRowUpdatedEventArgs e)
{
if (e.Status == System.Data.UpdateStatus.Continue && e.StatementType == System.Data.StatementType.Insert)
{
if (pk != null)
{
OleDbCommand cmdGetIdentity = new OleDbCommand("SELECT ##IDENTITY", trans);
// Execute the post-update query to fetch new ##Identity
e.Row.Table.Columns[pk(0)] = Convert.ToInt32(cmdGetIdentity.ExecuteScalar());
e.Row.AcceptChanges();
}
}
}
2) In the constructor of the form using the dataset and table adapter I attach the function in step 1 to the RowUpdated event on the table adapter's internal data adapter.
// Event to handle inserted records and retrieve the primary key ID
m_daDataSources.Adapter.RowUpdated += new System.Data.OleDb.OleDbRowUpdatedEventHandler(m_daDataSources._adapter_RowUpdated);

Categories