I've a DataTable[having 5 columns] from db which has combined data.
I need to invoke a group by on this combined table and create 2 tables...one with groupedBy rows and other having items.
What would be fastest way to do this in C# code?
Also, I've written code below for adding columns for these 2 tables.Is that correct?
Heres my code:
string colName = "ACCOUNT_ID";
var allRows = combinedTable.AsEnumerable();
var accountRowGroups = allRows.GroupBy(row => row[colName]);
DataTable masterDataTable = new DataTable();
DataTable childPricesDataTable = new DataTable();
// Create the columns
DataColumnCollection pdCols = combinedTable.Columns;
for (int ndx = 0; ndx < pdCols.Count; ndx++)
{
string columnName = pdCols[ndx].ColumnName;
Type type = Type.GetType(pdCols[ndx].DataType.ToString());
masterDataTable.Columns.Add(columnName, type);
childPricesDataTable.Columns.Add(columnName, type);
}
See a similar question here: How to merge multiple DataTable objects with group by with C# and concatinating duplicate rows?
I agree with duffymo, do it in SQL rather than data in-memory.
However if that isn't an option:
You can add a relationship between the two DataTables: http://msdn.microsoft.com/en-us/library/ay82azad(v=vs.71).aspx
Then you can run group by's on the combined datatables: http://codecorner.galanter.net/2009/04/20/group-by-and-aggregates-in-net-datatable/
Here is a proper example from the SQL Team:
http://weblogs.sqlteam.com/davidm/archive/2004/05/20/1351.aspx
public static DataTable GROUPBY(DataTable Table, DataColumn[] Grouping, string[] AggregateExpressions, string[] ExpressionNames, Type[] Types)
{
if (Table.Rows.Count == 0)
return Table;
DataTable table = SQLOps.PROJECT(Table, Grouping);
table.TableName = "GROUPBY";
for (int i = 0; i < ExpressionNames.Length; i++)
{
table.Columns.Add(ExpressionNames[i], Types[i]);
}
foreach (DataRow row in table.Rows)
{
string filter = string.Empty;
for (int i = 0; i < Grouping.Length; i++)
{
//Determine Data Type
string columnname = Grouping[i].ColumnName;
object o = row[columnname];
if (o is string || DBNull.Value == o)
{
filter += columnname + "='" + o.ToString() + "' AND ";
}
else if (o is DateTime)
{
filter += columnname + "=#" + ((DateTime)o).ToLongDateString()
+ " " + ((DateTime)o).ToLongTimeString() + "# AND ";
}
else
filter += columnname + "=" + o.ToString() + " AND ";
}
filter = filter.Substring(0, filter.Length - 5);
for (int i = 0; i < AggregateExpressions.Length; i++)
{
object computed = Table.Compute(AggregateExpressions[i], filter);
row[ExpressionNames[i]] = computed;
}
}
return table;
}
It's not direct answer to your question but maybe alternative (I believe better) way to solve your problem.
If I understand correctly you have DataTable, some defined manipulations/filters and want return modfied DataTable as result. But in practice the result DataTable hasn't known shape (it may be different depending columns in filter).
Consider using linq instead of manipulating DataTables and DataRelations. Linq gives support for all mentioned by you operations: grouping, filtering, trimming and many more.
As the result you may return object which is bindable also, so you may use it in your grid, but the method definition will be much cleaner.
Related
I have a dataset which has duplicate rows i want my error message to execute when duplicate rows are present.
Below is my code please help
DataSet dsXml = new DataSet();
dsXml.ReadXml(new XmlTextReader(new StringReader(xml)));
Hashtable hTable = new Hashtable();
ArrayList duplicateList = new ArrayList();
foreach (DataRow drow in dsXml.Tables[0].Rows)
{
if (hTable.Contains(drow))
{
duplicateList.Add(drow);
}
else
{
script.Append("alert('Error - There are some Duplicate entries.'); ");
ErrorOcc = true;
if (ErrorOcc)
{
this.ScriptOutput = script + " ValidateBeforeSaving = false;";
this.StayContent = "yes";
return;
}
}
}
Your code is not working, because DataRow instances will be compared by references instead of comparing their fields. You can use custom comparer:
public class CustomDataRowComparer : IEqualityComparer<DataRow>
{
public bool Equals(DataRow x, DataRow y)
{
if (x.ItemArray.Length != y.ItemArray.Length)
return false;
for (int i = 0; i < x.ItemArray.Length; i++)
if (!x[i].Equals(y[i]))
return false;
return true;
}
public int GetHashCode(DataRow obj)
{
int hash = 17;
foreach (object field in obj.ItemArray)
hash = hash * 19 + field.GetHashCode();
return hash;
}
}
or use existing DataRowComparer which compares DataRow objects for equivalence by using value-based comparison:
HashSet<DataRow> set = new HashSet<DataRow>(DataRowComparer.Default);
// or: new HashSet<DataRow>(new CustomDataRowComparer());
foreach (DataRow row in dsXml.Tables[0].Rows)
{
if (!set.Add(row))
// duplicate row
}
You can also check if duplicated rows exist with Linq to DataSet query:
var duplicatedRowsExist = dsXml.Tables[0].AsEnumerable()
.GroupBy(r => r, DataRowComparer.Default)
.Any(g => g.Count() > 1);
You have to compare the content of the rows, not the rows themselves. Something like this should do it:
var hasDupes = dsXml.Tables[0].Rows
.AsEnumerable()
.GroupBy(row => new
{
row.Field<string>("Title"),
row.Field<string>("Address"),
row.Field<string>("State"),
row.Field<string>("City"),
row.Field<int>("Status"),
row.Field<int>("CreatedBy"),
row.Field<int>("UpdatedBy")
})
.Where(g => g.Count() > 1)
.Any();
if(hasDupes)
//Show error message
I think you have to alter you logic a little. You don't add the row to the hTable, so there are never duplicates. And I guess you have to show the message in the end, else the list will not be complete yet.
As stated by others, you do need Sergeys answer to get the comparison to work. If you have that covered, this code will solve the other logic problems.
foreach (DataRow drow in dsXml.Tables[0].Rows)
{
if (!hTable.Contains(drow))
{
hTable.Contains(drow);
hTable.Add(drow);
}
else
{
duplicateList.Add(drow);
}
}
script.Append("alert('Error - There are some Duplicate entries.'); ");
ErrorOcc = true;
if (ErrorOcc)
{
this.ScriptOutput = script + " ValidateBeforeSaving = false;";
this.StayContent = "yes";
return;
}
First, you need to define your comparison between rows. It appears when you create your hTable there is nothing in it, so the hTable.Contains call is always going to return false.
As a side note, you can't just compare a DataRow with another DataRow, it will use the default equality comparison (implemented using IEqualityComparer) and effectively boils down to a reference equality check, which none of the rows will be equal to each other.
Somewhere, you can either implement your own IEqualityCompariosn, or simply write a custom method to check the values of each row.
Here is the answer of my above Question
You can Check duplicate rows in dataset.. it is working fine try it.
DataSet dsXml = new DataSet();
dsXml.ReadXml(new XmlTextReader(new StringReader(xml)));
List<string> duplicateList = new List<string>();
foreach (DataRow drow in dsXml.Tables[0].Rows)
{
string strr = "";
for (int j = 0; j < dsXml.Tables[0].Columns.Count; j++ )
{
strr += drow[j];
}
if (!duplicateList.Contains(strr))
{
duplicateList.Add(strr);
}
else
{
script.Append("alert('Error - There are some Duplicate entries.'); ");
ErrorOcc = true;
if (ErrorOcc)
{
this.ScriptOutput = script + " ValidateBeforeSaving = false;";
this.StayContent = "yes";
return;
}
}
}
In my application i am filtering a datatable using a filter expression and am getting a DataRow which matches the condition.Now i want to check if the value of particular column exists in any row of DataRow array.
Code:
string FilterCond1 = "id=" + getId;
DataRow[] myrow = DataTable.Select(FilterCond1);
if (myrow.Length > 0)
{
//check for size=28 in DataRow[]
}
else
{
}
I have column size in the datatable DataTable and i want to check if any row of the DataRow array has a value 28 in the column size.How can i go about it?
Try this
string FilterCond1 = "id=" + getId;
DataRow[] myrow = DataTable.Select(FilterCond1);
if (myrow.Length > 0)
{
for(int i = 0; i < myrow.Length; i ++)
{
if(myrow[i]["size"].ToString() == "28")
{
// YOUR CODE HERE
}
}
}
else
{
}
EDIT
Just add the condition to your filter.
string FilterCond1 = "id=" + getId + " AND size=28";
Then you don't need the if(myrow[i]["size"].ToString() == "28") as you know the rows in the array are the one you want.
You can use column collection to access particular column value within row.
if(myrow[rowIndex]["ColoumnName"].ToString() == "somevalue")
Where row index could from zero to length-1
Edit based on comments, you can put multiple condition on column in select, check it here, and may not need to iterate.
string FilterCond1 = "id=" + getId + " AND size = " + 28;
DataRow[] myrow = dt.Select(FilterCond1);
To iterate through rows collection
for(int i=0; i < myrow.Length; i++)
{
if(myrow[i]["size"].ToString() == "28")
{
//your code
}
}
First you should aiterate through all rows using foreach then use the below code..
if(myrow[row]["size"] == 28)
or
int ColIndex = 3; // replace 3 with ur co. index
if(myrow[row][ColIndex] == 28)
I have 2 datatable let say dtData1 and dtData2. I have records in both the datatable I want to compare both the data table and want to create a new datatable let say dtData3 with uniqe records.
suppose: dtData1 has 10 records, and dtData2 has 50 records, but what ever records are there in dtData2 in that 7 records are same as dtData1. So here I want unique records in dtData3, means I want only 43 records in dtData3.
we don't know in datatable where we have the duplicate, and its possible that we will not have duplicates or its also possible that we will have all duplicate records.
So in dtData3 I need unique records
Some one please help me.
var dtData3 = dtData2.AsEnumerable().Except(dtData1.AsEnumerable(), DataRowComparer.Default);
Use this.. Probably it will help you.
Suppose you have two data table
DataTable dt1 = new DataTable();
dt1.Columns.Add("Name");
dt1.Columns.Add("ADD");
DataRow drow;
for (int i = 0; i < 10; i++)
{
drow = dt1.NewRow();
drow[0] = "NameA" + 1;
drow[1] = "Add" + 1;
dt1.Rows.Add();
}
DataTable dt2 = new DataTable();
dt2.Columns.Add("Name");
dt2.Columns.Add("ADD");
DataRow drow1;
for (int i = 0; i < 11; i++)
{
drow1 = dt2.NewRow();
drow1[0] = "Name" + 1;
drow1[1] = "Add" + 1;
dt2.Rows.Add();
}
Now To solve your problem Call :-
DataTable d3 = CompareTwoDataTable(dt1, dt2);
The method is something like this;--
public static DataTable CompareTwoDataTable(DataTable dt1, DataTable dt2)
{
dt1.Merge(dt2);
DataTable d3 = dt2.GetChanges();
return d3;
}
Then where the need to compare dtData1 and dtData2? Instead you can copy the contents from dtData2 to dtData3 starting from index7.
First Create Array variables to save unique coloumn values for datatable3. Use Foreach loop with second gridview rows. If any match then dnt save it in a array value, if dnt match then save it in arrays. and display it by attaching with third gridview.......
e.g
string[] name = new string[4];
int i=0,j=0;
foreach(GridViewRows gv in GridView1.rows)
{
if(gv.Cells[0].Text == GridView2.Rows[i].Cells[0].Text)' //if match
{
// dnt save
}
else' //if dnt match save in array for further use
{
name[j] = gv.Cells[0].Text;
j= j++;
}
i=i++;
}
After Saving unique values in Array "name"...Bind it in Third Gridview
During DataBound of third Gridview add this method...
Private void GridView3_RowDataBound(object sender,EventArgs e)
{
if(e.Row.RowState == DataControlState.DataRow)
{
foreach(string nm in name)
{
e.Rows.Cells.Add(name);
}
}
}
public DataTable CompareTwoDataTable(DataTable dtOriginalTable, DataTable dtNewTable, ArrayList columnNames)
{
DataTable filterTable = new DataTable();
filterTable = dtNewTable.Copy();
string filterCriterial;
if (columnNames.Count > 0)
{
for (int iNewTableRowCount = 0; iNewTableRowCount < dtNewTable.Rows.Count; iNewTableRowCount++)
{
filterCriterial = string.Empty;
foreach (string colName in columnNames.ToArray())
{
filterCriterial += colName.ToString() + "='" + dtNewTable.Rows[iNewTableRowCount][colName].ToString() + "' AND ";
}
filterCriterial = filterCriterial.TrimEnd((" AND ").ToCharArray());
DataRow[] dr = dtOriginalTable.Select(filterCriterial);
if (dr.Length > 0)
{
filterTable.Rows[filterTable.Rows.IndexOf(filterTable.Select(filterCriterial)[0])].Delete();
}
}
}
return filterTable;
}
I am trying to find a fast way to find a string in all datatable columns!
Followed is not working as I want to search within all columns value.
string str = "%whatever%";
foreach (DataRow row in dataTable.Rows)
foreach (DataColumn col in row.ItemArray)
if (row[col].ToString() == str) return true;
You can use LINQ. It wouldn't be any faster, because you still need to look at each cell in case the value is not there, but it will fit in a single line:
return dataTable
.Rows
.Cast<DataRow>()
.Any(r => r.ItemArray.Any(c => c.ToString().Contains("whatever")));
For searching for random text and returning an array of rows with at least one cell that has a case-insensitive match, use this:
var text = "whatever";
return dataTable
.Rows
.Cast<DataRow>()
.Where(r => r.ItemArray.Any(
c => c.ToString().IndexOf(text, StringComparison.OrdinalIgnoreCase) > 0
)).ToArray();
If you want to check every row of every column in your Datatable, try this (it works for me!).
DataTable YourTable = new DataTable();
// Fill your DataTable here with whatever you've got.
foreach (DataRow row in YourTable.Rows)
{
foreach (object item in row.ItemArray)
{
//Do what ya gotta do with that information here!
}
}
Don't forget to typecast object item to whatever you need (string, int etc).
I've stepped through with the debugger and it works a charm. I hope this helps, and good luck!
This can be achieved by filtering. Create a (re-usable) filtering string based on all the columns:
bool UseContains = false;
int colCount = MyDataTable.Columns.Count;
string likeStatement = (UseContains) ? " Like '%{0}%'" : " Like '{0}%'";
for (int i = 0; i < colCount; i++)
{
string colName = MyDataTable.Columns[i].ColumnName;
query.Append(string.Concat("Convert(", colName, ", 'System.String')", likeStatement));
if (i != colCount - 1)
query.Append(" OR ");
}
filterString = query.ToString();
Now you can get the rows where one of the columns matches your searchstring:
string currFilter = string.Format(filterString, searchText);
DataRow[] tmpRows = MyDataTable.Select(currFilter, somethingToOrderBy);
You can create a routine of search with an array of strings with the names of the columns, as well:
string[] elems = {"GUID", "CODE", "NAME", "DESCRIPTION"};//Names of the columns
foreach(string column in elems)
{
string expression = string.Format("{0} like '%{1}%'",column,
txtSearch.Text.Trim());//Search Expression
DataRow[] row = data.Select(expression);
if(row.Length > 0) {
// Some code here
} else {
// Other code here
}
}
You can get names of columns by using ColmunName Method. Then, you can search every column in DataTable by using them. For example, follwing code will work.
string str = "whatever";
foreach (DataRow row in dataTable.Rows)
{
foreach (DataColumn column in dataTable.Columns)
{
if (row[column.ColumnName.ToString()].ToString().Contains(str))
{
return true;
}
}
}
You can create a filter expression on the datatable as well. See this MSDN article. Use like in your filter expression.
string filterExp = "Status = 'Active'";
string sortExp = "City";
DataRow[] drarray;
drarray = dataSet1.Customers.Select(filterExp, sortExp, DataViewRowState.CurrentRows);
for (int i=0; i < drarray.Length; i++)
{
listBox1.Items.Add(drarray[i]["City"].ToString());
}
I created a datatable with two column and four rows. I am trying to retrieve information from the row by linq based on the information I provide in the query statement for one column, but I get nothing in console.write statement.
var super =
from lang in JapanesePhrases.AsEnumerable()
where lang.Field<string>("Meaning") == "Song of Truth"
select lang.Field<string>("Phrase");
foreach (string item in super)
{
Console.Write(item + "\n");
}
i tried now this code:
var table = new DataTable();
table.Columns.Add("Meaning");
table.Columns.Add("Phrase");
for (int i = 0; i < 5; i++)
{
var row = table.NewRow();
row["Meaning"] = "Meaning"+i;
row["Phrase"] = "Phrase"+i;
table.Rows.Add(row);
}
var super = from lang in table.AsEnumerable()
where lang.Field<string>("Meaning") == "Meaning1"
select lang.Field<string>("Phrase");
foreach (string item in super)
{
Console.Write(item + "\n");
}
Console.ReadLine();
running seamlessly.
i should check data in datatable.