compare datatables and save unmatched rows in third datatable - c#

I am trying to compare two datatables and capture the difference in third datatable.
DataTable one = new DataTable();
one.Columns.Add("ID");
one.Columns.Add("PCT");
one.Rows.Add("1", "0.1");
one.Rows.Add("2", "0.2");
one.Rows.Add("3", "0.3");
DataTable two = new DataTable();
two.Columns.Add("ID");
two.Columns.Add("PCT");
two.Columns.Add("OldPCT");
two.Rows.Add("1", "0.1", "0");
two.Rows.Add("2", "0.1", "0");
two.Rows.Add("3", "0.9", "0");
two.Columns.Remove("OldPCT");
//First method
DataTable three = two.AsEnumerable().Except(one.AsEnumerable()).CopyToDataTable();
foreach (DataRow dr in three.AsEnumerable())
{
string strID = dr[0].ToString();
string strPCT = dr[1].ToString();
}
//second method
var diffName = two.AsEnumerable().Select(r => r.Field<string>("PCT")).Except(one.AsEnumerable().Select(r => r.Field<string>("PCT")));
if (diffName.Any())
{
DataTable Table3 = (from row in two.AsEnumerable()
join name in diffName
on row.Field<string>("PCT") equals name
select row).CopyToDataTable();
}
So far I have tried two methods and I'm not not getting my expected result and it should be like.
In third datatable the values should be like mentioned below.
ID PCT
2 O.1
3 0.9
Recent One:
DataTable one = new DataTable();
one.Columns.Add("ID");
one.Columns.Add("PCT");
one.Rows.Add("1", "0.1");
one.Rows.Add("2", "0.2");
one.Rows.Add("2", "0.2");
one.Rows.Add("3", "0.3");
one.Rows.Add("3", "0.3");
DataTable two = new DataTable();
two.Columns.Add("ID");
two.Columns.Add("PCT");
two.Rows.Add("1", "0.1");
two.Rows.Add("2", "0.1");
two.Rows.Add("2", "0.1");
two.Rows.Add("3", "0.8");
two.Rows.Add("3", "0.9");
Now I need to get all the rows from datatable two except first row. But I am getting only last three rows.

Building on Hogan's answer, you can use DataRowComparer.Default as the second parameter to the Except() method (instead of creating a custom IEqualityComparer):
// this will get all rows from table two that don't match rows in one
// the result is an IEnumerable<DataRow>
var unmatched = two.AsEnumerable()
.Except(one.AsEnumerable(), DataRowComparer.Default);
// CopyToDataTable converts an IEnumerable<DataRow> into a DataTable
// but it blows up if the source object is empty
// this statement makes sure unmatched has data before calling CopyToDataTable()
// if it is empty, we 'clone' (make an empty copy) of one of the original DataTables
var three = unmatched.Any() ? unmatched.CopyToDataTable() : one.Clone();
This will do a value-based comparison of the fields in each row to determine if they are equal.

