New to LINQ and c# but having problem with one specific problem.
I have 2 Data tables.
DataTable 1 has Name, House Number in it. DataTable 2 holds Street, FirstHouseNumber, LastHouseNumber.
What I want to do is create a new table that has Name, House Number, Street in it but I keep ending up with lots of DataTable1 Count * DataTable2 Count when I would only expect the same count as DataTable 1.
Heres the expression I am using:
var funcquery = from name in DT1.AsEnumerable()
from street in DT2.AsEnumerable()
where name.Field<int>("Number") >= street.Field<int>("FirstHouseNumber") && name.Field<int>("Number") <= street.Field<int>("LastHouseNumber")
select new { Name = name.Field<string>("Name"), Number = name.Field<int>("Number"), street.Field<string>("Street") };
What am I doing wrong? I just cant get it at all.
TIA.
This will be easier to think about with a more specific example:
Name House Number
------ ------------
Chris 123
Ben 456
Joe 789
Street First House Last House
Number Number
-------- ------------ ------------
Broadway 100 500
Main 501 1000
To demonstrate the results using this data, I use the following code:
class HouseInfo
{
public string Name;
public int HouseNumber;
public HouseInfo(string Name, int HouseNumber)
{
this.Name = Name;
this.HouseNumber = HouseNumber;
}
}
class StreetInfo
{
public string Street;
public int FirstHouseNumber;
public int LastHouseNumber;
public StreetInfo(string Street, int FirstHouseNumber, int LastHouseNumber)
{
this.Street = Street;
this.FirstHouseNumber = FirstHouseNumber;
this.LastHouseNumber = LastHouseNumber;
}
}
static void Main(string[] args)
{
HouseInfo[] houses = new HouseInfo[] {
new HouseInfo("Chris", 123),
new HouseInfo("Ben", 456),
new HouseInfo("Joe", 789) };
StreetInfo[] streets = new StreetInfo[] {
new StreetInfo("Broadway", 100, 500),
new StreetInfo("Main", 501, 1000)};
var funcquery = from name in houses
from street in streets
where name.HouseNumber >= street.FirstHouseNumber && name.HouseNumber <= street.LastHouseNumber
select new { Name = name.Name, Number = name.HouseNumber, Street = street.Street };
var results = funcquery.ToArray();
for (int i = 0; i < results.Length; i++)
{
Console.WriteLine("{0} {1} {2}", results[i].Name, results[i].Number, results[i].Street);
}
}
The results are:
Chris 123 Broadway
Ben 456 Broadway
Joe 789 Main
This seems to be what you are after, so I don't think the problem is in the code you've given. Perhaps it is in the data itself (if multiple streets include the same house range) or something else. You may need to supply more specific data that demonstrates your problem.
Here's the same code implemented using DataTables instead of classes. It yields the same result:
System.Data.DataTable DT1 = new System.Data.DataTable("Houses");
System.Data.DataTable DT2 = new System.Data.DataTable("Streets");
DT1.Columns.Add("Name", typeof(string));
DT1.Columns.Add("Number", typeof(int));
DT2.Columns.Add("Street", typeof(string));
DT2.Columns.Add("FirstHouseNumber", typeof(int));
DT2.Columns.Add("LastHouseNumber", typeof(int));
DT1.Rows.Add("Chris", 123);
DT1.Rows.Add("Ben", 456);
DT1.Rows.Add("Joe", 789);
DT2.Rows.Add("Broadway", 100, 500);
DT2.Rows.Add("Main", 501, 1000);
var funcquery = from System.Data.DataRow name in DT1.Rows
from System.Data.DataRow street in DT2.Rows
where (int)name["Number"] >= (int)street["FirstHouseNumber"]
&& (int)name["Number"] <= (int)street["LastHouseNumber"]
select new { Name = name["Name"], Number = name["Number"], Street = street["Street"] };
var results = funcquery.ToArray();
for (int i = 0; i < results.Length; i++)
{
Console.WriteLine("{0} {1} {2}", results[i].Name, results[i].Number, results[i].Street);
}
Related
How do I output the value of this table because it outputs to me:
lab5.FootballTeam - lab5.Tournament - System.Collections.Generic.HashSet`1[System.Int32]
lab5.FootballTeam - lab5.Tournament - System.Collections.Generic.HashSet`1[System.Int32]
lab5.FootballTeam - lab5.Tournament - System.Collections.Generic.HashSet`1[System.Int32]
And i need:
title1 city1 1910 turnirtilte1 true 1950 1980 1989
title2 city2 1920 turnirtilte1 true 1950 1991 1995
title3 city3 1930 turnirtilte1 true 1950 2002
Here is my code:
using System;
using System.Data;
using System.Collections.Generic;
namespace lab5
{
class FootballTeam
{
public string title;
public string city;
public int foundationYear;
public FootballTeam(string title1, string city1, int foundationYear1)
{
title = title1;
city = city1;
foundationYear = foundationYear1;
}
public string Print()
{
return $"{title},{city},{foundationYear}";
}
}
class Tournament
{
public string title;
public Boolean international;
public int foundationYear;
public Tournament(string title1, Boolean international1, int foundationYear1)
{
title = title1;
international = international1;
foundationYear = foundationYear1;
}
public string Print()
{
return $"{title},{international},{foundationYear}";
}
}
class Program
{
static void Main(string[] args)
{
var date1 = new DateTime(2008, 5, 1);
HashSet<int> set1 = new HashSet<int>();
HashSet<int> set2 = new HashSet<int>();
HashSet<int> set3 = new HashSet<int>();
HashSet<int> turnir = new HashSet<int>();
FootballTeam team1 = new FootballTeam("title1", "city1", 1910);
FootballTeam team2 = new FootballTeam("title2", "city2", 1920);
FootballTeam team3 = new FootballTeam("title3", "city3", 1930);
Tournament tournament1 = new Tournament("turnirtilte1", true, 1950);
turnir.Add(1980);
turnir.Add(1989);
turnir.Add(1991);
turnir.Add(1995);
turnir.Add(2002);
set1.Add(1980);
set1.Add(1989);
set2.Add(1991);
set2.Add(1995);
set3.Add(2002);
DataTable dt = new DataTable();
dt.Columns.Add("FootballTeam", typeof(FootballTeam));
dt.Columns.Add("Tournament", typeof(Tournament));
dt.Columns.Add("HashSet", typeof(HashSet<int>));
dt.Rows.Add(team1, tournament1, set1);
dt.Rows.Add(team2, tournament1, set2);
dt.Rows.Add(team3, tournament1, set3);
var selectedBooks = dt.Select();
foreach (var b in selectedBooks)
{
Console.WriteLine("{0} - {1} - {2}", b["FootballTeam"], b["Tournament"], b["HashSet"]);
}
}
}
}
Thank you very much.
Rename the Print() methods so they override ToString(). The WriteLine() method automatically calls ToString() when formatting the value into the template.
Alternatively, you can change b["FootballTeam"] to b["FootballTeam"].Print(), and do the same for the Tournament.
Then you also need to loop through your HashSet. Collections in .Net will never automatically print the items. You have to do that work yourself to print each individual item yourself. Sometimes you can use String.Join() to help with this.
Trying to Generate a Dynamic Linq Query, based on DataTable returned to me... The column names in the DataTable will change, but I will know which ones I want to total, and which ones I will want to be grouped.
I can get this to work with loops and writing the output to a variable, then recasting the parts back into a data table, but I'm hoping there is a more elegant way of doing this.
//C#
DataTable dt = new DataTable;
Dt.columns(DynamicData1)
Dt.columns(DynamicData1)
Dt.columns(DynamicCount)
In this case the columns are LastName, FirstName, Age. I want to total ages by LastName,FirstName columns (yes both in the group by). So one of my parameters would specify group by = LastName, FirstName and another TotalBy = Age. The next query may return different column names.
Datarow dr =..
dr[0] = {"Smith","John",10}
dr[1] = {"Smith","John",11}
dr[2] = {"Smith","Sarah",8}
Given these different potential columns names...I'm looking to generate a linq query that creates a generic group by and Total output.
Result:
LastName, FirstName, AgeTotal
Smith, John = 21
Smith, Sarah = 8
If you use a simple converter for Linq you can achieve that easily.
Here a quick data generation i did for the sample :
// create dummy table
var dt = new DataTable();
dt.Columns.Add("LastName", typeof(string));
dt.Columns.Add("FirstName", typeof(string));
dt.Columns.Add("Age", typeof(int));
// action to create easily the records
var addData = new Action<string, string, int>((ln, fn, age) =>
{
var dr = dt.NewRow();
dr["LastName"] = ln;
dr["FirstName"] = fn;
dr["Age"] = age;
dt.Rows.Add(dr);
});
// add 3 datarows records
addData("Smith", "John", 10);
addData("Smith", "John", 11);
addData("Smith", "Sarah", 8);
This is how to use my simple transformation class :
// create a linq version of the table
var lqTable = new LinqTable(dt);
// make the group by query
var groupByNames = lqTable.Rows.GroupBy(row => row["LastName"].ToString() + "-" + row["FirstName"].ToString()).ToList();
// for each group create a brand new linqRow
var linqRows = groupByNames.Select(grp =>
{
//get all items. so we can use first item for last and first name and sum the age easily at the same time
var items = grp.ToList();
// return a new linq row
return new LinqRow()
{
Fields = new List<LinqField>()
{
new LinqField("LastName",items[0]["LastName"].ToString()),
new LinqField("FirstName",items[0]["FirstName"].ToString()),
new LinqField("Age",items.Sum(item => Convert.ToInt32(item["Age"]))),
}
};
}).ToList();
// create new linq Table since it handle the datatable format ad transform it directly
var finalTable = new LinqTable() { Rows = linqRows }.AsDataTable();
And finally here are the custom class that are used
public class LinqTable
{
public LinqTable()
{
}
public LinqTable(DataTable sourceTable)
{
LoadFromTable(sourceTable);
}
public List<LinqRow> Rows = new List<LinqRow>();
public List<string> Columns
{
get
{
var columns = new List<string>();
if (Rows != null && Rows.Count > 0)
{
Rows[0].Fields.ForEach(field => columns.Add(field.Name));
}
return columns;
}
}
public void LoadFromTable(DataTable sourceTable)
{
sourceTable.Rows.Cast<DataRow>().ToList().ForEach(row => Rows.Add(new LinqRow(row)));
}
public DataTable AsDataTable()
{
var dt = new DataTable("data");
if (Rows != null && Rows.Count > 0)
{
Rows[0].Fields.ForEach(field =>
{
dt.Columns.Add(field.Name, field.DataType);
});
Rows.ForEach(row =>
{
var dr = dt.NewRow();
row.Fields.ForEach(field => dr[field.Name] = field.Value);
dt.Rows.Add(dr);
});
}
return dt;
}
}
public class LinqRow
{
public List<LinqField> Fields = new List<LinqField>();
public LinqRow()
{
}
public LinqRow(DataRow sourceRow)
{
sourceRow.Table.Columns.Cast<DataColumn>().ToList().ForEach(col => Fields.Add(new LinqField(col.ColumnName, sourceRow[col], col.DataType)));
}
public object this[int index]
{
get
{
return Fields[index].Value;
}
set
{
Fields[index].Value = value;
}
}
public object this[string name]
{
get
{
return Fields.Find(f => f.Name == name).Value;
}
set
{
var fieldIndex = Fields.FindIndex(f => f.Name == name);
if (fieldIndex >= 0)
{
Fields[fieldIndex].Value = value;
}
}
}
public DataTable AsSingleRowDataTable()
{
var dt = new DataTable("data");
if (Fields != null && Fields.Count > 0)
{
Fields.ForEach(field =>
{
dt.Columns.Add(field.Name, field.DataType);
});
var dr = dt.NewRow();
Fields.ForEach(field => dr[field.Name] = field.Value);
dt.Rows.Add(dr);
}
return dt;
}
}
public class LinqField
{
public Type DataType;
public object Value;
public string Name;
public LinqField(string name, object value, Type dataType)
{
DataType = dataType;
Value = value;
Name = name;
}
public LinqField(string name, object value)
{
DataType = value.GetType();
Value = value;
Name = name;
}
public override string ToString()
{
return Value.ToString();
}
}
I think I'd just use a dictionary:
public Dictionary<string, int> GroupTot(DataTable dt, string[] groupBy, string tot){
var d = new Dictionary<string, int>();
foreach(DataRow ro in dt.Rows){
string key = "";
foreach(string col in groupBy)
key += (string)ro[col] + '\n';
if(!d.ContainsKey(key))
d[key] = 0;
d[key]+= (int)ro[tot];
}
return d;
}
If you want the total on each row, we could get cute and create a column that is an array of one int instead of an int:
public void GroupTot(DataTable dt, string[] groupBy, string tot){
var d = new Dictionary<string, int>();
var dc = dt.Columns.Add("Total_" + tot, typeof(int[]));
foreach(DataRow ro in dt.Rows){
string key = "";
foreach(string col in groupBy)
key += (string)ro[col] + '\n'; //build a grouping key from first and last name
if(!d.ContainsKey(key)) //have we seen this name pair before?
d[key] = new int[1]; //no we haven't, ensure we have a tracker for our total, for this first+last name
d[key][0] += (int)ro[tot]; //add the total
ro[dc] = d[key]; //link the row to the total tracker
}
}
At the end of the operation every row will have an array of int in the "Total_age" column that represents the total for that First+Last name. The reason I used int[] rather than int, is because int is a value type, whereas int[] is a reference. Because as the table is being iterated each row gets assigned a reference to an int[] some of them with the same First+Last name will end up with their int[] references pointing to the same object in memory, so incrementing a later one increments all the earlier ones too (all "John Smith" rows total column holds a refernece to the same int[]. If we'd made the column an int type, then every row would point to a different counter, because every time we say ro[dc] = d[key] it would copy the current value of d[key] int into ro[dc]'s int. Any reference type would do for this trick to work, but value types wouldn't. If you wanted your column to be value type you'd have to iterate the table again, or have two dictionaries, one that maps DataRow -> total and iterate the keys, assigning the totals back into the row
I try to build some table inside a text file which
look like this:
Name Grade
--------------------
John 100
Mike 94
...
...
I have this bunch of code:
List<string> NamesList = new List<string>();
List<int> Grades = new List<int>();
Grades.Add(98);
Grades.Add(100);
NamesList.Add("John");
NamesList.Add("Alon");
if (NamesList.Count() == Grades.Count())
{
var length = NamesList.Count();
var min = Grades.Min();
var max = Grades.Max();
using (System.IO.StreamWriter myF =
new System.IO.StreamWriter(#"C:\Users\axcel\textfolder\myFile.txt", true))
{
for (int i = 0; i < length; i++)
{
if (i == 0)
{
myF.WriteLine("Name Age Grade");
myF.WriteLine("==================================");
}
myF.WriteLine(NamesList.ElementAt(i));
myF.WriteLine(" ");
myF.WriteLine(Grades.ElementAt(i));
}
}
}
but my problem is that writing the grades after the names it is writing in a new line. I thought of writing it together to a string and to streaming it but I want to avoid an extra computing...
How can I solve it?
WriteLine() always add a new line after your text. So in your case it should be
myF.Write(NamesList.ElementAt(i));
myF.Write(" ");
myF.WriteLine(Grades.ElementAt(i));
var students = new List<(string name, int age, int grade)>()
{
("John", 21, 98),
("Alon", 45, 100)
};
students.Add(("Alice", 35, 99));
using (var writer = new StreamWriter("myFile.txt"))
{
writer.WriteLine(string.Join("\t", "Name", "Age", "Grade"));
foreach(var student in students)
{
writer.WriteLine(string.Join("\t", student.name, student.age, student.grade));
}
}
As some comments have suggested you could use a Student class to group name, age and grade. In this example I've used a Value Tuple instead.
You can see how it improves the readability of the code and you can focus on the problem you are actually trying to solve. You can reduce your write operation to a simple, readable expression - meaning you are less likely to make mistakes like mixing up Write and WriteLine.
You can always align the text by using string interpolation alignment.
To follow some of the comments, I also urge you to build a class holding the values.
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public int Grade { get; set; }
}
And here is the code using string interpolation alignment
var students = new List<Student>
{
new Student {Name = "John", Age = 10, Grade = 98},
new Student {Name = "Alon", Age = 10, Grade = 100}
};
var minGrade = students.Min(s => s.Grade);
var maxGrade = students.Max(s => s.Grade);
using (var myF = new System.IO.StreamWriter(#"C:\Users\axcel\textfolder\myFile.txt", true))
{
myF.WriteLine($"{"Name",-15}{"Age",-10}{"Grade",5}");
myF.WriteLine("==============================");
foreach (var student in students)
{
myF.WriteLine($"{student.Name,-15}{student.Age,-10}{student.Grade,5}");
}
}
This will produce the following result:
Name Age Grade
==============================
John 10 98
Alon 10 100
Positive numbers are right-aligned and negative numbers are left-aligned
You can read more about it on the string interpolation page at Microsoft Docs
To solve the issue you are having, you could just use:
myF.WriteLine(NamesList.ElementAt(i) + " " + Grades.ElementAt(i));
However the code you provided would benefit from being modified as described in the comments (create a class, use FileHelpers, etc.)
Solution 1:
Why you are not trying the concatenating the two string like:
string line = NamesList.ElementAt(i) + " " + Grades.ElementAt(i);
myF.WriteLine(line);
OR
Solution 2:
What you are using is WriteLine("Text") function which always writes the text to next line. Instead you can use Write("Text") function which will write the string on same line. you can try like:
myF.Write(NamesList.ElementAt(i));
myF.Write(" ");
myF.Write(Grades.ElementAt(i));
myF.WriteLine(); // Here it will enter to new line
I am have trouble getting my program to print out an array of Employee class objects neat and orderly. I will proved my code, could someone please let me know how I would structure my write statement to make this happen.
What the output actually is:
Bruce Wayne, 123456, 25.88, 35.50
Clark Kent 232344 25.88 38.75
Diana Prince 657659 27.62 30.25
Hal Jordan 989431 23.14 44.25
Barry Allan 342562 25.12 25.50
Arthur Curry 565603 21.09 23.75
John Jones 812984 18.99 32.75
Dinah Lance 342988 18.99 34.50
Oliver Queen 340236 17.45 41.25
Ray Palmer 120985 24.75 40.00
Ronald Raymond 239824 16.43 35.00
Carter Hall 657123 19.34 42.75
Shayera Hol 761742 16.73 38.50
What the output should be:
Bruce Wayne 123456 25.88 35.50
Clark Kent 232344 25.88 38.75
Diana Prince 657659 27.62 30.25
Hal Jordan 989431 23.14 44.25
Barry Allan 342562 25.12 25.50
Arthur Curry 565603 21.09 23.75
John Jones 812984 18.99 32.75
Dinah Lance 342988 18.99 34.50
Oliver Queen 340236 17.45 41.25
Ray Palmer 120985 24.75 40.00
Ronald Raymond 239824 16.43 35.00
Carter Hall 657123 19.34 42.75
Shayera Hol 761742 16.73 38.50
// This is from the Employee class.
public void PrintEmployee()
{
Console.WriteLine(name + " " + number + " " + rate + " " + hours + "
" + gross);
}
// This is from main program.
for (int i = 0; i < Employees.Length; i++)
{
if (Employees[i] == null)
{
break;
}
Employees[i].PrintEmployee();
}
If you need more code please let me know.
Also if anyone knows a nicer way of printing the table please let me know.
All help is greatly appreciated.
If you look into the String.PadRight() method, you will see that it pads a string with whitespace characters until it is a specified width. We can do this with the name field, choosing some reasonable maximum name length (perhaps 25?) to size the column.
Also, instead of a PrintEmployee method, you might consider overwriting the ToString() method on your Employee class, so the default of employee.ToString() will look the way you want. For example:
public class Employee
{
public string Name { get; set; }
public int Number { get; set; }
public double Rate { get; set; }
public double Hours { get; set; }
public override string ToString()
{
return $"{Name.PadRight(20)} {Number} {Rate:00.00} {Hours:00.00}";
}
}
Now that we have the names displayed in a 20-character column (and the doubles are displayed with proper decimal places), things should line up well:
static void Main()
{
var employees = new List<Employee>
{
new Employee {Name = "Bruce Wayne", Number = 123456, Rate = 25.88, Hours = 35.5},
new Employee {Name = "Clark Kent", Number = 232344, Rate = 25.88, Hours = 38.75},
new Employee {Name = "Diana Prince", Number = 657659, Rate = 27.62, Hours = 30.25},
new Employee {Name = "Hal Jordan", Number = 989431, Rate = 23.14, Hours = 44.25},
new Employee {Name = "Barry Allan", Number = 342562, Rate = 25.12, Hours = 25.50},
new Employee {Name = "Arthur Curry", Number = 565603, Rate = 21.09, Hours = 23.75},
new Employee {Name = "John Jones", Number = 812984, Rate = 18.99, Hours = 32.75},
new Employee {Name = "Dinah Lance", Number = 342988, Rate = 18.99, Hours = 34.50},
new Employee {Name = "Oliver Queen", Number = 340236, Rate = 17.45, Hours = 41.25},
new Employee {Name = "Ray Palmer", Number = 120985, Rate = 24.75, Hours = 40.00},
new Employee {Name = "Ronald Raymond", Number = 239824, Rate = 16.43, Hours = 35.00},
new Employee {Name = "Carter Hall", Number = 657123, Rate = 19.34, Hours = 42.75},
new Employee {Name = "Shayera Hol", Number = 761742, Rate = 16.73, Hours = 38.50},
};
foreach (var employee in employees)
{
Console.WriteLine(employee);
}
Console.Write("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
Output
Have a look at this post here:
Format Strings in Console.WriteLine method
Padding will definitely be your answer. Depending on your needs, you can pad them at the Employee Class level on the getter in your properties.
Or do something like this:
var name = "Ronald Raymond";
var number = "239824";
var rate = "16.43";
var hours = "35.00";
var gross = "1256";
var formatted = string.Format("{0,-15}", name) +
string.Format("{0,-7}", number) +
string.Format("{0,-7}", rate) +
string.Format("{0,-7}", hours) +
string.Format("{0,-7}", gross);
Console.WriteLine(formatted);
For the fun of programming, you could also do this:
String FixStringLength(String input, int length) {
Char[] temp = new Char[length];
for(int i = 0; i < length; i++){
if(i < input.Length){
temp[i] = input[i];
}
else{
temp[i] = ' ';
}
}
return new String(temp);
}
And then
Console.WriteLine(FixStringLength(name, 30) + number);
I have one dataset which contains Student Fee Structure of a single FeeID as mentioned below:
FeeID Amount FeeItem Type
*---------------------------------------------*
10 7500 Admission Fee T
10 900 Annual Fee T
10 150 Application Fee T
10 850 Boy's Uniform T
10 50 Computer Fee R
For example I have another dataset having following data:
FeeID Amount FeeItem Type
*---------------------------------------------*
9 8500 Admission Fee T
9 950 Annual Fee T
9 150 Application Fee T
9 850 Boy's Uniform T
9 50 Computer Fee R
11 7500 Admission Fee T
11 900 Annual Fee T
11 150 Application Fee T
11 850 Boy's Uniform T
11 50 Computer Fee R
I want to check whether the set comprising of last three columns belongs to another dataset that contains data of all Fee Structures where FeeID may vary. Actual I want to retrieve matching FeeID having same Fee Structure.
In the above example if I search for the first one in the second it will return True and matching FeeID will be 11.
You can use this trick
Dataset ds1
DataSet ds2
DataSet dest;
dest.Merge(ds1);
dest.AcceptChanges();
dest.Merge(ds2);
diff = destination.GetChanges()
and check if diff is empty
Edit: I've made ade some improvements to below code. Following DataTable.IndexOf(otherTable) extension method checks if a table is a subset of another table.
You can pass column names that you want to ignore in the comparison. The order of the columns does not matter but the order of DataRows(the subset must have the same order of rows).
It returns -1 when no equal subset of DataRows was found or the first index in the main-table(this in the extension) where the equal subset starts.
public static class DataTableExtensions
{
public static int IndexOf(this DataTable tblMain, DataTable tblSub, params String[] ignoreColumns)
{
if (tblMain == null)
throw new ArgumentNullException("tblMain");
if (tblSub.Rows.Count == 0)
throw new ArgumentException("tblSub must not be empty", "tblSub");
if (tblSub.Rows.Count > tblMain.Rows.Count)
return -1;
IEnumerable<String> relevantColumnNames = tblSub.Columns.Cast<DataColumn>()
.Select(c => c.ColumnName)
.Except(ignoreColumns)
.ToArray();
foreach (String colName in relevantColumnNames)
if (!tblMain.Columns.Contains(colName))
return -1;
for (int mainRowIndex = 0; tblMain.Rows.Count - mainRowIndex >= tblSub.Rows.Count; mainRowIndex++)
{
// check if current window is equal to tblSub
bool allRowsAreEqual = true;
for (int windowIndex = mainRowIndex; windowIndex < tblSub.Rows.Count + mainRowIndex; windowIndex++)
{
DataRow currentMain = tblMain.Rows[windowIndex];
DataRow currentSub = tblSub.Rows[windowIndex - mainRowIndex];
bool allFieldsAreEqual = relevantColumnNames.All(colName =>
Object.Equals(currentMain[colName], currentSub[colName]));
if (!allFieldsAreEqual)
{
allRowsAreEqual = false;
break; // continue with next window in main-table
}
}
if (allRowsAreEqual)
return mainRowIndex;
}
// no equal window found in main-table
return -1;
}
}
Your sample data:
var TblSub = new DataTable();
var TblMain = new DataTable();
TblSub.Columns.Add("FeeID", typeof(int));
TblSub.Columns.Add("Amount", typeof(int));
TblSub.Columns.Add("FeeItem", typeof(string));
TblSub.Columns.Add("Type", typeof(char));
TblMain.Columns.Add("FeeID", typeof(int));
TblMain.Columns.Add("Amount", typeof(int));
TblMain.Columns.Add("FeeItem", typeof(string));
TblMain.Columns.Add("Type", typeof(char));
TblSub.Rows.Add(10, 7500, "Admission Free", 'T');
TblSub.Rows.Add(10, 900, "Annual Fee", 'T');
TblSub.Rows.Add(10, 150, "Application Free", 'T');
TblSub.Rows.Add(10, 850, "Boy's Uniform", 'T');
TblSub.Rows.Add(10, 50, "Computer Free", 'R');
TblMain.Rows.Add(9, 8500, "Admission Free", 'T');
TblMain.Rows.Add(9, 950, "Annual Fee", 'T');
TblMain.Rows.Add(9, 150, "Application Free", 'T');
TblMain.Rows.Add(9, 850, "Boy's Uniform", 'T');
TblMain.Rows.Add(9, 50, "Computer Free", 'R');
TblMain.Rows.Add(10, 7500, "Admission Free", 'T');
TblMain.Rows.Add(11, 900, "Annual Fee", 'T');
TblMain.Rows.Add(11, 150, "Application Free", 'T');
TblMain.Rows.Add(11, 850, "Boy's Uniform", 'T');
TblMain.Rows.Add(11, 50, "Computer Free", 'R');
You can use it in this way:
int firstIndex = TblMain.IndexOf(TblSub, "FeeID");
if (firstIndex == -1)
Console.Write("Second table does not contain first table");
else
Console.Write("Second table does contain first table at row-index " + firstIndex);
Output is:
Second table does contain first table at row-index 5
old approach:
You could use following method which checks whether or not two DataTables are equal.
You need to add using System.Linq for Enumerable.SequenceEqual. If you cannot use Linq, use loops.
static bool TablesEqual(DataTable table1, DataTable table2, params int[] skipColumns)
{
if (table1.Rows.Count != table2.Rows.Count)
return false;
for (int i = 0; i < table1.Rows.Count; i++)
{
var array1 = table1.Rows[i].ItemArray
.Where((obj, index) => !skipColumns.Contains(index));
var array2 = table2.Rows[i].ItemArray
.Where((obj, index) => !skipColumns.Contains(index)); ;
if (!array1.SequenceEqual(array2))
{
return false;
}
}
return true;
}
You just have to pass the indices of the columns you want o ignore, like:
bool allEqual = TablesEqual(t1, t2); // all equal
bool equalIgnore0 = TablesEqual(t1, t2, 0); // ignore first column
bool equalIgnore0and2 = TablesEqual(t1, t2, 0, 2); // ignore first and third column
Finally I am able to solve this issue. The following is my function. I am using the function TablesEqual as provided by Tim Schmelter. The function will return a valid FeeID which is a positive integer else -1 if no match is found.
private int MatchFeeID(DataTable Dt)
{
DataTable mainDt = bl.GetDataSet("Select * From FeeMaster"); //assume this is a function that will return all the fee structures from database.
var fids = (from row in mainDt.AsEnumerable().Distinct()
group row by row.Field<string>("fid") into rowGroup
select new
{
fid = rowGroup.Key
});
foreach (var fid in fids)
{
string id = fid.fid;
DataTable t1 = new DataTable();
DataTable t2 = new DataTable();
DataRow[] dr1 = mainDt.Select(String.Format("fid = '{0}'", id));
t1 = dr1.CopyToDataTable();
t2 = Dt;
bool res = TablesEqual(t1, t2, 0, 1);
if (res) return Convert.ToInt32(id);
}
return -1;
}