Convert datatable to another datatable By Linq C#? - c#

i have a Datatable in which there are Columns are
Assigned to
Storyid,
Completed,
Effort. assigned to person can have Same Storyid with diffrent or same (Completed,Effort hours)(image attached)
input datatable
i want dataTable to like in this form where Assigned to Effort and completed hours will be Sum and there storyid's(image attached below
output datatable

I hope this is what you are looking for
var result = yourDatatable
.GroupBy(e => e.AssignedTo)
.ToList()
.Select(eg => new
{
AssignedTo = eg.Key,
CompletedSum = eg.Sum(p=>p.completed),
EfforSum = eg.Sum(p=>p.effort),
StoryIds = string.Join(",", eg.Select(i => i.storyid))
});

Related

The source contains no DataRows

DataTable dt = ds.Tables[4].AsEnumerable()
.Where(x => ((DateTime)x["EndDate"]).Date >= DateTime.Now.Date)
.CopyToDataTable();
ds.Tables[4] has rows but it throws the exception
"The source contains no DataRows."
Any idea how to handle or get rid of this exception?
ds.Tables[4] might, but the result of your linq-query might not, which is likely where the exception is being thrown. Split your method chaining to use interim parameters so you can be dead certain where the error is occurring. It'll also help you check for existing rows before you call CopyToDataTable() and avoid said exception.
Something like
DataTable dt = null;
var rows = ds.Tables[4].AsEnumerable()
.Where(x => ((DateTime)x["EndDate"]).Date >= DateTime.Now.Date);
if (rows.Any())
dt = rows.CopyToDataTable();
Another option is to use the ImportRow function on a DataTable
DataTable dt = ds.Tables[4].Clone();
var rows = ds.Tables[4].AsEnumerable()
.Where(x => ((DateTime)x["EndDate"]).Date >= DateTime.Now.Date);
foreach (var row in rows)
dt.ImportRow(row);
Simply splitting in two lines
var rowSources = ds.Tables[4].AsEnumerable()
.Where(x => ((DateTime)x["EndDate"]).Date >= DateTime.Now.Date);
if(rowSources.Any())
{
DataTable dt = rowSources.CopyToDataTable();
... code that deals with the datatable object
}
else
{
... error message ?
}
This allows to check if the result contains any DataRow, if yes then you could call the CopyToDataTable method.

LinQ returning no datarow error

In below code, Hours is passed with selected values from the UI as below
Hours = "10.1, 11.2";
If there is no value from UI, these will contain as below.
Hours = "";
Below code is to slipt them & get the value from Database. This gives error if the values are having "".
Error at q.CopyToDataTable() The source contains no DataRows.
public void Populate_Gridview(string Hours)
{
string[] selectedHours = Hours.Split(',');
var q = from a in dt.AsEnumerable()
where selectedHours.Contains(a.Field<double?>("Hours").ToString())
select a;
GridView1.DataSource = q.CopyToDataTable();
GridView1.DataBind();
}
If I have Double array, I cannot slipt them if the input is not having any value i.e. "". I get error Input string was not in a correct format.
Double[] selectedHours = Array.ConvertAll(All.Split(','), Double.Parse);
Please guide on how I can solve this issue.
Try this
var q = from a in dt.AsEnumerable()
where (selectedHours.Contains(a.Field<double?>("Hours").ToString())
|| selectedHours.Length==0)
select a;
First, i would improve the way you are deriving the doubles from the string since your way is error prone and inefficient. You could try this approach:
double[] selectedHours = hours.Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries)
.Select(str => {
str = str.Trim();
double hour;
bool isDouble = double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out hour);
return new { str, isDouble, hour };
})
.Where(h => h.isDouble)
.Select(h => h.hour)
.ToArray();
var q = from row in dt.AsEnumerable()
join hour in selectedHours
on row.Field<double?>("Hours") equals hour
select row;
var dataSource = dt.Clone(); // empty
if (q.Any())
dataSource = q.CopyToDataTable();
The first issue is that CopyToDataTable only works if the source has some rows in it. Otherwise, it cannot determine the columns to be included in the table. A possible solution is to create an empty destination table with the right columns, and copy the rows to that table:
var destTable = dt.Copy();
destTable.Rows.Clear();
q.CopyToDataTable(destTable, LoadOption.OverwriteChanges);
GridView1.DataSource = destTable;
GridView1.DataBind();
The other issue is that, when called on an empty string, by default Split returns an array with on empty string, hence the error when you call Double.Parse on it. To avoid this, use StringSplitOptions.RemoveEmptyEntries:
string[] splitted = All.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
Double[] selectedHours = Array.ConvertAll(splitted, Double.Parse);

FileNames in a DataTable - First Column

