I'm getting a collection of records in a DataTable and binding it to a grid control. Before binding it I'm sorting the data based on few conditions. For brevity I'm will explain a test scenario.
I've two fields Category and Country. I want to first sort the records based on category and then by country. But the catch here is I want to push all the empty category values to the end and then sort based on the alphabetical order.
For that I'm doing -
var rows = dt.AsEnumerable()
.OrderBy(r => string.IsNullOrEmpty(Convert.ToString(r["Category"]))) //push empty values to bottom
.ThenBy(r => Convert.ToString(r["Category"]))
.ThenBy(r => Convert.ToString(r["Country"]))
But now, the fields based on which I need to sort, is dynamic which I'm having in an array.
How can I use the lambda expressions to order the records dynamically based on the fields? (pushing the empty values to the end)
I assume the array you're talking about is an array of strings.
var columns = new string[] { "Category", "Country" };
var rows = dt.AsEnumerable().OrderBy(x => 0);
foreach(var columnName in columns)
{
rows = rows.ThenBy(r => string.IsNullOrEmpty(Convert.ToString(r[category])))
.ThenBy(r => Convert.ToString(r[category]));
}
Because LINQ uses deferred execution, your query will not be evaluated until you actually need results. That's why you can construct it in multiple steps like in the example above.
Related
I am working on google spreadsheets, user connect its spreadsheet, and the system pushed data to user connected spreadsheet at day end.
here are spreadsheet columns
The sequence of columns is
"PatientName","DOB","StripeId","Card","ChargeCreated","Status","Amount"
here is the code is used
var GoogleSpreadSheetData = db.GoogleSpreadSheetDatas
.ToList()
.Select(x => new List<string>{
x.PatientName.Trim(),
x.DOB.Trim(),
x.StripeId.Trim(),
x.Card.Trim(),
x.ChargeCreated.Trim(),
x.Status.Trim(),
x.Amount.Trim(),
})
.ToList();
the sequence of google spread sheet columns and query return is same sequence like
"PatientName","DOB","StripeId","Card","ChargeCreated","Status","Amount"
and data will mapped on exact column in spread sheet Reference image.
if user changes sequence of columns like this image, replaced PatientName column to DOB. when next time data push to spreadsheet PatientName data is pushed in the DOB column and vice versa.
Before pushing data to the spreadsheet, firstly I call google API(using Developer Metadata) to get a sequence of that spreadsheet, I got the updated column sequence in response.
My question is
How can I change the sequence on query select?
Can I rearrange the list on query select or after?
Please help to solve this problem.
Thank you.
Assuming your MetaData query provides you with a List<string> of column names:
List<string> spreadsheetColSeq;
Then you can query the database to retrieve the rows:
var dbData = db.GoogleSpreadSheetDatas
.Select(x => new List<string> {
x.PatientName.Trim(),
x.DOB.Trim(),
x.StripeId.Trim(),
x.Card.Trim(),
x.ChargeCreated.Trim(),
x.Status.Trim(),
x.Amount.Trim(),
})
.ToList();
And then you can create a mapping from the query order to the spreadsheet order and reorder the each row's List<string> to match the new order:
var origSeq = new[] { "PatientName", "DOB", "StripeId", "Card", "ChargeCreated", "Status", "Amount" };
var colOrder = spreadsheetColSeq
.Select(colName => Array.IndexOf(origSeq, colName))
.ToList();
var GoogleSpreadSheetData = dbData
.Select(row => colOrder.Select(idx => row[idx]).ToList())
.ToList();
Note: If columns may be deleted, this will crash since Array.IndexOf will return -1 for a missing value. You could filter colOrder by adding .Where(idx => idx > -1) before the ToList() to skip missing columns.
I have a List of String[], which I am trying to convert to a dataset/datatable using LINQ.
I parsed the text file to list, in which the first row has 4 columns and others have data associated with columns.
Everything comes up as an array in the list.
List[10] where List [0] has string[4] items.
List<string[]> list = File.ReadLines(s)
.Select(r => r.TrimEnd('#'))
.Select(line => line.Split(';'))
.ToList();
DataTable table = new DataTable();
table.Columns.AddRange(list.First().Select(r => new DataColumn(r.Value)).ToArray());
list = list.Skip(1).ToArray().ToList();
list.ForEach(r => table.Rows.Add(r.Select(c => c.Value).Cast<object>().ToArray()));
The LINQ doesn't accept the Value property.
Can some one suggest the simple and efficient way for this implementation?
System.String doesn't have a property named Value.
If you want to create a column for each item in the first row, just give it the strings:
table.Columns.AddRange(list.First().Select(r => new DataColumn(r)).ToArray());
// You don't need ToArray() here.
list = list.Skip(1).ToList();
// Get rid of Value in this line too, and you don't need
// .Select(c => c) either -- that's a no-op so leave it out.
list.ForEach(row => table.Rows.Add(row.Cast<object>().ToArray()));
There's no Dictionary here. list.First() is an array of strings. When you call Select on an array, it just passes each item in the array to the lambda in turn.
Dictionary<TKey,TValue>.Select() passes the lambda a series of KeyValuePair<TKey, TValue>. Different class, different behavior.
I think what I need is relatively simple but every example I Google just returns results using First(), which I'm already doing. Here is my expression:
var options = configData.AsEnumerable().GroupBy(row => row["myColumn"]).Select(grp => grp.First());
What I need is only ONE column from the grp portion and to be able to suffix .ToList() on there without an error. As it stands I receive 4 columns, but only need a specific one, kind of like if this (grp => grp["myColumn"]), didn't result in error the Error 153 Cannot apply indexing with [] to an expression of type 'System.Linq.IGrouping<object,System.Data.DataRow>'
Also, Key does not work in the grouping portion as these results are from a DataTable object. See here - >
If you want only the keys, you can use
var options = configData.AsEnumerable().Select(row=>row["myColumn"]).Distinct();
I think that this is what you want:
configData.AsEnumerable()
.GroupBy(r => r["myColumn"])
.Select(g => new
{
myColumnValue = g.Key,
myColumnItems = g.Select(r => r["OtherColumn"]).ToList()
});
Do you understand how/what this does though? Try it out and inspect the resulting IEnumerable. I'm not sure you have a perfect understanding on how GroupBy works but take your time with above example.
See this part:
new
{
myColumnValue = g.Key,
myColumnItems = g.Select(r => r["OtherColumn"]).ToList()
}
This creates an anonymous type which outputs the values of "OtherColumn" column into a list grouped by "myColumn" where value of "myColumn" is in the myColumnValue property.
I'm not sure this answers your question but it looks like this is what you want.
The variable g is of the type IGrouping<object, DataRow>, it's not DataRow. The IGrouping interface is designed to provide a list of DataRow's grouped by object values - it does not produce a flat list, if it did then it would just be a Sort, not GroupBy.
Just specify the field you want after your call to First() e.g.
.Select(grp => grp.FirstOrDefault()["MyFieldName"]);
This will take the first record from the grouping and select the specified field from that record.
Say you have columns AppleType, CreationDate and want to order each group of AppleType by CreationDate. Furthermore, you want to create a new column which explicitly ranks the order of the CreationDate per AppleType.
So, the resulting DataSet would have three columns, AppleType, CreationDate, OrderIntroduced.
Is there a LINQ way of doing this? Would I have to actually go through the data programmatically (but not via LINQ), create an array, convert that to a column and add to the DataSet? I have there is a LINQ way of doing this. Please use LINQ non-method syntax if possible.
So are the values actually appearing in the right order? If so, it's easy - but you do need to use method syntax, as the query expression syntax doesn't support the relevant overload:
var queryWithIndex = queryWithoutIndex.Select((x, index) => new
{
x.AppleType,
x.CreationDate,
OrderIntroduced = index + 1,
});
(That's assuming you want OrderIntroduced starting at 1.)
I don't know offhand how you'd then put that back into a DataSet - but do you really need it in a DataSet as opposed to in the strongly-typed sequence?
EDIT: Okay, the requirements are still unclear, but I think you want something like:
var query = dataSource.GroupBy(x => x.AppleType)
.SelectMany(g => g.OrderBy(x => x.CreationDate)
.Select((x, index ) => new {
x.AppleType,
x.CreationDate,
OrderIntroduced = index + 1 }));
Note: The GroupBy and SelectMany calls here can be put in query expression syntax, but I believe it would make it more messy in this case. It's worth being comfortable with both forms.
If you want a pure Linq to Entities/SQL solution you can do something like this:
Modified to handle duplicate CreationDate's
var query = from a in context.AppleGroup
orderby a.CreationDate
select new
{
AppleType = a.AppleType,
CreationDate = a.CreationDate,
OrderIntroduced = (from b in context.AppleGroup
where b.CreationDate < a.CreationDate
select b).Count() + 1
};
I have a datatable that I have grouped as follows:
var result = from data in view.AsEnumerable()
group data by new {Group = data.Field<string>("group_no")}
into grp
select new
{
Group = grp.Key.Group,
PRAS = grp.Average(c => Convert.ToDouble(c.Field<string>("pAKT Total")))
};
Now, the average function is also counting the empty cells in it's calculation. For example, there are 10 cells with only 5 populated with values. I want the average to be the sum of the 5 values divided by 5.
How can I ensure that it does what I want?
Thanks.
Maybe something like this:
PRAS = grp.Select(row => row.Field<string>("pAKT Total"))
.Where(s => !String.IsNullOrEmpty(s))
.Select(Convert.ToDouble)
.Average()
To my knowledge, that's not possible with the Average method.
You can however achieve the result you want to, with the following substitute:
PRAS = grp.Sum(c => Convert.ToDouble(c.Field<string>("pAKT Total"))) / grp.Count(c => !c.IsDBNull)
This only makes sense, when you want to select the "empty" rows in the group, but just don't want to include them in your average. If you don't need the "empty" rows at all, don't select them in the first place, i.e. add a where clause that excludes them.