I am really in a position where I can't think of answer regarding optional grouping in linq.
Basically,I want to generate report which comes from a screen having filters.
These filters(mostly grouped) are optional and can be rearranged.It's something like
Filters: 1.Clients Projects Tasks Duration
or
2.Projects Clients Tasks Duration
or
3.Task Duration etc.
with all possible combinations.
Then data should look like
1.ClientA
ProjectA
TaskA
26hrs 45mins
TaskB
43hrs 23mins
ProjectB
TaskX......
2.ProjectA
ClientA
TaskA
26hrs 45mins...
3.TaskA
26hrs 45mins
TaskB
6hrs 35mins
I have data.But unable to write logic which is generalized.
I am thinking with some enum which will hold filters (viewmodel)selected like
enum.Client,enum.Project... and
if (clientGroupChecked) then
foreach(var clientGroup in list){
//group list by client here
if(projectGroupChecked) then
foreach(var projectGroup in clientGroup){
//group list by project here
}
}
I know,it's wrong.This way I have to put logic for all the combinations possible.
Couldn't think of anything else.I want it really to be generalized because it may have more filters added in future and I don't want to change entire logic just for extra filters(Of course,I want to add new filter group somewhere in the logic.But I want it to be more easy to maintain also.
Edited:#sschimmel :My point is grouping can be shuffled(for this I have buttons[selected -->green and unselected-->gray and these buttons are movable for grouping].So when writing linq logic,how can I know on what criteria I have to group in particular way? For ex: I have columns A B C D E F.In this, I can just choose to group by A or by A B or B A or ACB....etc. with all possible combinations.How to achieve this?I don't want if else check because we have many possibilities.If one more filter is added,it would have many more possibilities. That's why I am thinking for need of general approach to do this.
Edit 2:
Please find attachment and how I am trying below.
//for the following ,I need some way of writing properly passing right values
var reportGroupingCP = (from t in TaskEntries
group t by new { clientId,projectId } into g
select new
{
ClientId = g.Key.clientId,
ProjectId = g.Key.projectId,
Result = (Type)g //What could be T
}).ToList();
var reportGroupingCE = (from t in TaskEntries
group t by new { clientId,employeeId } into g
select new
{
ClientId = g.Key.clientId,
EmployeeId = g.Key.employeeId,
Result = (Type)g //What could be T
}).ToList();
//Can't use above if there is filter only for client.similarly for other cases/I don't want to write for each one.I need way to do this dynamically.May be by passing enum or adding ids to some class or something else
Filter 1
Filter 2
If I understood your question correctly, you wan't to do group your data dynamically on multiple properties.
The easiest solution would be to use Dynamic LINQ which lets you create queries from strings you can easily compose from user inputs.
// userSelections is created dynamically from what the user selected.
var userSelections = new[] { "clientId", "projectId" };
var propertiesToGroupBy = string.Join(", ", userSelections);
var groupedData = data.GroupBy("new(" + propertiesToGroupBy + ")");
It's not type safe nor checked during compile time, but fairly easy to use and solves your problem. It's also documented nicely.
I tried to come up with a solution that dynamically combines Expression<Func<TaskEntry, object>>s but got stuck on creating the Expression that ìnstantiates the anonymous type you would use inside the GroupBy(new { ... }). Because the number of selected properties to group by is not known during compile time, it's not possible to create the anonymous type.
Related
I have a Questionnaire application that I am building for one of our teams and I have having an issue with making the report like they would like it (if at all possible). What I currently have is:
var completedSurveys = (from s in db.SurveyResponses
join d in db.Demographics on s.SurveyID equals d.SurveyID into grpjoin
from d in grpjoin.DefaultIfEmpty()
select new
{
Survey_ID = s.SurveyID,
Survey_Date = s.Survey.SurveyDate,
Question = s.Questions.QuestionTxt,
Response = s.Responses.ResponseTxt,
Zip_Code = d.ZipCode,
Department = d.CityDepartments.Department,
Ace_Score = s.Survey.AceScore,
}).ToList();
which after running through the gridview/excel code produces:
My Current Excel Sheet
and what they would like is an excel sheet that has the Questions as column headers along with SurveyID, Date, Zip, Department & Score with the responses to the questions as the row data that way everything shows only once. Something like this excel sheet I got from somewhere else:
Example
I've tried multiple different groupings for example:
var completedSurveys = (from s in db.SurveyResponses
join d in db.Demographics on s.SurveyID equals d.SurveyID into grpjoin
from d in grpjoin.DefaultIfEmpty()
group new {s.Questions.QuestionTxt, s.Survey.SurveyDate } by new { s.SurveyID, s.Responses.ResponseTxt, d.ZipCode, d.CityDepartments.Department, s.Survey.AceScore } into g
orderby g.Key.SurveyID ascending
select new
{
Survey_ID = g.Key.SurveyID,
Survey_Date = g.Select(s => s.SurveyDate),
Response = g.Key.ResponseTxt,
Zip = g.Key.ZipCode,
Department = g.Key.Department,
Ace_Score = g.Key.AceScore
}).ToList();
but I'm still not quite getting what I want. If I have to tell them that what I've already got is as good as it gets, then that's fine but I thought I would at least reach out for some advice. Any Assistance would be appreciated.
Thanks
You've got a few unknowns in your question, but if the crux of it is "how do you pivot data in c#", then I've got good news and bad news. The good news is that it's doable. The bad news is that it's not pretty.
First, you would want to group your "completedSurveys" by surveyId:
var groupedSurveys =
completedSurveys
.GroupBy(g => g.Survey_ID);
Then create a class of what you'd like your individual row to look like:
public class survey {
public int Survey_ID;
public DateTime date;
public int Zip_Code;
public int Ace_Score;
public string Q1;
public string Q2;
public string Q3;
}
Then, create a home for such objects:
var surveys = new List<survey>();
Then, loop through the groupings in the grouped surveys. We'll call this the outer loop.
In each outer loop:
create a new instance of "survey"
the common data to all items will exist in the first item, so you could just populate using it for this purpose. If nulls get in your way, then you'll have to do the appropriate logic inside the inner loop (described next)
loop through the individual items in your grouping (the inner loop)
in the inner loop, use a switch statement to map the item in the group to the appropriate field in the "survey" class using the expected question text.
when done with the inner loop, push the survey instance to the surveys home.
move on to the next outer loop iteration
Sample code for this is as follows:
foreach (var surveyItems in groupedSurveys) {
var firstItem = surveyItems.FirstOrDefault();
var survey = new survey() {
Survey_ID = firstItem.Survey_ID,
date = firstItem.Survey_Date,
Zip_Code = firstItem.Zip_Code,
Ace_Score = firstItem.Ace_Score
};
foreach(var item in surveyItems) {
switch (item.Question) {
case "Text for question 1":
survey.Q1 = item.Response;
break;
case "Text for question 2":
survey.Q2 = item.Response;
break;
case "Text for question 3":
survey.Q3 = item.Response;
break;
}
}
surveys.Add(survey);
}
You'll see that your surveys object now has pivoted data, with one "row" for each survey.
However, this is all without knowing about your excel writing code. It might be wiser to keep things "normalized" here and parse it over there. You can also consider pivoting at the server level. Pivot statements there are probably going to be a little friendlier.
Also, I don't want to cut into your logic to create "completedSurveys". But suffice it to say, if you cut it up, you might be able to make it look more elegant as a whole.
Thanks to Dave Bennett, I have a great Neo4j query that provides the results I need. I need to get this to work in Neo4JClient. Here's my working Neo4j query:
`// Composite Tile with related users
match (t:Tile{summary:"Test 1"})<-[:ASSIGNED_TO]-(u:User)
with {summary: t.summary,
id: t.id,
sprint_id: t.sprint_id,
size: t.size,
percent_done: t.percent_done,
color: t.color_id,
description: t.description,
queue_id: t.queue_id,
swimlane_id: t.swimlane_id,
icons: t.icons,
order: t.order,
assignees: collect(u)} as tile
RETURN collect(tile) as tiles`
Visual Studio and/or Neo4jClient is not very happy with my attempt:
var compositeTile = client.Cypher
.Match("(t:Tile)<-[:ASSIGNED_TO]-(u:User)")
.Where((Tile t)=> t.summary == tile.summary)
.With( {
summary: t.summary,
id: t.id,
sprint_id: t.sprint_id,
size: t.size,
percent_done: tile.percent_done,
color: t.color,
description: t.description,
queue_id: t.queue_id,
swimlane_id: t.swimlane_id,
icons: t.icons,
Order: t.order,
assignees: collect(u)
} as tile)
.return collect(tile) as tiles;
It just throws lots of red squiggly lines, I suspect because I'm not formatting the .with statement correctly. I haven't been able to find an example, so I'm asking here. How do I do this correctly in Neo4jClient?
So, two answers to your question - the first is that the .With statement takes a string as a parameter, so you just need to wrap your text with " (or #" if you want to keep the formatting). The second answer is more of a problem for you though:
It's not possible to do what you're trying to do with Neo4jClient - at the moment it doesn't allow you to create anonymous types. It's not able to deserialize the content. I thought it might be a quick fix, but it seems like it would be more involved. So, what we're looking at here is a change of the query.
I presume you have a class called Tile with the properties you're after. So I would change the query to something like this:
client.Cypher
.Match("(t:Tile)<-[:ASSIGNED_TO]-(u:User)")
.Where((Tile t) => t.summary == tile.summary)
.Return((t,u) => new {
Tile = t.As<Tile>(),
User = u.CollectAs<User>()
});
Which will give you a C# anonymous type with a Tile and an IEnumerable<Node<User>> elements, obviously you want the Users in your Tile, so you can then parse the result:
var tiles = new List<Tile>();
foreach (var result in results)
{
var tile = result.Tile;
foreach (var user in result.Users)
tile.users.Add(user.Data);
tiles.Add(tile);
}
One thing you might find is that you need to initialize the Users collection, so either add Users = new List<User>() to your Tile constructor, or just before parsing the data.
Hello I'm new to linq and lambda
I have two lists
fl.LocalOpenFiles ...
List<string> f....
there is a property (string) for example taking index 0
fl.LocalOpenFiles[0].Path
i wanted to select all from the first list fl.LocalOpenFiles where fl.LocalOpenFiles.Path starts with a string from the List<string> f
I finally got this...
List<LocalOpenFile> lof = new List<LocalOpenFile>();
lof = fl.LocalOpenFiles.Join(
folders,
first => first.Path,
second => second,
(first, second) => first)
.ToList();
But its just selecting folders that meet the requirement first.Path == second and i couldnt find a way to get the data that i want which is something meeting this "braindump" requirement:
f[<any>] == fl.LocalOpenFiles[<any>].Path.Substring(0, f[<any>].Length)
Another Example...
List<string> f = new List<string>{ "abc", "def" };
List<LocalOpenFile> lof = new List<LocalOpenFile>{
new LocalOpenFile("abc"),
new LocalOpenFile("abcc"),
new LocalOpenFile("abdd"),
new LocalOpenFile("defxsldf"),)}
// Result should be
// abc
// abcc
// defxsldf
I hope i explained it in a understandable way :)
Thank you for your help
Do you mean something like this :
List<LocalOpenFile> result =
lof.Where(file => f.Any(prefix => file.Path.StartsWith(prefix)))
.ToList();
You can use a regular where instead of a join, which will give you more straight forward control over the selection criteria;
var result =
from file in lof
from prefix in f
where file.Path.StartsWith(prefix)
select file.Path; // ...or just file if you want the LocalOpenFile objects
Note that a file matching multiple prefixes may show up more than once. If that is a problem, you can just add a call to Distinct to eliminate duplicates.
EDIT:
If you - as it seems in this case - only want to know the matching path and not the prefix it matches (ie you only want data from one collection as in this case), I'd go for #har07's Any solution instead.
I am going to ask a very basic question and probably a repeated one but I have a bit different situation.
I want to use "in" operator in Linq.
I have to get all the rows from table which has Id provided
by my array and returns the row if it has. How can I do it.
My array has
var aa="1091","1092","1093" and so on.
and my table uses these Ids as Primary keys
.I have to get all the rows whose Id is contained in the array and I do not want to use S.P.
You can use Enumerable.Contains,
var aa = new string[3] { "1091", "1092", "1093" };
var res = yourDataSource.Where(c => aa.Contains(c.ID));
IN statements are created by using Contains in your Where call. Assuming you use integers as IDs, you could write something like this:
var myArray=new[]{1091,1092,1094};
var myEntities=from entity in myTable
where myArray.Contains(entity.ID)
select entity;
I have a list of multiple string and I need to do operation on them by the suffixe they have. The only thing that is not changing is the beginning of the string (They will be always ManifestXXX.txt, FileNameItems1XXX...). The string end's with a suffix is different everytime. Here is what I have so far (Linq Pad):
var filesName = new[] { "ManifestSUFFIX.txt",
"FileNameItems1SUFFIX.txt",
"FileNameItems2SUFFIX.txt",
"FileNameItems3SUFFIX.txt",
"FileNameItems4SUFFIX.txt",
"ManifestWOOT.txt",
"FileNameItems1WOOT.txt",
"FileNameItems2WOOT.txt",
"FileNameItems3WOOT.txt",
"FileNameItems4WOOT.txt",
}.AsQueryable();
var query =
from n in filesName
group n by n.EndsWith("SUFFIX.txt") into ere
select new{ere} ;
query.Dump();
The condition in the GROUP is not good. I am thinking to try to get all possible suffixe with a nested SELECT in the group but I can't find a way to do it.
How can I have 3 differents group, grouping by their suffixe with Linq? Is it possible?
*Jimmy answer is great but still doesn't work the way desired. Any fix?
group by the suffix rather than whether it matches any particular one.
...
group by GetSuffix(n) into ere
...
string GetSuffix(string n) {
return Regex.Replace(n,"^Manifest|^FileNameItems[0-9]+", "");
}