I am searching a PDF file for a keyword and returning the pages on which that keyword was found. If the keyword IS FOUND, I'm returning a list of pages and the fileName. However, if the keyword was NOT FOUND in the PDF file, I want to deleted the row in the datatable.
public DataTable dtPubSearchResultsFromFiles(string sqlQuery, string safeKeyword)
{
// Returns a datatable of publication search results based on PDF files.
SqlConnection con = new SqlConnection(getConnectionString());
SqlCommand cmd = new SqlCommand(sqlQuery, con);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataTable dt = new DataTable();
dt.Columns.Add("Pages", typeof(string));
da.Fill(dt);
dt.PrimaryKey = new DataColumn[] { dt.Columns["publicationID"] };
foreach (DataRow row in dt.Rows)
{
//call search function to look for keyword
List<int> myPages = new List<int>();
string fileName = row["linkToPublicationPDF"].ToString();
myPages = ReadPdfFile(fileName, safeKeyword);
if (myPages.Count > 0)
{
string pagelist = "";
foreach (int page in myPages)
{
pagelist = pagelist + page + " ";
}
row["Pages"] = pagelist;
}
else
{
//remove/delete the row from the datatable if "myPages.Count" is 0
dt.Rows.Remove(row);
}
}
return dt;
}
When I add this ("dt.Rows.Remove(row)"), I get this error when the page is called "System.InvalidOperationException: Collection was modified; enumeration operation might not execute."
Suggestions? Comments? Fixes? All are welcome...
Bob
Your code is getting some data from the database such that your program can work with it.
The exception you're getting is because your you're modifying (by removing an element) the collection you're iterating on and that's not possible.
You can solve this by creating a temporary List where you store the rows you want to delete. Once you're done with the iteration you can iterate on the temporary list and remove what you decided you don't want anymore.
var toRemove = new List<DataRow>();
foreach (DataRow row in dt.Rows)
{
//call search function to look for keyword
List<int> myPages = new List<int>();
string fileName = row["linkToPublicationPDF"].ToString();
myPages = ReadPdfFile(fileName, safeKeyword);
if (myPages.Count > 0)
{
string pagelist = "";
foreach (int page in myPages)
{
pagelist = pagelist + page + " ";
}
row["Pages"] = pagelist;
}
else
{
//remove/delete the row from the datatable if "myPages.Count" is 0
toRemove.Add(row);
}
}
}
foreach (DataRow row toRemove.Add)
{
dt.Rows.Remove(row);
}
try a simple Delete
row.Delete();
then after the loop
dt.AcceptChanges();
but it will probably fail
see answer from mario
it may work if it is really only marking the row for deletion
Related
I have correct source DataTable "sourceDataTable" and I call method to split it into several and store the result into DataSet "ds":
DataSet ds = MyClass.SplitDataTables(sourceDataTable);
Here is the method MyClass.SplitDataTables():
public static DataSet SplitDataTables(DataTable sourceDataTable)
{
using (DataSet dsOut = new DataSet())
{
DataTable dt1 = new DataTable;
DataTable dt2 = new DataTable;
DataTable dt3 = new DataTable;
dt1 = sourceDataTable.Clone();
dt2 = sourceDataTable.Clone();
dt3 = sourceDataTable.Clone();
foreach (DataRow row in sourceDataTable.Rows)
{
//column is for example "City" and some row has "Boston" in it, so I put this row into dt1
if (row["ColumnName"].ToString() == "something")
{
dt1.ImportRow(row);
}
else if (...)
{ } //for other DataTables dt2, dt3, etc...
else .......... ;
}
//here I put resulting DataTables into one DataSet which is returned
string[] cols= { "dt1", "dt2", "dt3" };
foreach (string col in cols)
{
dsOut.Tables.Add(col);
}
return dsOut;
}
}
So with this returned DataSet I display new Windows each with one DataTable
foreach (DataTable dtt in ds.Tables)
{
string msg = dtt.TableName;
Window2 win2 = new Window2(dtt, msg);
win2.Show();
}
All I get shown is Windows with placeholder for "empty DataGrid"
Windows code is correct, as it works whith "unsplit DataTable".
I assume code in splitting DataTables is all wrong as it does not output DataSet with filled DataTables. I will greatly appreciate any help on this issue. Thank you!
You don't need a for loop here
Replace below code
string[] cols= { "dt1", "dt2", "dt3" };
foreach (string col in cols)
{
dsOut.Tables.Add(col);
}
With this
dsOut.Tables.Add(dt1);
dsOut.Tables.Add(dt2);
dsOut.Tables.Add(dt3);
Thanks to #Krishna I got this solved. So if you ever encounter similar problem here are 2 things to note:
string[] cols = { "dt1", "dt2", "dt3", ... };
foreach (string col in cols)
{
dsOut.Tables.Add(col);
}
This cycle does not have access to objects of DataTables with same name and writes only empty DataTables into DataSet collection (regardless of same name!).
If you create new DataTable and you will be making it a clone of another DataTable, dont bother yet with setting its name.
DataTable dt1 = new DataTable();
Make new DataTable of same format as source DataTable:
dt1 = sourceDataTable.Clone();
dt2 = sourceDataTable.Clone();
//etc...
Now you have to set unique DataTable names to each DataTable cloned from source DataTable:
dt1.TableName = "Name1";
dt2.TableName = "Name2";
//and so on
Now all works as intended.
I have a Data Table I'm using as the data source for a repeater and would like to have the results show in a random order each time it's called.
I've been able to do this while retrieving the data but wish to cache the result set before it's bound.
Is the any was to shuffle or randomise the rows of a data table before binding to the repeater?
CODE:
TreeProvider tp = new TreeProvider();
DataSet ds = new DataSet();
string sKey = "KEY";
using (CachedSection<DataSet> cs = new CachedSection<DataSet>(ref ds, 5, true, null, sKey))
{
if (cs.LoadData)
{
ds = tp.SelectNodes("", "URL", "", true, "DOCTYPE", "", "NewID()", -1, true, 5);
cs.Data = ds;
}
}
if (!DataHelper.DataSourceIsEmpty(ds))
{
rprItems.DataSource = ds.Tables[0].DefaultView;
rprItems.DataBind();
}
Any guidance is appreciated.
I ended up taking a copy of the table, adding a field and assigning a random number to each row then ordering by that row.
DataTable dt = ds.Tables[0].Copy();
if (!dt.Columns.Contains("SortBy"))
dt.Columns.Add("SortBy", typeof (Int32));
foreach (DataColumn col in dt.Columns)
col.ReadOnly = false;
Random rnd = new Random();
foreach (DataRow row in dt.Rows)
{
row["SortBy"] = rnd.Next(1, 100);
}
DataView dv = dt.DefaultView;
dv.Sort = "SortBy";
DataTable sortedDT = dv.ToTable();
rprItems.DataSource = sortedDT;
rprItems.DataBind();
You could try something like this, I know it's not pretty but:
DataTable newTable = new DataTable();
newTable.TableName = "<NewTableName>";
//Make a new Random generator
Random rnd = new Random();
while (<new table length> != <old table length>)
{
//We'll use this to make sure we don't have a duplicate row
bool rowFound = false;
//index generation
int index = rnd.Next(0, <max number of rows in old data>);
//use the index on the old table to get the random data, then put it into the new table.
foreach (DataRow row in newTable.Rows)
{
if (oldTable.Rows[index] == row)
{
//Oops, there's duplicate data already in the new table. We don't want this.
rowFound = true;
break;
}
}
if (!rowFound)
{
//add the row to newTable
newTable.Rows.Add(oldTable.Rows[index];
}
}
You'll have to use your own tables, names, and lengths of course, but this should be ok to use. If there's a lot of data, this could take a while. It's the best I can come up with, and it's untested. I'm curious to know if it works.
You could try:
DataTable dt = new DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("Sort");
dt.Rows.Add("TEST");
dt.Rows.Add("TEST1");
dt.Rows.Add("TEST2");
var rnd = new Random(DateTime.Now.Millisecond);
foreach (DataRow row in dt.Rows)
{
row["Sort"] = rnd.Next(dt.Rows.Count);
}
var dv = new DataView(dt);
dv.Sort = "Sort";
foreach (DataRowView row in dv)
{
Console.WriteLine(row[0]);
}
If your datatable is not too big it should do it.
I have a menu control in my Master page.T he name of the menu and corresponding url is coming from the database. If a menu has a sub menu it is also showing properly.
But the problem arises if a sub menu has a child menu.
My database table has 4 columns
MenuId || MenuName || ParentId || URL.
and the code is
private void getMenu()
{
DataSet ds = new DataSet();
DataTable dt = new DataTable();
ds = objSec.ShowMenu(s_UserId);
dt = ds.Tables[0];
DataRow[] drowpar = dt.Select("ParentID=" + 0);
foreach (DataRow dr in drowpar)
{
menuBar.Items.Add(new MenuItem(dr["MenuName"].ToString(), dr["MenuID"].ToString(),
"", dr["URL"].ToString()));
}
foreach (DataRow dr in dt.Select("ParentID >" + 0))
{
try
{
MenuItem mnu = new MenuItem(dr["MenuName"].ToString(), dr["MenuID"].ToString(),
"", dr["URL"].ToString());
menuBar.FindItem(dr["ParentID"].ToString()).ChildItems.Add(mnu);
}
catch (Exception ex)
{
}
}
}
The approach in your sample might lead to incorrect results if sub-items are contained in the table before their parent-item. In addition, the empty catch-block might hide any errors. Therefore, I'd recommend another approach.
Instead of looping the table, you can also use recursion to fill the control. This removes the amount of duplicated code:
private void getMenu()
{
DataSet ds = objSec.ShowMenu(s_UserId);
DataTable dt = ds.Tables[0];
AddMenuItems(dt, 0, menu.Items);
}
private void AddMenuItems(DataTable dt, int parentId, MenuItemCollection items)
{
DataRow[] rows = dt.Select("ParentID=" + parentId.ToString());
foreach(var dr in rows)
{
var id = (int) dr["MenuID"];
var menuItem = new MenuItem(dr["MenuName"].ToString(), id.ToString(),
"", dr["URL"].ToString());
items.Add(menuItem);
// Add subitems
AddMenuItems(dt, id, menuItem.ChildItems);
}
}
The sample first calls the AddMenuItems method for the top-level items (ParentID = 0). After each item is added, its children are added by calling the AddMenuItems method again (hence the term "recursive"), providing the id of the top-level item as parent. For each 2nd level child, the method is called again and so on.
I have the following code, its a custom people picker for sharepoint 2010.
It searches by username, but also by the person name.
Because its a contains search, if I try with part of my username: cia
It shows my duplicated rows because that matches the username but also the person name.
this is my code (I cant use LINQ:
protected override int IssueQuery(string search, string groupName, int pageIndex, int pageSize)
{
try
{
// Find any user that has a matching name
var table = ADHelper.ExecuteNameQuery(RootPath, search);
// 20249: Search by username, method was already done, but it was not being called.
var table2 = ADHelper.ExecutesAMAccountNameQuery(search);
table2.Merge(table,);
PickerDialog.Results = table2;
Normally the DataTable.Merge method removes duplicates implicitely. But only when all columns' values are the same.
I'm not sure if there is something simplier(you've mentioned that you cannot use LINQ), but you could merge both and remove the duplicates afterwards:
List<string> dupColumns = new List<string>();
dupColumns.Add("ColumnA");
dupColumns.Add("ColumnB");
table2.Merge(table,);
RemoveDuplicates(table2, dupColumns);
And here the remove-duplicates function:
private void RemoveDuplicates(DataTable table, List<string> keyColumns)
{
Dictionary<string, string> uniquenessDict = new Dictionary<string, string>(table.Rows.Count);
System.Text.StringBuilder sb = null;
int rowIndex = 0;
DataRow row;
DataRowCollection rows = table.Rows;
while (rowIndex < rows.Count)
{
row = rows[rowIndex];
sb = new System.Text.StringBuilder();
foreach (string colname in keyColumns)
{
sb.Append(((string)row[colname]));
}
if (uniquenessDict.ContainsKey(sb.ToString()))
{
rows.Remove(row);
}
else
{
uniquenessDict.Add(sb.ToString(), string.Empty);
rowIndex++;
}
}
}
you should the .ToTable function
here is a sample code
DataTable DT1 = new DataTable();
DT1.Columns.Add("c_" + DT1.Columns.Count);
DT1.Columns.Add("c_" + DT1.Columns.Count);
DT1.Columns.Add("c_" + DT1.Columns.Count);
DataRow DR = DT1.NewRow();
DR[0] = 0;
DR[1] = 1;
DR[2] = 2;
DT1.Rows.Add(DR);
DataTable DT2 = new DataTable();
DT2.Columns.Add("c_" + DT2.Columns.Count);
DT2.Columns.Add("c_" + DT2.Columns.Count);
DT2.Columns.Add("c_" + DT2.Columns.Count);
DT2.Columns.Add("c_" + DT2.Columns.Count);
DR = DT2.NewRow();
DR[0] = 0;
DR[1] = 1;
DR[2] = 2;
DR[3] = 3;
DT2.Rows.Add(DR);
DT1.Merge(DT2);
Trace.IsEnabled = true;
DataTable DT_3=DT1.DefaultView.ToTable(true,new string[]{"c_1","c_2","c_0"});
foreach (DataRow CDR in DT_3.Rows)
{
Trace.Warn("val",CDR[1]+"");//you will find only one data row
}
I wrote a method for splitting a DataTable into multiple small data tables; however I am getting exception. How do I correct it? Please share the code.
Exception message:
This row already belongs to another table.
Framework: .Net 3.0
private static List<DataTable> SplitDataTable(DataTable dt, int size)
{
List<DataTable> split = new List<DataTable>();
DataTable current = dt.Clone();
int iterator = 0;
foreach (DataRow dr in dt.Rows)
{
iterator = iterator + 1;
if (iterator == size)
{
current = dt.Clone();
split.Add(current);
iterator = 0;
}
current.Rows.Add(dr);
//Exception: This row already belongs to another table.
}
return split;
}
Client:
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("TEST", typeof(int));
dt.Columns.Add("VAL", typeof(string));
dt.Rows.Add(0,"a");
dt.Rows.Add(1,"b");
dt.Rows.Add(2,"c");
dt.Rows.Add(3,"d");
List<DataTable> split = SplitDataTable(dt, 2);
}
Use dt.Copy(); instead of dt.Clone();
Before you add a DataRow to your cloned datatable, you need to remove it from the original source datatable:
foreach (DataRow dr in dt.Rows)
{
iterator = iterator + 1;
if (iterator == size)
{
current = dt.Clone();
split.Add(current);
iterator = 0;
}
dt.Rows.Remove(dr); // remove it from the source FIRST, then add it to the cloned DataTable
current.Rows.Add(dr);
}
Use current.ImportRow(dr); instead of current.Rows.Add(dr);
You can either remove the DataRow from the source DataTable or create a new DataRow and add it to the new DataTable.
You just need to change the line where you add the data row to current data table. Use the overload which takes an object array to create a new row. This way you are not cloning or copying any rows, instead creating a new row.
current.Rows.Add(dr.ItemArray);
I think this function will not work properly try this
I make some modification on you code it's working fine
private static List<DataTable> SplitDataTable(DataTable dt, int size)
{
List<DataTable> split = new List<DataTable>();
DataTable current = dt.Clone();
int iterator1 = 0;
foreach (DataRow dr in dt.Rows)
{
if (current.Rows.Count < size)
{
current.Rows.Add(dr.ItemArray);
}
if (current.Rows.Count == size)
{
iterator1= iterator1+size;
split.Add(current);
current = dt.Clone();
}
}
if (iterator1 < dt.Rows.Count) { split.Add(current); }
return split;
}
happy codding