I am working on LINQ query where I need all the questions where each question may or may not have sub-question.
I am getting group by null/ exception issue as some parent question doesn't have child question. I am doing left join followed; group by parent question
(from question in Context.Questions.Where(question => question.ConsultationId == ConsultationId)
join questionHierarchy in Context.QuestionHierarchy on question.Id equals questionHierarchy.ParentQuestionId into qs
from childQuestion in qs.DefaultIfEmpty()
group childQuestion by question into g
select new
{
g.Key,
g
}).ToList();
found the answer
(from question in Context.Questions.Where(question => question.ConsultationId == ConsultationId)
join questionHierarchy in Context.QuestionHierarchy on question.Id equals questionHierarchy.ParentQuestionId into qs
from childQuestion in qs.DefaultIfEmpty()
group childQuestion by question into groupQuestions
select new
{
groupQuestions.Key,
childQuestions = groupQuestions.DefaultIfEmpty() == null? null : groupQuestions
}).ToList();
If you want something like "questions with their sub-questions" cnsider using GroupJoin? instead of an inner join followed by a GroupBy
In small steps:
IQueryable<Question> questions = dbContext.Questions.Where(...)
IQueryable<QuestinoHierarch> subQuestions = Context.QuestionHierarchy;
var questionsWithTheirSubQuestions = questions.GroupJoin( // GroupJoin the questions
subQuestions, // with the subQuestions
question => question.Id, // from every question take the Id
subQuestion => subQuestion.ParentQuestionId,
// from every subQuestion take the ParentQuestionId,
(question, subQuestions) => new // take the question with all matching subQuestions
{ // to make one new object:
// select only the question items you plan to use
Title = question.Title,
Priority = question.Priority,
...
// select only the subQuestions that you want (or all)
// for example only the answered sub questions:
SubQuestions = subQuestions
.Where(subQuestion.Type = QuestionType.Answered)
.Select(subQuestion => new
{
// again: select only the subQuestion properties you plan to use
Name = subQuestion.Name,
Date = subQuestion.Date,
...
})
.ToList(),
});
TODO: if desired, make one big statement.
Related
I am struggling with converting SQL to NHibernate HQL.
SQL Query
SELECT Posts.id, Count(Comments.id) FROM Posts LEFT JOIN Comments ON Posts.id=Comments.fk_post GROUP BY Posts.id
LINQ
Session.Query<Post>().Fetch(x => x.Comments).GroupBy(x => x.id, x => x.Comments)
.Select(x => new { PostId = x.Key, CommentCount = x.Single().Count }).ToList();
This is still failing with exception:
Parameter 'inputInfo' has type 'Remotion.Linq.Clauses.StreamedData.StreamedSingleValueInfo' when type 'Remotion.Linq.Clauses.StreamedData.StreamedSequenceInfo' was expected.
What is wrong with my query?
So you have tables of Posts and Comments. There is a one-to-many relation between Posts and Comments: every Post has zero or more Comments, every Comment belongs to exactly one Post, namely the Post that the foreign key Comments.fk_post refers to.
You want to fetche the Id of every Post, together with the number of Comments for this Post.
Whenever you need to select "items with their zero or more sub-items", like Schools with their Students, Customers with their Orders, or in your case Posts with their Comments, consider to use one of the overloads of Queryable.GroupJoin.
You can also see that a GroupJoin is the most obvious solution, if you see a SQL Left Outer Join followed by a GroupBy.
Whenever you see a SQL left outer join followed by a GroupBy, it is almost certain that you need a GroupJoin.
If you want something else than juse "items with their sub-items", use the overload that has a parameter resultSelector.
I don't know nHibernate, I assume that Session, Query, Fetch are used to get the IQueryables. As this is not part of the question, I leave it up to you to get the IQueryables:
IQueryable<Post> posts = ...
IQueryable<Comment> comments = ...
// GroupJoin Posts with Comments
var postIdsWithCommentsCount = posts.GroupJoin(comments,
post => post.Id, // from every Post take the primary key
comment => comment.fk_post, // from every Comment take the foreign key to Post
// parameter resultSelector: from every Post, with all its zero or more Comments,
// make one new
(post, commentsOfThisPost) => new
{
Id = post.Id,
Count = commentsOfThisPost.Count(),
});
Try this query:
var query =
from p in Session.Query<Post>()
from c in p.Comments.DefaultIfEmpty()
group c by p.Id into g
select new
{
PostId = g.Key,
CommentCount = g.Sum(x => (int?)c.Id == null ? 0 : 1)
}
var result = query.ToList();;
I want to change code below to be sql translateable because now i get exception.
Basicallly i want list of customers from certain localisation and there could be more than one customer with the same CustomerNumber so i want to take the one that was most recently added.
In other words - distinct list of customers from localisation where "distinct algorithm" works by taking the most recently added customer if there is conflict.
The code below works only if it is client side. I could move Group By and Select after ToListAsync but i want to avoid taking unnecessary data from database (there is include which includes list that is pretty big for every customer).
var someData = await DbContext.Set<Customer>()
.Where(o => o.Metadata.Localisation == localisation)
.Include(nameof(Customer.SomeLongList))
.GroupBy(x => x.CustomerNumber)
.Select(gr => gr.OrderByDescending(x => x.Metadata.DateAdded).FirstOrDefault())
.ToListAsync();
Short answer:
No way. GroupBy has limitation: after grouping only Key and Aggregation result can be selected. And you are trying to select SomeLongList and full entity Customer.
Best answer:
It can be done by the SQL and ROW_NUMBER Window function but without SomeLongList
Workaround:
It is because it is not effective
var groupingQuery =
from c in DbContext.Set<Customer>()
group c by new { c.CustomerNumber } into g
select new
{
g.Key.CustomerNumber,
DateAdded = g.Max(x => x.DateAdded)
};
var query =
from c in DbContext.Set<Customer>().Include(x => x.SomeLongList)
join g in groupingQuery on new { c.CustomerNumber, c.DateAdded } equals
new { g.CustomerNumber, g.DateAdded }
select c;
var result = await query.ToListAsync();
I have an MVC ViewModel that I'd like to pass through to a Razor view. In the controller, I've created a database context and joined tables together using Linq. Once summed and grouped, I'm getting an error:
Error CS1061 'decimal' does not contain a definition for 'GroupBy' and no accessible extension method 'GroupBy' accepting a first argument of type 'decimal' could be found (are you missing a using directive or an assembly reference?
I've gone through almost every example on stack overflow and google and couldn't find an example that matched the structure of my query. Also, the MS examples are very trivial and are not of much use.
Here is the action in the controller:
public IHttpActionResult GetEmployeeReleasedAllocatedBonus(int eid)
{
var employeeReleasedAllocatedBonus =
(from br in _context.BonusReleases
join emp in _context.Employees
on new
{
br.EmployeeID,
empID = br.EmployeeID
} equals new
{
emp.EmployeeID,
empID = eid
}
join job in _context.Jobs on br.JobID equals job.JobID
join bonus in _context.Bonus
on new
{
br.JobID,
empID = br.EmployeeID
}
equals new
{
bonus.JobID,
empID = bonus.EmployeeID
}
select new EmployeeAllocatedReleasedBonusViewModel()
{
AllocatedToEmployee = br.Amount, AllocatedPercentage = bonus.Amount * 100
,
JobNumber = job.JobNumber, JobDescription = job.JobDescription
})
.ToList()
.Sum(s => s.AllocatedToEmployee)
.GroupBy(g => new {g.JobNumber, g.JobDescription, g.AllocatedPercentage});
return Ok(employeeReleasedAllocatedBonus);
}
It's worth mentioning that the AllocatedPercentage datatype is a decimal. However, I've tried changing it to string but the error message stays.
Also tried using the group functionality right before .ToList() but that didn't work either.
After ToList() you have a List<EmployeeAllocatedReleasedBonusViewModel>.
In Sum(s => s.AllocatedToEmployee), every s is one EmployeeAllocatedReleasedBonusViewModel. Apparently a EmployeeAllocatedReleasedBonusViewModel has a property AllocatedToEmployee which is probably of type decimal. This can be summed into one decimal.
The result of the Sum (a decimal) is the input of your GroupBy. Does type decimal have a method GroupBy? Of course it doesn't!
Alas you forgot to tell us your requirements. It is difficult to extract them from code that doesn't do what you want.
It seems to me that you have two one-to-many relations:
Employees have zero or more BonusReleases. Every BonusRelease belongs to exactly one Employee using foreign key
Jobs have zero or more BonusReleases. Every BonusRelease belongs to exactly one Job.
Now what do you want: do you want all JobNumbers and JobDescriptions of all Jobs with the total of their AllocatedPercentage? I'm not sure what the Employees do within this query.
Whenever you want items with their sub-items, like Schools with their Students, Customers with their Orders, Orders with their OrderLines, use GroupJoin. If you want it the other way round: Student with the School that he attends, Order with the Customer who placed the Order, use Join.
var result = dbContext.Jobs.GroupJoin(dbContext.BonusReleases,
job => job.Id, // from every Job take the primary key
bonusRelease => bonusReleas.JobId, // from every BonusRelease take the foreign key
// parameter ResultSelector: take every Job with all its BonusReleases to make a new:
(job, bonusReleasesOfThisJob) => new
{
JobNumber = job.JobNumber,
JobDescription = job.JobDescription
// do you want the total of all allocated percentages?
TotalAllocatedPercentages = bonusReleasesOfThisJob
.Select(bonus => bonus.Amount)
.Sum(),
// do something to make it a percentage
// or do you want a sequence of allocated percentages?
TotalAllocatedPercentages = bonusReleasesOfThisJob
.Select(bonus => bonus.Amount)
.ToList(),
});
Or do you want the JobNumber / JobDescription / Total allocated bonus per Employee?
var result = dbContext.Employees.GroupJoin(dbContext.BonusReleases,
employee => employee.Id, // from every Employee take the primary key
bonus => bonus.EmployeeId, // from every BonusRelease take the foreign key
(employee, bonusesOfThisEmployee) => new
{
// Employee properties:
EmployeeId = employee.Id,
EmpoyeeName = employee.Name,
// for the jobs: Join the bonusesOfThisEmployee with the Jobs:
Jobs = dbContext.Jobs.GroupJoin(bonusOfThisEmployee,
job => job.Id,
bonusOfThisEmployee => bonusOfThisEmployee.JobId,
(job, bonusesOfThisJob) => new
{
Number = job.Id,
Description = job.Description,
TotalBonus = bonusOfThisJob.Select(bonus => bonus.Amount).Sum(),
}),
});
Harald's comment was key - after ToList(), I had a list of . Therefore I took a step back and said what if I put the results into an anonymous object first. Then do the group by and then the sum, putting the final result into the view model. It worked. Here is the answer.
var employeeReleasedAllocatedBonus =
(from br in _context.BonusReleases
join emp in _context.Employees
on new
{
br.EmployeeID,
empID = br.EmployeeID
} equals new
{
emp.EmployeeID,
empID = eid
}
join job in _context.Jobs on br.JobID equals job.JobID
join bonus in _context.Bonus
on new
{
br.JobID,
empID = br.EmployeeID
}
equals new
{
bonus.JobID,
empID = bonus.EmployeeID
}
select new
{
AllocatedToEmployee = br.Amount
,AllocatedPercentage = bonus.Amount * 100
,JobNumber = job.JobNumber
,JobDescription = job.JobDescription
})
.GroupBy(g => new {g.JobNumber, g.JobDescription, g.AllocatedPercentage})
.Select(t => new EmployeeAllocatedReleasedBonusViewModel
{
JobNumber = t.Key.JobNumber,
JobDescription = t.Key.JobDescription,
AllocatedPercentage = t.Key.AllocatedPercentage,
AllocatedToEmployee = t.Sum(ae => ae.AllocatedToEmployee)
});
I am working on LINQ query and part of objective to do SQL database call once to achieve result. I have number of questions which may have collection of answers.
I need to choose all the questions and collection of answer and if there no answer for specific question, I still need it.
for code gives me only question which have answer but not ones without answer
var t3 = (Context.Answers
.Include(answer => answer.AnswerStatusType)
.Where(answer => Context.Questions.Where(q => q.profileId == ProfileId)
.Any(t => t.Id == answer.QuestionId)))
.GroupBy(
x => x.QuestionId,
x => x,
(key, g) => new
{
Question = key,
Answers = g.ToList(),
}
).ToList();
You probably need something like this:
var t3 =
(
from q in Context.Questions
where q.profileId == ProfileId
join a in Context.Answers on q.Id equals a.QuestionId into gas
select new
{
Question = q,
Answers = gas.ToList(),
}
).ToList();
I had the exact same problem and solved it with this giant query, which could probably be optimized, but maybe it will help someone with a similar problem.
// Perform left join to get all the questions (even those that don't have answers)
var results = (from quiz in db.Quizzes
join question in db.Questions on quiz.QuestionID equals question.QuestionID
from answer in db.QuestionAnswers.Where(a => question.QuestionID == a.QuestionID).DefaultIfEmpty()
where quiz.QuizID == quizId
group (answer != null ? new AnswerVM()
{
AnswerId = answer.AnswerID,
AnswerText = answer.AnswerText,
Correct = question.CorrectAnswerID != null ? answer.AnswerID == question.CorrectAnswerID : false,
} : null) // For the questions with no answers
by new
{
question.QuestionID,
question.DisplayOrder,
question.QuestionText,
question.CorrectAnswerID
} into g
select new QuestionVM
{
QuestionId = g.Key.QuestionID,
QuestionText = g.Key.QuestionText,
CorrectAnswerId = g.Key.CorrectAnswerID,
Answers = g.ToList()
})
.AsEnumerable()
.Select(x => {
// If there are no answers then group by will return the list with one value (null),
// then we need to change the answers list to be null (not the list with one null value)
if (x.Answers.Count(a => a != null) <= 0)
{
x.Answers = null;
}
return x;
}).ToList();
I have two tables..
Student (StudentId,Name,FatherName)
Qualification (QualificationId,StudentId,DegreeName)
I have got data like this..
var myList = (from c in entities.Students
join q in entities.Qualifications on c.StudentId equals q.StudentId
select new {c.Name,c.FatherName,q.DegreeName}).ToList();
Now i want to filter myList more.. How can i do it, like..
var filteredList = myList.Select(c=> new Student
{
Name=c.Name,
FatherName=c.FatherName
//Degree=C.Degree
}).ToList();
The above Linq Query is not working if i want to get DegreeName also, My Question is how to further Filter myList.
Thanks.
var filteredList = myList.Where(i => i.FatherName == "Shahid").ToList();
Keep in mind since you called ToList() on the original query you are now filtering in memory. If you want to filter in the database then remove the ToList() on the first query and do it like this:
var myList = from c in entities.Students
join q in entities.Qualifications on c.StudentId equals q.StudentId
select new {
c.Name,
c.FatherName,
q.DegreeName
};
var filteredInDatabase = myList.Where(i => i.FatherName == "Shahid").ToList();