replace List.foreach to LINQ - c#

I'm new to LINQ and doing some experiments with it.
Sorry if it is a duplicate but I cant seem to find proper guide (for me) to it
I want to replace this code :
DataTable table
List<string> header = new List<string>();
table.Columns.Cast<DataColumn>().ToList().ForEach(col => header.Add(col.ColumnName));
with something LINQ like:
var LINQheader = from mycol in table.Columns select mycol.ColumnName;
LINQheader.tolist();
but it doesn't even compile.
what I want Is not a one line solution but would like some logic to understand how construct it with more complicated environments (Like choosing many node in XML with some logic)

here is the original code
table.Columns.Cast<DataColumn>().ToList().ForEach(col => header.Add(col.ColumnName));
Why Cast used?
because it allows you to treat DataColumnCollection items as a DataColumn not an object.
Why ToList used?
becuase it converts your IEnumerable to List and allows you to call ForEach because this function is special method that exists in List class.
Why ForEach used?
because it allows you to do what you want for each element on the list (in your case it adds column name of each column to another list(header)).
Simplified version:
now assume you want to add column names to header where they starts with "Student"
you can write something like this
DataTable table = new DataTable();
List<string> header = new List<string>();
foreach (DataColumn col in table.Columns)
{
if (col.ColumnName.StartsWith("Id")) // you can remove this line if you want to add all of them
header.Add(col.ColumnName);
}
you can also use this
table.Columns.Cast<DataColumn>()
.ToList()
.ForEach(col =>
{
if (col.ColumnName.StartsWith("Id"))
header.Add(col.ColumnName)
});
or
var headers = table.Columns.Cast<DataColumn>()
.Where(col => col.ColumnName.StartsWith("Id"))
.Select(col => col.ColumnName);
header.AddRange(headers);

You can use Enumerable.Aggregate() for this:
var header = table.Columns.Cast<DataColumn>().Aggregate(new List<string>(), (list, col) => { list.Add(col.ColumnName); return list; });
In general, Linq allows for retrieval and transformation of sequences of data from data sources. What you want to do in this question is to iterate over a sequence and return an immediate result. That isn't the primary focus of Linq, but there are methods that perform tasks like this, including Aggregate(), Average(), Count(), Max() and so on.