You need a custom IEqualityComparer
void Main()
{
DataTable one = new DataTable();
one.Columns.Add("ID");
one.Columns.Add("PCT");
one.Rows.Add("1", "0.1");
one.Rows.Add("2", "0.2");
one.Rows.Add("3", "0.3");
DataTable two = new DataTable();
two.Columns.Add("ID");
two.Columns.Add("PCT");
two.Columns.Add("OldPCT");
two.Rows.Add("1", "0.1", "0");
two.Rows.Add("2", "0.1", "0");
two.Rows.Add("3", "0.9", "0");
two.Columns.Remove("OldP
DataTable three = two.AsEnumerable().Except(one.AsEnumerable(),new RowEqualityComparer()).CopyToDataTable();
foreach (DataRow dr in three.AsEnumerable())
{
string strID = dr[0].ToString();
string strPCT = dr[1].ToString();
}
}
class RowEqualityComparer : IEqualityComparer<DataRow>
{
public bool Equals(DataRow b1, DataRow b2)
{
if ((b1.Field<string>("ID") == b2.Field<string>("ID")) && (b1.Field<string>("PCT") == b2.Field<string>("PCT")))
{
return true;
}
else
{
return false;
}
}
public int GetHashCode(DataRow bx)
{
return (bx.Field<string>("ID")+bx.Field<string>("PCT")).GetHashCode();
}
}

Related

How to cut DataTable's Columns by using a string array?

I have a DataTable. How to cut out its columns, and return a new DataTable?
I'm trying to create a method with the following parameters:
DataTable dt.
A string Array containing some fields.
It should look something like this:
public static DataTable cutDataTable(DataTable dt, List<string> fileds)
{
DataTable newDt = null;
// processing code...
return newDt
}
No exceptions.
You could make use of DataView.ToTable method for the purpose. For example,
var fields = new []{"Age","Id"}; //Fields to be selected.
var result = new DataView(table).ToTable(false, fields);
This would return a new DataTable from the source table with only the specified fields.
If I understand you correctly, you are trying to do something like this:
public static DataTable CutDataTable(DataTable dt, IList<string> columnsToRemove,
bool keepData = true)
{
DataTable newDt = (keepData ? dt.Copy() : dt.Clone());
foreach (string colName in columnsToRemove)
{
if (!newDt.Columns.Contains(colName)) continue;
newDt.Columns.Remove(colName);
}
return newDt;
}
Usage:
var dt = new DataTable();
dt.Columns.Add("Column1");
dt.Columns.Add("Column2");
dt.Columns.Add("Column3");
dt.Columns.Add("Column4");
dt.Rows.Add("1", "2", "3", "4");
var newDt = CutDataTable(dt, new[] { "Column3", "Column4" });
Console.WriteLine("New column count = " + newDt.Columns.Count);
Console.WriteLine("First row data: " + string.Join(",", newDt.Rows[0].ItemArray));
Output:
New column count = 2
First row data: 1,2

c# split one datatable to several into dataset

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.

Change row position in DataTable or move row in DataTable

I have a DataTable that must be sorted, however i want to return a particular row as the last row even after sorting the DataTable. I will identify this row by a string value in a partiucular column.
public DataTable StandardReport3B(string missionId, string reportType, string startDate, string endDate)
{
List<long> missionIdList = new List<long>();
string[] missionIds = missionId.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string mission in missionIds)
{
missionIdList.Add(Convert.ToInt64(mission));
}
DataTable dt = new DataTable();
object reportData = null;
using (var ctx = new MedicalServiceEntities())
{
reportData = ctx.GetData(startDate, endDate, missionId).ToList();
}
dt = this.GenericListToDataTable(reportData);
DataView dv = dt.DefaultView;
dv.Sort = dt.Columns[0].ColumnName + " Asc";
return dv.ToTable();
}
You can use LINQ-to-DataSet:
dt = dt.AsEnumerable()
.OrderBy(r => r.Field<string>(0) == "Special Value")
.ThenBy(r => r.Field<string>(0))
.CopyToDataTable();
That works because the comparison returns either true or false and false is "lower".
In case someone just wants to know how to move the row in the table to another index as the title suggests: Add data row to datatable at predefined index
I will identify this row by a string value in a partiucular column.
What about:
dv.Sort = "ParticularColumn ASC, " + dt.Columns[0].ColumnName + " ASC">
Where "ParticularColumn" is a column with an identical, low value for all rows except the one you want to be last - and this row has a higher value.

filter a data table with values from an array c#

