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.
Related
In a block of code I have a Foreach that I use to run through and count specific pieces that may or may not exist in the database. Basically for each part on an Order, I go on to count the Product Groups those belong to and then the Division those Product Groups belong to. For that I use this LINQ query:
foreach (var OrderDtl_yRow in ( from ThisOrderDtl in Db.OrderDtl
join ThisProdGrup in Db.ProdGrup on
ThisOrderDtl.ProdCode equals ThisProdGrup.ProdCode
where
ThisOrderDtl.Company == Session.CompanyID &&
ThisOrderDtl.OrderNum == 195792
select new
{
ProdCode = ThisOrderDtl.ProdCode,
Division = ThisProdGrup.Division_c,
OrderNum = ThisOrderDtl.OrderNum,
OrderLine = ThisOrderDtl.OrderLine
}))
{ ....counting things... }
Currently I've got message boxes set up to return the values to me as the process is going. I get everything to return correctly except the Division, that always shows up as blank in the MessageBoxes (So NULL I'd assume). So my Counters for Division don't Increment.
If I take that out into LINQPad I'm unsure how to return results of a foreach, but I tried it with
if(OrderDtl_yRow.Division != null && OrderDtl_yRow.Division != "")
{i++;}
i.Dump();
and got 5 (There were 5 rows I expected so I'm at least pulling our something). Then I converted it to a simpler FirstOrDefault statement to test a single value like
var OrderDtl_yRow = ( from ThisOrderDtl in OrderDtl
join ThisProdGrup in ProdGrup on
ThisOrderDtl.ProdCode equals ThisProdGrup.ProdCode
where
ThisOrderDtl.OrderNum == 195792 &&
ThisOrderDtl.OrderLine == 1
select new
{
ProdCode = ThisOrderDtl.ProdCode,
Division = ThisProdGrup.Division_c,
OrderNum = ThisOrderDtl.OrderNum,
OrderLine = ThisOrderDtl.OrderLine
}).FirstOrDefault();
Then if I do a OrderDtl_yRow.Dump() I get my result and sure enough, Division comes through. So all signs point to it being fine, yet I can't bring over the value where I actually need it to show up. Thoughts? Thanks!
P.S. For those familiar with Epicor ERP Division is a UD field, so it technically belongs to the table ProdGrup_UD, but in Epicor it recognized that as the table ProdGrup just fine, its only SQL that makes you join _UD to the parent table. I tried joining it anyways for funsies and it didn't like it because it knew the column was there already. So that should be fine.
UPDATE: Rookie Move, didn't upload the Division data into the testing environment, so nothing was there, then checked against Live data where it existed and scratched my head as to why it didn't match. But I learned something about LinqPad and Linq so it wasn't a useless exercise.
You need to play some more in Linqpad to see what is happening, Set the language to C# program, Press F4 and add references to Server\Bin\Epicor.System.dll and Server\Assemblies\Erp.Data.910100.dll and point the app.copnfig to your Server\web.config file. In the main block create yourself a Db context with var Db = new Erp.ErpContext();
Linqpad can display complex data structures so you needn't have done FirstOrDefault in your last example. for instance:
void Main()
{
var Db = new Erp.ErpContext();
var sessionCompany = "EPIC06";
var x = (from hed in Db.OrderHed
join dtl in Db.OrderDtl
on new { hed.Company, hed.OrderNum }
equals new { dtl.Company, dtl.OrderNum }
into dtlList
where
hed.Company == sessionCompany
select new { hed, dtlList })
.Dump();
}
Also note that in SQL dbo.ProdGrup is an autogenerated view that joins the tables Erp.ProdGrup and Erp.ProdGrup_UD for you.
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.
I know it's not something unusual to make such kind of queries but I think I get lost so I seek help. I have to tables with relation 1:N and to make it more clear I'll post a print screen from the management studio :
I am working on a asp.net mvc 3 project and I need to make a view where all Documents will be shown (and some filter and stuff, but I think that is irrelevant for this case). I need the data from the table Documents and only one specific record for each document from the DocumentFields table. This record is the record holding the name of the Document and it's uniqueness is DocumentID == Docmuents.Id, DocumentFields.RowNo == 1 and DocumentsFields.ColumnNo == 2. This is unique record for every Document and I need to get the FieldValue from this record which actually holds the Name of the Document.
I am not very sure how to build my query (maybe using JOIN) and I also would like to make my view strongly typed passing a model of type Documents but I'm not sure if it's possible, but I think depending on the way the query is build will determine the type of the model for the view.
I believe what you want is something like this:
var results =
from d in dbContext.Documents
join df in dbContext.DocumentFields
on new { d.Id, RowNo = 1, ColumnNo = 2 } equals
new { Id = df.DocumentId, df.RowNo, df.ColumnNo }
select new
{
Document = d,
DocumentName = df.FieldValue
};
Of course if you set up navigation properties, you can just do this:
var results =
from d in dbContext.Documents
let df = d.DocumentFields.First(x => x.RowNo == 1 && x.ColumnNo == 2)
select new
{
Document = d,
DocumentName = df.FieldValue
};
I've been looking at an example of LINQ from the following link; I've posted the code below the link.
Is it possible to modify this example so that the items returned in var contain all sub elements found in the items matching the doc.Descendants("person") filter? I basically want this XML query to act like a SQL select * so I don't have to explicitly specify field names like they've done with drink, moneySpent, and zipCode.
http://broadcast.oreilly.com/2010/10/understanding-c-simple-linq-to.html#example_1
static void QueryTheData(XDocument doc)
{
// Do a simple query and print the results to the console
var data = from item in doc.Descendants("person")
select new
{
drink = item.Element("favoriteDrink").Value,
moneySpent = item.Element("moneySpent").Value,
zipCode = item.Element("personalInfo").Element("zip").Value
};
foreach (var p in data)
Console.WriteLine(p.ToString());
}
The OP said he liked the answer posted, so I'll just resubmit it for science :)
var data = from item in doc.Descendants("person")
select item;
The only problem with this is that data is an IEnumerable<XElement>, and you'll have to query the fields by string names.
// Do a simple query and print the results to the console
var data = from item in doc.Descendants("person")
select item;
I am trying to figure out what I am doing wrong in the below LINQ statement. It doesn't like the third SELECT. It finds tblAddresse.tblAdminCounty in Intelisense when I am typing the query but when I type the SELECT after it it freaks.
Does it have to do with how tblAddress and tblAdminCounty are related? I would have thought that the fact it shows in Intellisense under tblAddress would make that statement self-evident but obviously not.
If I was to query just the CountyName in a seperate function it would look like this -->
var countyName = from adminCounty in context.tblAdminCounties
where adminCounty.CountyID == countyID
select adminCounty.CountyName;
And this is the larger 3-tiered approach based on this site --> HERE
var query = from tblBusinesse in context.tblBusinesses
where tblBusinesse.BusinessID == businessID
select new
{
tblBusinesse.BusinessName,
tblBusinesse.ContactName,
tblBusinesse.EmailAddress,
Address = from tblAddresse in tblBusinesse.tblAddresses
select new
{
tblAddresse.AddressLine1,
tblAddresse.AddressLine2,
tblAddresse.AddressLine3,
tblAddresse.CityName,
County = from adminCounty in tblAddresse.tblAdminCounty
select new
{
adminCounty.CountyName
}
}
};
You're trying to query it as if a single address has multiple counties. Doesn't the fact that it's called tblAdminCounty rather than tblAdminCounties suggest that it's just a single item?
Try changing this:
County = from adminCounty in tblAddresse.tblAdminCounty
select new
{
adminCounty.CountyName
}
to just:
County = tblAddresse.tblAdminCounty