var LINQheader = from mycol in table.column select mycol.ColumnName;
LINQheader.tolist();
This will not compile as there is no such property in DataTable as column, there is only Columns, and you have to use .Cast() method as they are not implementing right interface (see #Uriil's answer).
Try this:
var LINQheader = from mycol in table.Columns.Cast<DataColumn>()
select mycol.ColumnName;
LINQheader.tolist();
If you want to use wrap it in an extension method, you can do it like this:
public static IEnumerable<string> GetHeaderColumns (this DataTable dataTable)
{
if (dataTable == null || !dataTable.Columns.Any())
{
yield break;
}
foreach (var col in dataTable.Columns.Cast<DataColumn>())
{
yield return col.ColumnName;
}
}

static void Main(string[] args)
{
DataTable tbl = new DataTable();
tbl.Columns.Add("A");
tbl.Columns.Add("B");
var p = from DataColumn col in tbl.Columns select col.ColumnName;
foreach(string a in p)
{
Console.WriteLine(a);
}
}
Here little code example. If you want to be List<string>, use ToList().
EDIT:
Like #Grundy says you missing to specify type of the col, which is DataColumn.
List<string> columnList = (from DataColumn mycol in table.Columns select mycol.ColumnName).ToList();
Here this will be your one line.

Why not simple select like
DataTable table;
IEnumerable<string> header = from mycol in table.Columns.Cast<DataColumn>()
select mycol.ColumnName;

You have some problems, which requires workaround:
ForEach is List specifict method, so can can not translate it into LINQ
LINQ is for data selection, aggregation, but not for data
modification
table.Columns, returns DataColumnCollection, which does not
implement IEnumerable<T>, so you will have to cast it anyway:
var LINQheader = from mycol in table.Columns.Cast<DataColumn>()
select name.ColumnName;

Related

Populate List<string> from DataTable column

I have a datatable dt with a single column and a list of strings. If I want to change this to a list (for example, to make it easier to compare with another list) I would default to something like:
var ls = new List<string>();
foreach (DataRow dr in dt.Rows)
{
ls.Add(dr["COLUMN_NAME"].ToString());
}
I don't think it's possible to get more efficient than that, but is there shorter way to write this, maybe using LINQ?
As mentioned in the comment, the LINQ way might be shorter in code, but not more efficient.
Anyway, here is the one liner LINQ version.
var list = new List<string>(dt.Rows.Cast<DataRow>().Select(r => r["COLUMN_NAME"].ToString()));

Compare two DataTable Rows remove identical rows besides one.

I want to check the rows of two data tables. If there is an exact match I want to remove all but one.
I've figured out how to compare two rows of data. I'm not sure the best way to return the cleaned up version without duplicates.
the tables within my program are pulling tables from a database, so i simplified them for the example.
Here's what I've worked out so far.
var table1 = new list<string>();
var table2 = new list<string>();
foreach (DataRow row1 in table.Rows)
foreach (DataRow row2 in table2.Rows)
{
var array1 = row1.ItemArray;
var array2 = row2.ItemArray;
if (array1.SequenceEqual(array2))
{
// store the unique elements within a new list?
// remove duplicates and return the remainder?
}
}
I thought using the Intersect() method might be an option as well.
Cast to a hashset of your desired type. They will automatically remove duplicates, as by definition hashset cannot have duplicate entries.
More info:
https://www.dotnetperls.com/hashset

Cannot copy data from query to datatable

I have a small problem, which I just cannot find how to fix it.
For my data table, Dictionar.DtDomenii, I need to copy the unique data from my other data table, Dictionar.Dt.
I wrote a query, but when using query.CopyToDataTable() to copy the data into my DtDomenii table, the "CopyToDataTable" function does not show...
Am I doing something wrong? Is there an easier way to copy distinct data (categories from my example) from one data table to another?
PS: I've already read the information from MSDN https://msdn.microsoft.com/en-us/library/bb386921%28v=vs.110%29.aspx
void LoadCategories()
{
var query = (from cat in Dictionar.dt.AsEnumerable()
select new
{
categorie = categorii.Field<string>("Categoria")
}).Distinct();
// This statement does not work:
Dictionar.dtDomenii = query.CopyToDataTable();
}
Only collections of DataRows can use the CopyToDataTable method. For example:
DataTable table = new DataTable();
table.AsEnumerable().CopyToDataTable(); // this works
List<DataRow> dataRows = new List<DataRow>();
dataRows.CopyToDataTable(); // this also works
List<string> strings = new List<string>();
strings.CopyToDataTable(); // this does not work
The select new... part of your query is converting the DataRows into objects. You need to convert the objects back into DataRows before you can use the CopyToDataTable method.
You might have better luck doing something like this:
DataTable copy = Dictionar.dt
.AsEnumerable() // now an enumerable of DataRow
.Distinct() // remove duplicates, still an enumerable of DataRow
.CopyToDataTable(); // done!
You can also make a complete copy of the table with Dictionar.dt.Copy(), then remove the duplicate rows manually.

Converting DataTable to Generic List<T>

I am trying to convert a Data table to a generic List. I am following this post. DataTable to List<object>.
Method is
public static List<MProps> TableToList<T1>(DataTable dt)
{
if (dt == null)
{
return null;
}
List<DataRow> rows = new List<DataRow>();
foreach (DataRow row in dt.Rows)
{
rows.Add(row);
}
return TableToList<T1>(rows);
}
I keep getting two errors at "return TableToList<T1>(rows);" Saying
Error 24 Argument 1: cannot convert from 'System.Collections.Generic.List<System.Data.DataRow>' to 'System.Data.DataTable' C:\Users..\Program.cs
AND
Error 23 The best overloaded method match for 'xx.Program.TableToList<T1>(System.Data.DataTable)' has some invalid arguments C:\Users\--\Program.cs
I cant figure out what is the problem.
Use
List<DataRow> rows = dt.AsEnumerable().ToList();
If you need to return List
return (from DataRow row in dt.Rows
select new MProps
{
//assign properties here
prop1 = row["colname"].ToString()
}).ToList();
In your return, you're trying to call the method again, but you're passing rows, which is a List<DataRow>. Your method expects a DataTable, so it's telling you that you're calling your method incorrectly.
Theoretically, you should be making a List<MProps> and returning that, rather than making a List<DataRow> and calling TableToList. Perhaps make a DataRowToMProp method and call that for every DataRow?
var lst = from x in dt.AsEnumerable()
where x.Field <string>("PersonName") == "ching" select x;

c# linq question

I have a data table and I want to select all distinct names from the result. I wrote following linq query for it.
var distinctRows = (from DataRow myDataRow in myDataTable.Rows
select new { col1 = myDataRow ["Name"]}).Distinct();
Now how can I iterate through distinctRows? Seems like I cannot do foreach(DataRow Row in distinctRows), It gives me "Cannot convert type 'AnonymousType#1' to 'System.Data.DataRow'" error
Since you're only selecting one field, you don't need an anonymous type here. Just select the names and then iterate over the distinct ones. To wit:
var distinctNames = (from DataRow myDataRow in myDataTable.Rows
select myDataRow.Field<string>("Name")
).Distinct();
foreach(var name in distinctNames) {
Console.WriteLine(name);
}
Note that the error makes it very clear what the problem is here. You are trying to convert an instance of an anonymous type to an instance of DataRow and that is impossible. Without changing your code, you could iterate this as
foreach(var item in distinctRows) {
Console.WriteLine((string)item.col1);
}
But I would change this as per the above as you don't need the anonymous type and your variable names and field names are poor.
Those aren't DataRows; they're anonymous objects.
To loop through them, you need to declare the variable using the var keyword.
However, there's no point in the anonymous type in the first place.
You can change your query to select myDataRow.Field<string>("Name") to get a set of strings.
You can use the keyword var to refer to anonymous types (which is what you're returning an IEnumerable<> of).
foreach(var row in distinctRows)
{
// do something with each anonymous type instance
}
Since you're only returning anonymous types with one string property however, you may as well project an IEnumerable<string>
That's because myDataRow["Name"] doesn't return a DataRow. Try
foreach(var item in distinctRows) {}
That's because the return value isn't a DataRow. It's an ad-hoc type that containes the property col1.
To build on SLaks answer . . .
var distinctRows = (from DataRow myDataRow in myDataTable.Rows
select new { col1 = myDataRow ["Name"]}).Distinct();
foreach(var row in distinctRows)
{
System.Console.Writeline(row.col1); //should work fine
}
The problem here is that you're selecting a new anonymous type by doing select new { col1 = myDataRow ["Name"]} and not the actual row itself. So when you try to iterate this as DataRow, it will error out because your anonymous type being selected is not of type DataRow.
If you want to be able to select a whole data row and not just the name field, you will need to implement a custom IEqualityComparer for data row to pass to the Distinct() extension method.
An example would be:
public class NameComparer : IEqualityComparer<DataRow>
{
public bool Equals(DataRow left, DataRow right)
{
return left.Field<string>("Name").Equals(right.Field<string>("Name"));
}
public int GetHashCode(DataRow obj)
{
return obj.ToString().GetHashCode();
}
}
Then using it:
var distinctRows = (from DataRow myDataRow in myDataTable.Rows
select myDataRow).Distinct(new NameComparer());
You can also foreach them but first you had to List them as the following :
List<string> rslt =(from DataRow myDataRow in myDataTable.Rows
select new { col1 = myDataRow ["Name"].ToString()}).Distinct().ToList();
foreach(string str in rlst)
{}
Hope this helped

Categories

Resources