I have a delete method, it gets an array of GUIDs and I have a data table... how can I filter the data table so it will only contain thoes GUIDs?
public void delete(Guid[] guidlist)
{
datatable template = ReadTemplateList()
...
}
Thank you!
With Linq to DataSet you can create new DataTable which will have only rows with those Guids:
public void Delete(Guid[] guids)
{
DataTable table = ReadTemplateList()
.AsEnumerable()
.Where(r => guids.Contains(r.Field<Guid>("ColumnName")))
.CopyToDataTable();
// ...
}
Another option (which also will work on old .NET versions) is filtering your table with built-in RowFilter functionality which supports IN filters. Let's assume you are filtering by column named ID:
// Check if guids.Length > 0
StringBuilder filter = new StringBuilder("ID IN (");
foreach (Guid id in guids)
filter.AppendFormat("Convert('{0}','System.Guid'),", id);
filter.Append(")");
DataTable template = ReadTemplateList();
DataView view = template.DefaultView;
view.RowFilter = filter.ToString();
DataTable table = view.ToTable();
You can use LINQ:
public static DataTable DeleteGuidsFromTemplate(Guid[] guidlist)
{
DataTable template = ReadTemplateList();
var rowsWithGuids = from row in template.AsEnumerable()
join guid in guidlist
on row.Field<Guid>("Guid") equals guid
select row;
return rowsWithGuids.CopyToDataTable();
}
If you can't use LINQ since you're lower than NET 3.5:
public static DataTable DeleteGuidsFromTemplate(Guid[] guidlist)
{
DataTable template = ReadTemplateList();
DataTable templateGuids = template.Clone();
foreach(DataRow row in template.Rows)
{
Guid guid = (Guid)row["Guid"];
int index = Array.IndexOf(guidlist, guid);
if (index >= 0)
templateGuids.ImportRow(row);
}
return templateGuids;
}
A solution based on DataTable and DataRows.
//fill the datatable from back end
string connStr = ConfigurationManager.ConnectionStrings["ConsoleApplication1.Properties.Settings.NORTHWNDConnectionString"].ConnectionString;
SqlConnection conn = new SqlConnection(connStr);
conn.Open();
SqlCommand comm = new SqlCommand("select categoryid,categoryname from Categories order by categoryname");
comm.Connection = conn;
SqlDataReader dr = comm.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(dr);
//datatable filter logic
string[] filter = { "1", "2" };
DataTable filteredTable = dt.Clone();
foreach (string str in filter)
{
DataRow[] filteredRows = dt.Select("categoryid="+ str); //search for categoryID
foreach (DataRow dtr in filteredRows)
{
filteredTable.ImportRow(dtr);
}
}
EDIT
Without going in the for loop
DataTable dt = new DataTable();
dt.Load(dr); //fill the datatable with sqldatareader
DataTable filteredTable = dt.Clone();
DataView dv = dt.DefaultView;
dv.RowFilter = "categoryid in (1,2)";
filteredTable = dv.ToTable();

two datatable adding

I have two datatables.
1st table-----> DataTable _dtMain = new COrder().GetDetails();
2nd table-----> DataTable _dtSub = new CGrid().GetSubDetails();
I want to add the above two tables. I am using _dtMain.Merge(_dtSub );
But it will append _dtSub table to _dtMain table. I want datatable to add second table to first table in column wise (that means after first table last column)
Don't think there any built-in method for the thing you want to achieve.
I think you need to make your own implementation for that stuff, like for example an extension method
public static DataTable Aggregate(this DataTable dt1, DataTable dt2)
{
var aggregator = new DataTable();
//add columns from dt1 and dt2
//add rows from dt1 dt2
}
or, you can do a complete Outer Join on both tables, like descrived in this article.
After a lot of research , i find out the answer for my question that i posted before. Here is the code for combing 2 datatable & the data inside it.
public DataTable CombineTable(DataTable _dtGridDetails, DataTable _dtSubGridDetails)
{
//first create the datatable columns
DataSet mydataSet = new DataSet();
mydataSet.Tables.Add(" ");
DataTable myDataTable = mydataSet.Tables[0];
//add left table columns
DataColumn[] dcLeftTableColumns = new DataColumn[_dtGridDetails.Columns.Count];
_dtGridDetails.Columns.CopyTo(dcLeftTableColumns, 0);
foreach (DataColumn LeftTableColumn in dcLeftTableColumns)
{
if (!myDataTable.Columns.Contains(LeftTableColumn.ToString()))
myDataTable.Columns.Add(LeftTableColumn.ToString());
}
//now add right table columns
DataColumn[] dcRightTableColumns = new DataColumn[_dtSubGridDetails.Columns.Count];
_dtSubGridDetails.Columns.CopyTo(dcRightTableColumns, 0);
foreach (DataColumn RightTableColumn in dcRightTableColumns)
{
if (!myDataTable.Columns.Contains(RightTableColumn.ToString()))
{
// if (RightTableColumn.ToString() != RightPrimaryColumn)
myDataTable.Columns.Add(RightTableColumn.ToString());
}
}
//add left-table data to mytable
foreach (DataRow LeftTableDataRows in _dtGridDetails.Rows)
{
myDataTable.ImportRow(LeftTableDataRows);
}
for (int nIndex = 0; nIndex <= myDataTable.Rows.Count-1; nIndex++)
{
if (nIndex == _dtSubGridDetails.Rows.Count)
break;
myDataTable.Rows[nIndex][columnindex] = _dtSubGridDetails.Rows[nIndex][columnindex];
}
return myDataTable;
}

Categories