I'm trying to figure out where DataRowCollection.Add(DataRow row) inserts the new row into its datatable. Is it at the end of the table, like an append? Is it random?
Also, I want to use this while I'm for looping through a datatable. If some condition exists, add a new row containing different data to run through the For loop to the end of the datatable. Are there any specific problems with this approach? How else might I handle it?
EDIT: I am For Looping through a .Net DataTABLE stored in memory. I'm not touching the dataBASE where the original data is stored during this looping operation. The DataTABLE is populated prior to the loop and is not a problem.
Here is relavant code:
DataTable machineANDlastDate = new DataTable();
//Populate machineANDlastDate
for (int i = 0; i < machineANDlastDate.Rows.Count; i++)
{
lastFutureDate = DateTime.Parse(machineANDlastDate.Rows[i]["MaxDueDate"].ToString());
newDateTime = lastFutureDate.AddDays(frequency); //This is where the new date is created.
machineSerial = machineANDlastDate.Rows[i]["machineSerial"].ToString();
if (newDateTime < DateTime.Now)
{
machineANDlastDate.Rows.Add(new String[] { machineSerial, newDateTime.AddDays(frequency).ToString() });
continue;
}
...Removed for irrelevancy...
}
Is this a valid way to add a row to the end of the datatable?
As far as I know, it is always added to the end of the collection.
If you for loop through the database, there shouldn't be a problem, if you begin at the beginning of the data table and finish at the end of it or smth similar. However, you will then also loop through the newly created data rows, and I don't know whether you want to achieve this. You only could get problems if you take a foreach loop instead because it cannot handle modifications of the underlying collection.
If you want to know if a row is new or not you can check the DataRow.RowState property.
// your code to add rows
...
// process added rows
foreach (DataRow row in machineANDlastDate.Rows)
{
if (row.RowState == DataRowState.Added)
{
// do stuff
}
}
// now confirm new rows (they won't have a RowState of Added after this)
machineANDlastDate.AcceptChanges();
It's always at the end of the table, as far as i know most of the DataBase conectors, whe you use their add row, its always at the end.
The Add method will insert a DataRow into a DataRowCollection object only. To actually add the DataRow to the data table, you will need to call the NewRow method which appends itself onto the DataTable, and thus appends the row to the table in that database. For reference, check out http://msdn.microsoft.com/en-us/library/9yfsd47w.aspx
Related
I'm trying to use the update() method, but it is inserting my datatable data into my database without checking if the row exists, so it is inserting duplicate data. It is also not deleting rows that don't exist in datatable. How to resolve this? I want to synchronize my datatable with server table.
private void Form1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'MyDatabaseDataSet11.Vendor_GUI_Test_Data' table. You can move, or remove it, as needed.
this.vendor_GUI_Test_DataTableAdapter.Fill(this.MyDatabaseDataSet11.Vendor_GUI_Test_Data);
// read target table on SQL Server and store in a tabledata var
this.ServerDataTable = this.MyDatabaseDataSet11.Vendor_GUI_Test_Data;
}
Insertion
private void convertGUIToTableFormat()
{
ServerDataTable.Rows.Clear();
// loop through GUIDataTable rows
for (int i = 0; i < GUIDataTable.Rows.Count; i++)
{
String guiKEY = (String)GUIDataTable.Rows[i][0] + "," + (String)GUIDataTable.Rows[i][8] + "," + (String)GUIDataTable.Rows[i][9];
//Console.WriteLine("guiKey: " + guiKEY);
// loop through every DOW value, make a new row for every true
for(int d = 1; d < 8; d++)
{
if ((bool)GUIDataTable.Rows[i][d] == true)
{
DataRow toInsert = ServerDataTable.NewRow();
toInsert[0] = GUIDataTable.Rows[i][0];
toInsert[1] = d + "";
toInsert[2] = GUIDataTable.Rows[i][8];
toInsert[3] = GUIDataTable.Rows[i][9];
ServerDataTable.Rows.InsertAt(toInsert, 0);
//printDataRow(toInsert);
//Console.WriteLine("---------------");
}
}
}
Trying to update
// I got this adapter from datagridview, casting my datatable to their format
CSharpFirstGUIWinForms.MyDatabaseDataSet1.Vendor_GUI_Test_DataDataTable DT = (CSharpFirstGUIWinForms.MyDatabaseDataSet1.Vendor_GUI_Test_DataDataTable)ServerDataTable;
DT.PrimaryKey = new DataColumn[] { DT.Columns["Vendor"], DT.Columns["DOW"], DT.Columns["LeadTime"], DT.Columns["DemandPeriod"] };
this.vendor_GUI_Test_DataTableAdapter.Update(DT);
Let's look at what happens in the code posted.
First this line:
this.ServerDataTable = this.MyDatabaseDataSet11.Vendor_GUI_Test_Data;
This is not a copy, but just an assignment between two variables. The assigned one (ServerDataTable) receives the 'reference' to the memory area where the data coming from the database has been stored. So these two variables 'point' to the same memory area. Whatever you do with one affects what the other sees.
Now look at this line:
ServerDataTable.Rows.Clear();
Uh! Why? You are clearing the memory area where the data loaded from the database were. Now the Datatable is empty and no records (DataRow) are present there.
Let's look at what happen inside the loop
DataRow toInsert = ServerDataTable.NewRow();
A new DataRow has been created, now every DataRow has a property called RowState and when you create a new row this property has the default value of DataRowState.Detached, but when you add the row inside the DataRow collection with
ServerDataTable.Rows.InsertAt(toInsert, 0);
then the DataRow.RowState property becomes DataRowState.Added.
At this point the missing information is how a TableAdapter behaves when you call Update. The adapter needs to build the appropriate INSERT/UPDATE/DELETE sql command to update the database. And what is the information used to choose the proper sql command? Indeed, it looks at the RowState property and it sees that all your rows are in the Added state. So it chooses the INSERT command for your table and barring any duplicate key violation you will end in your table with duplicate records.
What should you do to resolve the problem? Well the first thing is to remove the line that clears the memory from the data loaded, then, instead of calling always InsertAt you should first look if you have already the row in memory. You could do this using the DataTable.Select method. This method requires a string like it is a WHERE statement and you should use some value for the primarykey of your table
var rows = ServerDataTable.Select("PrimaryKeyFieldName = " + valueToSearchFor);
if you get a rows count bigger than zero then you can use the first row returned and update the existing values with your changes, if there is no row matching the condition then you can use the InsertAt like you are doing it now.
You're trying too hard, I think, and you're unfortunately getting nearly everything wrong
// read target table on SQL Server and store in a tabledata var
this.ServerDataTable = this.MyDatabaseDataSet11.Vendor_GUI_Test_Data;
No, this line of code doesn't do anything at all with the database, it just assigns an existing datatable to a property called ServerDataTable.
for (int i = 0; i < GUIDataTable.Rows.Count; i++)
It isn't clear if GUIDataTable is strongly or weakly typed, but if it's strong (I.e. it lives in your dataset, or is of a type that is a part of your dataset) you will do yourself massive favors if you do not access it's Rows collection at all. The way to access a strongly typed datatable is as if it were an array
myStronglyTypedTable[2] //yes, third row
myStronglyTypedTable.Rows[2] //no, do not do this- you end up with a base type DataRow that is massively harder to work with
Then we have..
DataRow toInsert = ServerDataTable.NewRow();
Again, don't do this.. you're working with strongly typed datatables. This makes your life easy:
var r = MyDatabaseDataSet11.Vendor_GUI_Test_Data.NewVendor_GUI_Test_DataRow();
Because now you can refer to everything by name and type, not numerical index and object:
r.Total = r.Quantity * r.Price; //yes
toInsert["Ttoal"] = (int)toInsert["Quantity"] * (double)toInsert["Price"]; //no. Messy, hard work, "stringly" typed, casting galore, no intellisense.. The typo was deliberate btw
You can also easily add data to a typed datatable like:
MyPersonDatatable.AddPersonRow("John, "smith", 29, "New York");
Next up..
// I got this adapter from datagridview, casting my datatable to their format
CSharpFirstGUIWinForms.MyDatabaseDataSet1.Vendor_GUI_Test_DataDataTable DT = (CSharpFirstGUIWinForms.MyDatabaseDataSet1.Vendor_GUI_Test_DataDataTable)ServerDataTable;
DT.PrimaryKey = new DataColumn[] { DT.Columns["Vendor"], DT.Columns["DOW"], DT.Columns["LeadTime"], DT.Columns["DemandPeriod"] };
this.vendor_GUI_Test_DataTableAdapter.Update(DT);
Need to straighten out the concepts and terminology in your mind here.. that is not an adapter, it didn't come from a datagridview, grid views never provide adapters, your datatable variable was always their format and if you typed it as DataTable ServerDataTable then that just makes it massively harder to work with, in the same way that saying object o = new Person() - now you have to cast o every time you want to do nearly anything Person specific with it. You could always declare all your variables in every program, as type object.. but you don't.. Hence don't do the equivalent by putting your strongly typed datatables inside DataTable typed variables because you're just hiding away the very things that make them useful and easy to work with
If you download rows from a database into a datatable, and you want to...
... delete them from the db, then call Delete on them in the datatable
... update them in the db, then set new values on the existing rows in the datatable
... insert more rows into the db alongside the existing rows, then add more rows to the datatable
Datatables track what you do to their rows. If you clear a datatable it doesn't mark every row as deleted, it just jettisons the rows. No db side rows will be affected. If you delete rows then they gain a rowstate of deleted and a delete query will fire when you call adapter.Update
Modify rows to cause an update to fire. Add new rows for insert
As Steve noted, you jettisoned all the rows, added new ones, added (probably uselessly) a primary key(the strongly typed table will likely have already had this key) which doesn't mean that the new rows are automatically associated to the old/doesn't cause them to be updated, hen inserted a load of new rows and wrote them to the db. This process was never going to update or delete anything
The way this is supposed to work is, you download rows, you see them in the grid, you add some, you change some, you delete some, you hit the save button. Behind the scenes the grid just poked some new rows into the datatable, marked some as deleted, changed others. It didn't go to the huge (and unfortunately incorrect) lengths your code went to. If you want your code to behave the same you follow the same idea:
var pta = new PersonTableAdapter();
var pdt = pta.GetData(); //query that returns all rows
pta.Fill(somedataset.Person); //or can do this
pdt = somedataset.Person; //alias of Person table
var p = pdt.FindByPersonId(123); //PersonId is the primary key in the datatable
p.Delete(); //mark person 123 as deleted
p = pdt.First(r => r.Name = "Joe"); //LINQ just works on strongly typed datatables, out of the box, no messing
p.Name = "John"; //modify joes name to John
pdt.AddPersonRow("Jane", 22);
pta.Update(pdt); //saves changes(delete 123, rename joe, add Jane) to db
What you need to appreciate is that all these commands are just finding or creating datarow obj3cts, that live inside a table.. the table tracks what you do and the adapter uses appropriate sql to send changes to the db.. if you wanted to mark all rows in a datatable as deleted you can visit each of them and call Delete() on it, then update the datatable to save the changes to the db
I'd like to know if there is a way to automatically add columns in a DataTable inside a foreach loop? That is what I mean with "automatically".
I had in mind something like this:
DataTable dt = new DataTable();
foreach (var item in model.Statistik)
{
dt.Columns.Add();
row = dt.NewRow();
row[//Name of column goes here] = // either item or item.property;
dt.Rows.Add(row);
}
This is much more efficient than explicitly name every column before the for each loop. Because, what happens if some property changes or gets deleted, etc.? This is therefore, something I wish to get rid of.
I don't think you understand how dt.Columns and dt.Rows are related. There is one set of Columns for a DataTable. Every Row in the DataTable has all the matching columns - you don't have unique Columns in each Row. So every time you do dt.Columns.Add you are adding a column to every Row, old or new.
Also, how do you expect Row[<name of column>] to work if you don't specify the name to the DataTable?
I want to do this:
public void UpdateDataRowsToDataTable(string tableName, List<DataRow> rows)
{
foreach (DataRow row in rows)
{
// this method does not exist!!
_dataSet.Tables[tableName].Rows.Update(row);
}
}
I need a method that finds a row (maybe by primary key) and updates changed rows to the row in DataTable.
The only possibility what I know is to use Select (or Find) and then maybe iterate all columns and give them new values. Please tell me that this cannot be true!
Although the question is not quite clear it sounds like you have a group of rows from one table and want to update the equivalent rows in another datatable. If this is the case they you can just use the find method and manually update them as you suggested, or alternatively, add the new rows to another table and merge them (there are all sorts of options for merging two data tables). Merging however will just do the same thing under the hood (i.e. find by primary key and update the columns).
Another way would be to just replace the row and set its status to modified datarow.SetModified()
Or you can delete the old one and add the new one...
Could you use boxing to do that if your datatables are the same.
DestDataset.DestDataTable newChildRecords =
(DestDataset.DestDataTable)_dataset.Tables[tableName].GetChanges(DataRowState.Added);
If you are using dataAdapter...
foreach (DataRow row in rows)
{
Row.BeginEdit();
//Do your stuff
Row.EndEdit();
}
dataAdapter.Update(_dataSet, "yourTable");
Another option is to use the ItemArray property to update the DataRow instance. The only thing is that you still have to select the proper rows. Also, note that you have to change the whole ItemArray instead of its elements in order to have the modification reflected in the DataTable.
foreach (DataRow row in rows)
{
// Select the row
var rows = _dataSet.Tables[tableName].Select(string.Format("Table_ID = {0}", row["Table_ID"]));
// Update the row
if (0 < rows.Length)
rows[0].ItemArray = (object[])row.ItemArray.Clone();
}
Regardless of the way you chose to update the DataRow, you can put the whole thing in an extension method.
I have DataTable with the following columns:
ClientID date numberOfTransactions price
ClientID is of type string and I need to ensure that its contents include "A-" and "N6" for every value in the table.
I need to delete all rows from the DataTable where this first column (ClientID) does not contain both "A-" and "N6" (some totals and other unnecessary data). How can I select and delete these rows specifically from the DataTable?
I know this:
foreach (DataRow row in table.Rows) // Loop over the rows.
{
//Here should come part "if first column contains mentioned values
}
I also know this
If (string.Contains("A-") == true && string.Contains("N6") == true)
{
//Do something
}
I need help how to implement this for first column of each row.
Try this:
EDIT: Totally messed up that last line, so if you tried it, try it now that I made it not stupid. =)
List<int> IndicesToRemove = new List<int>();
DataTable table = new DataTable(); //Obviously, your table will already exist at this point
foreach (DataRow row in table.Rows)
{
if (!(row["ClientID"].ToString().Contains("A-") && row["ClientID"].ToString().Contains("N6")))
IndicesToRemove.Add(table.Rows.IndexOf(row));
}
IndicesToRemove.Sort();
for (int i = IndicesToRemove.Count - 1; i >= 0; i--) table.Rows.RemoveAt(IndicesToRemove[i]);
try using this,
assuming dt as your Datatabe object and ClientID as your first column (hence using ItemArray[0])
for(int i=0; i<dt.Rows.Count; i++)
{
temp = dt.Rows[i].ItemArray[0].ToString();
if (System.Text.RegularExpressions.Regex.IsMatch(temp, "A-", System.Text.RegularExpressions.RegexOptions.IgnoreCase) || System.Text.RegularExpressions.Regex.IsMatch(temp, "N6", System.Text.RegularExpressions.RegexOptions.IgnoreCase))
{
dt.Rows.RemoveAt(i);
i--;
}
}
Simple and straight forward solution... hope it helps
this should be more efficient, both in lines of Code and Time, try this :)
for(int x=0; x<table.Rows.Count;)
{
if (!table.Rows[x].ItemArray[0].contains("A-") && !table.Rows[x].ItemArray[0].contains("N6"))
table.Rows.RemoveAt(x);
else x++;
}
Happy Coding
Preface: C.Barlow's existing answer is awesome, this is just another route someone could take.
This is one way to do it where you never have to loop all the way through the original table (by taking advantage of the DataTable.Select() method):
DataTable table = new DataTable(); // This would be your existing DataTable
// Grab only the rows that meet your criteria using the .Select() method
DataRow[] newRows = table.Select("ClientID LIKE '%A-%' AND ClientID LIKE '%N6%'");
// Create a new table with the same schema as your existing one.
DataTable newTable = table.Clone();
foreach (DataRow r in newRows)
{
// Dump the selected rows into the table.
newTable.LoadDataRow(r.ItemArray, true);
}
And now you have a DataTable with only the rows you want. If necessary, at this point you could clear out the original table and replace it with the contents of the new one:
table.Clear();
table = newTable.Copy();
Edit: I thought of a memory optimization last night, you can just overwrite the existing table once you have the rows you need, which avoids the need for the temporary table.
DataTable table = new DataTable(); // This would be your existing DataTable
// Grab only the rows that meet your criteria using the .Select() method
DataRow[] newRows = table.Select("ClientID LIKE '%A-%' AND ClientID LIKE '%N6%'");
// Clear out the old table
table.Clear();
foreach (DataRow r in newRows)
{
// Dump the selected rows into the table.
table.LoadDataRow(r.ItemArray, true);
}
I want to delete all rows from datatable with rowstate property value Deleted.
DataTable dt;
dt.Clear(); // this will not set rowstate property to delete.
Currently I am iterating through all rows and deleting each row.
Is there any efficient way?
I don't want to delete in SQL Server I want to use DataTable method.
We are using this way:
for(int i = table.Rows.Count - 1; i >= 0; i--) {
DataRow row = table.Rows[i];
if ( row.RowState == DataRowState.Deleted ) { table.Rows.RemoveAt(i); }
}
This will satisfy any FK cascade relationships, like 'delete' (that DataTable.Clear() will not):
DataTable dt = ...;
// Remove all
while(dt.Count > 0)
{
dt.Rows[0].Delete();
}
dt.Rows.Clear();
dt.Columns.Clear(); //warning: All Columns delete
dt.Dispose();
I typically execute the following SQL command:
DELETE FROM TABLE WHERE ID>0
Since you're using an SQL Server database, I would advocate simply executing the SQL command "DELETE FROM " + dt.TableName.
I would drop the table, fastest way to delete everything. Then recreate the table.
You could create a stored procedure on the SQL Server db that deletes all the rows in the table, execute it from your C# code, then requery the datatable.
Here is the solution that I settled on in my own code after searching for this question, taking inspiration from Jorge's answer.
DataTable RemoveRowsTable = ...;
int i=0;
//Remove All
while (i < RemoveRowsTable.Rows.Count)
{
DataRow currentRow = RemoveRowsTable.Rows[i];
if (currentRow.RowState != DataRowState.Deleted)
{
currentRow.Delete();
}
else
{
i++;
}
}
This way, you ensure all rows either get deleted, or have their DataRowState set to Deleted.
Also, you won't get the InvalidOperationException due to modifying a collection while enumerating, because foreach isn't used. However, the infinite loop bug that Jorge's solution is vulnerable to isn't a problem here because the code will increment past a DataRow whose DataRowState has already been set to Deleted.