Is there a way to get the list of FileNames in a DataTable without using a foreach loop?
DataTable dtOutput = new DataTable();
dtOutput.Columns.Add("FileName", typeof(string));
dtOutput.Columns.Add(Col2..., typeof(decimal));
...
foreach (string file in Directory.EnumerateFiles(txtBoxReadFrom.Text, txtBoxTargetFilter.Text))
{
dtOutput.Rows.Add(Path.GetFileName(file));
}
progressBar1.Maximum = dtOutput.Rows.Count;
Linq hides the loops from you, it uses them anyway:
List<String> allFileNames = dtOutput.AsEnumerable()
.Select(r => r.Field<string>("FileName"))
.ToList();
But why do you want a list at all when you already have the table which is also an in-memory collection?
If you want to diplays these file-names in another multiline TextBox you can use:
txtBoxWriteTo.Lines = dtOutput.AsEnumerable()
.Select(r => r.Field<string>("FileName"))
.ToArray();
Convert the DataTable object to Enumerable().
Apply the select clause with the column name as field name.
Cast it to a string array.
string[] strSummCities = dtTemp1.AsEnumerable().Select(s => s.Field<string>("City")).ToArray<string>();
Hope this will help t resolve your query.
you should add a reference to System.Data.DataSetExtension in order to have the CopyToDataTable() for enumerable values, after this you can use:
DataTable dtOutput =
Directory.EnumerateFiles(txtBoxReadFrom.Text, txtBoxTargetFilter.Text).Select(r =>
{
DataRow row = dtOutput.NewRow();
row["fileName"] = r;
return row;
}).CopyToDataTable();

LINQ: Add RowNumber Column

How can the query below be modified to include a column for row number (ie: one-based index of results)?
var myResult = from currRow in someTable
where currRow.someCategory == someCategoryValue
orderby currRow.createdDate descending
select currRow;
EDIT1: I'm looking for the results to be {idx, col1, col2...col-n} not {idx, row}.
EDIT2: The row number should correspond to result rows not the table rows.
EDIT3: I DataBind these results to a GridView. My goal was to add a row number column to the GridView. Perhaps a different approach would be better.
Use the method-syntax where Enumerable.Select has an overload with the index:
var myResult = someTable.Select((r, i) => new { Row = r, Index = i })
.Where(x => x.Row.someCategory == someCategoryValue)
.OrderByDescending(x => x.Row.createdDate);
Note that this approach presumes that you want the original index of the row in the table and not in the filtered result since i select the index before i filter with Where.
EDIT: I'm looking for the results to be {idx, col1, col2...col-n} not
{idx, row}. The row number should correspond to result rows not
the table rows.
Then select the anonymous type with all columns you need:
var myResult = someTable.Where(r => r.someCategory == someCategoryValue)
.OrderByDescending(r => r.createdDate)
.Select((r, i) => new { idx = i, col1 = r.col1, col2 = r.col2, ...col-n = r.ColN });
Use this Select method:
Projects each element of a sequence into a new form by incorporating the element's index.
Example:
var myResult = someTable.Where(currRow => currRow.someCategory == someCategoryValue)
.OrderByDescending(currRow => currRow.createdDate)
.Select((currRow, index) => new {Row = currRow, Index = index + 1});
In response to your edit:
If you want a DataTable as result, you can go the non-Linq way by simply using a DataView and add a additional column afterwards.
someTable.DefaultView.RowFilter = String.Format("someCategory = '{0}'", someCategoryValue);
someTable.DefaultView.Sort = "createdDate";
var resultTable = someTable.DefaultView.ToTable();
resultTable.Columns.Add("Number", typeof(int));
int i = 0;
foreach (DataRow row in resultTable.Rows)
row["Number"] = ++i;
what about?
int i;
var myResult = from currRow in someTable
where currRow.someCategory == someCategoryValue
orderby currRow.createdDate descending
select new {Record = i++, currRow};
Just for fun, here's an alternative to Select with two arguments:
var resultsWithIndexes = myResult.Zip(Enumerable.Range(1, int.MaxValue - 1),
(o, i) => new { Index = i, Result = o });
According to you edit 1. NO, YOU CAN'T Linq returns the table as it is. You can build each column, but you lose the power of mapped entities.
This has been asked multiple times before: How do you add an index field to Linq results
There is no straightforward way if want to keep a flat list of columns (i.e. OP's Edit2) and also want a generic solution that works with any IEnumerable without requiring you to list out the set of expected columns.
However, there is a roundabout way to kinda go about it which is to dump the query results into a DataTable using the ToDataTable() method from here and then add a RowNumber column to that table.
var table = query.ToList().ToDataTable();
table.Columns.Add("RowNum", typeof(int));
int i = 0;
foreach (DataRow row in table.Rows)
row["RowNum"] = ++i;
This would likely cause performance issues with large datasets but it's not insanely slow either. On my machine a dataset with ~6500 rows took 33ms to process.
If your original query returned an anonymous type, then that type definition will get lost in the conversion so you'll lose the static typing on the column names of the resulting IEnumerable when you call table.AsEnumerable(). In other words, instead of being able to write something like table.AsEnumerable().First().RowNum you instead have to write table.AsEnumerable().First()["RowNum"]
However, if you don't care about performance and really want your static typing back, then you can use JSON.NET to convert the DataTable to a json string and then back to a list based on the anonymous type from the original query result. This method requires a placeholder RowNum field to be present in the original query results.
var query = (from currRow in someTable
where currRow.someCategory == someCategoryValue
orderby currRow.createdDate descending
select new { currRow.someCategory, currRow.createdDate, RowNum = -1 }).ToList();
var table = query.ToDataTable();
//Placeholder RowNum column has to already exist in query results
//So not adding a new column, but merely populating it
int i = 0;
foreach (DataRow row in table.Rows)
row["RowNum"] = ++i;
string json = JsonConvert.SerializeObject(table);
var staticallyTypedList = JsonConvert.DeserializeAnonymousType(json, query);
Console.WriteLine(staticallyTypedList.First().RowNum);
This added about 120ms to the processing time for my 6500 item dataset.
It's crazy, but it works.
I know I'm late to the party, but I wanted to show what worked for me.
I have a list of objects, and the object has an integer property on it for "row number"... or in this case, "Sequence Number". This is what I did to populate that field:
myListOfObjects = myListOfObjects.Select((o, i) => { o.SequenceNumber = i; return o; }).ToList();
I was surprised to see that this worked.
This one helped me in my case - Excel sheet extraction. anonymous type
var UploadItemList = ItemMaster.Worksheet().AsEnumerable().Select((x, index) => new
{
Code = x["Code"].Value == null ? "" : x["Code"].Value.ToString().Trim(),
Description = x["Description"].Value == null ? "" : x["Description"].Value.ToString().Trim(),
Unit = x["Unit"].Value == null ? "" : x["Unit"].Value.ToString().Trim(),
Quantity = x["Quantity"].Value == null ? "" : x["Quantity"].Value.ToString().Trim(),
Rate = x["Rate"].Value == null ? "" : x["Rate"].Value.ToString().Trim(),
Amount = x["Amount"].Value == null ? "" : x["Amount"].Value.ToString().Trim(),
RowNumber = index+1
}).ToList();
int Lc = 1;
var Lst = LstItemGrid.GroupBy(item => item.CategoryName)
.Select(group => new { CategoryName = group.Key, Items = group.ToList() ,RowIndex= Lc++ })
.ToList();

Copying data from var to DataTable after LINQ query

I am using LINQ query to filter data from a datatable and placing the data in to another datatable. For this I am using foreach statement to copy values from var to datatable. The datatable will be containing a very huge no. of rows so can you suggest me a way in which I can do the copying in a single go?
var drdatedisp = from row in dtfullreport.AsEnumerable()
group row by row.Field<string>("Order_Date") into g
select new
{
Order_Date = g.Key,
totalQnty = g.Sum(a => a.Field<int>("Item_Quantity")),
totalTax = g.Sum(a => float.Parse(a.Field<decimal>("TAXAMT").ToString())),
totalAmt = g.Sum(a => float.Parse(a.Field<decimal>("VALAMT").ToString()))
};
DataTable dtdatedisp = new DataTable();
dtdatedisp.Columns.Add("Order_Date");
dtdatedisp.Columns.Add("Item_Quantity");
dtdatedisp.Columns.Add("TAXAMT");
dtdatedisp.Columns.Add("VALAMT");
dtdatedisp.Rows.Clear();
foreach (var g in drdatedisp)
{
DataRow newRow1 = dtdatedisp.NewRow();
newRow1[0] = g.Order_Date;
newRow1[1] = g.totalQnty;
newRow1[2] = String.Format("{0:0.00}", g.totalTax);
newRow1[3] = String.Format("{0:0.00}", g.totalAmt);
dtdatedisp.Rows.Add(newRow1);
}
}
please see that there will be very less no of iterations....
is there any way ?? Can you Help me ??
There is a extension method called CopyToDataTable which is unfortunately on IEnumerable Data Row object. But with the use of following link, you can create/copy same extension method which will be called on any IEnumerable object And this would match your requirement.
Here is the link
With this you can directly write something like this in your code
drdatedisp.CopyToDataTable();
use CopyToDataTable(); Verified the result:
var results = (from myRow in myData.AsEnumerable()
where myRow.Field<double>("Num1") == 1
select myRow).CopyToDataTable();
DataTable dtTemp = (DataTable)results;
gvMain.DataSource = dtTemp;

Categories