Method Chaining equivalent? - c#

This is working properly (from initial testing).
Since method chaining is my preferred format, I've tried to figure out what the method chaining equivalent is, but with no luck. Any ideas?
var data = (from p in db.Persons
from c in db.Companies
where c.CompanyName == companyName && p.CompanyId == c.CompanyId
select p)
.Select(p => new
{
Id = p.PersonId,
Name = string.Format("{0} {1}", p.FirstName, p.LastName)
});
Thanks,
--Ed

I would reorder the query a bit to filter out the companyName first, then perform the join. This would allow you to use this fluent syntax:
var query = db.Companies.Where(c => c.CompanyName == companyName)
.Join(db.Persons, c => c.CompanyId, p => p.CompanyId, (p, c) => p)
.Select(p => new
{
Id = p.PersonId,
Name = string.Format("{0} {1}", p.FirstName, p.LastName)
});
Having said that, some queries are a lot easier to write in query syntax, so why constrict yourself? Complicated joins are usually nicer in query syntax and you also get the benefit of using SelectMany join format with from ... from... instead of join p in ... on x equals y. See this question for more details: When to prefer joins expressed with SelectMany() over joins expressed with the join keyword in Linq.

Without changing the query, something like below, where oi is the opaque identifier:
var data = db.Persons.SelectMany(p => db.Companies, (p, c) => new {p,c})
.Where(oi => oi.c.CompanyName == companyName
&& oi.p.CompanyId == oi.c.CompanyId)
.Select(oi => oi.p)
.Select(p => new
{
Id = p.PersonId,
Name = string.Format("{0} {1}", p.FirstName, p.LastName)
});
However, you might also consider a few re-writes; maybe a join, or moving the company-name check earlier; and removing the double-select.

Non-toplevel from clauses translate to SelectMany calls:
db.Persons.SelectMany(p => db.Companies.Select(c => new { p, c }))
It is non-intuitive and a little hacky (although logical and reliable). I personally tend to use the query syntax for complex queries like this one because the SelectMany form is unreadable.

Related

EF Core 2.0 Group By other properties

I have 2 tables:
USERS
UserId
Name
Scores (collection of table Scores)
SCORES
UserId
CategoryId
Points
I need to show all the users and a SUM of their points, but also I need to show the name of the user. It can be filtered by CategoryId or not.
Context.Scores
.Where(p => p.CategoryId == categoryId) * OPTIONAL
.GroupBy(p => p.UserId)
.Select(p => new
{
UserId = p.Key,
Points = p.Sum(s => s.Points),
Name = p.Select(s => s.User.Name).FirstOrDefault()
}).OrderBy(p => p.Points).ToList();
The problem is that when I add the
Name = p.Select(s => s.User.Name).FirstOrDefault()
It takes so long. I don't know how to access the properties that are not inside the GroupBy or are a SUM. This example is very simple becaouse I don't have only the Name, but also other properties from User table.
How can I solve this?
It takes so long because the query is causing client evaluation. See Client evaluation performance issues and how to use Client evaluation logging to identify related issues.
If you are really on EF Core 2.0, there is nothing you can do than upgrading to v2.1 which contains improved LINQ GroupBy translation. Even with it the solution is not straight forward - the query still uses client evaluation. But it could be rewritten by separating the GroupBy part into subquery and joining it to the Users table to get the additional information needed.
Something like this:
var scores = db.Scores.AsQueryable();
// Optional
// scores = scores.Where(p => p.CategoryId == categoryId);
var points = scores
.GroupBy(s => s.UserId)
.Select(g => new
{
UserId = g.Key,
Points = g.Sum(s => s.Points),
});
var result = db.Users
.Join(points, u => u.UserId, p => p.UserId, (u, p) => new
{
u.UserId,
u.Name,
p.Points
})
.OrderBy(p => p.Points)
.ToList();
This still produces a warning
The LINQ expression 'orderby [p].Points asc' could not be translated and will be evaluated locally.
but at least the query is translated and executes as single SQL:
SELECT [t].[UserId], [t].[Points], [u].[UserId] AS [UserId0], [u].[Name]
FROM [Users] AS [u]
INNER JOIN (
SELECT [s].[UserId], SUM([s].[Points]) AS [Points]
FROM [Scores] AS [s]
GROUP BY [s].[UserId]
) AS [t] ON [u].[UserId] = [t].[UserId]

LinQ method syntax multiple tables with .where() and without joins

How do fetch data from multiple tables with method syntax without using joins, but only .where() methods?
I'm making a select against EF5 db context which maps to this legacy table structure where I have a persons detail table and another table which refers both to itself to create a hierarchy and to the person details table this way:
PersonSet
.Where(p => p.LastName.ToLower()=="surname")
.Join(SubsetSet, p => p.Id, ss => ss.SubsetLink, (p, ss) => new { PersonDetail = p, person = ss })
.Where(m => m.person.Frame=="a" && m.person.Purpose=="1")
.Join(SubsetSet, ss1 => ss1.person.Owner, person => person.SubsetLink, (ss1, ss2) => new { person = ss1, club = ss2 })
.Where(a => a.club.Frame=="b" && a.club.Purpose=="2")
.Join(SubsetSet, ss => ss.club.Owner, ss2 => ss2.SubsetLink, (ss, ss2) => new { club = ss, association = ss2 })
.Where(a => a.association.Frame=="b" && a.association.Purpose=="3")
.Join(SubsetSet, ss => ss.association.Owner, ss3 => ss3.SubsetLink, (ss, ss3) => new { association = ss, district = ss3})
.Where(d => d.district.Frame=="b" && d.district.Purpose=="4" && d.district.SubsetLink=="12345")
.Select(proj => new { proj.association.club.person, proj.association.club, proj.association, proj.district })
.OrderByDescending(a => a.association.club.person.phyperson.FirstName)
.Take(10).Dump();
The above query works at least in LinqPad but, it seems to me that If I could get rid of those joins the statement might look a bit nicer. Now I know, like in the Albahari example below, that this can be done with query syntax. But I couldn't find an example that would illustrate this situation with method syntax. The way I'm trying to approach this might of course be wrong and that's why I can't find suitable examples.
Here I found something similar, but couldn't make it work in LinQPad:
Is multiple .Where() statements in LINQ a performance issue?
Or this one, where the solution is again in query syntax:
Cross Join with Where clause
Or this example by Albahari: (http://www.linqpad.net/WhyLINQBeatsSQL.aspx)
from p in db.Purchases
where p.Customer.Address.State == "WA" || p.Customer == null
where p.PurchaseItems.Sum (pi => pi.SaleAmount) > 1000
select p
Consider this query:
var q = from order in orders
from orderline in order.Lines
where orderline.Count > 10
select order.Discount * orderline.Price;
this more or less corresponds to
var q = orders
.SelectMany(order => order.Lines, (order, orderline) => new { order, orderline})
.Where(item => item.orderline.Count > 10)
.Select(item => item.order.Discount * item.orderline.Price);
For more information on SelectMany, see the MSDN documentation.
If you don't have associations defined:
var q = from order in orders
from orderline in orderLines
where orderline.OrderId == order.Id
where orderline.Count > 10
select order.Discount * orderline.Price;
this more or less corresponds to
var q = orders
.SelectMany(order => orderLines, (order, orderline) => new { order, orderline})
.Where(item => item.orderline.OrderId == item.order.Id)
.Where(item => item.orderline.Count > 10)
.Select(item => item.order.Discount * item.orderline.Price);

How to convert Linq expression with multiple joins into method syntax OR retrieve select index?

I have an existing (working!) linq expression:
from ca in db.CustomAnswer
join ss in db.SurveySubmission on ca.SubmissionId equals ss.Id
join cq in db.CustomQuestion on ca.QuestionId equals cq.Id
where (ss.SurveyId == request.SurveyId)
orderby ss.Submitted, cq.SortOrder
select new
{
SubmissionId = ss.Id,
Answer = ca.Answer
}
I want to add the index of the select into the new object, e.g.
from ca in db.CustomAnswer
join ss in db.SurveySubmission on ca.SubmissionId equals ss.Id
join cq in db.CustomQuestion on ca.QuestionId equals cq.Id
where (ss.SurveyId == request.SurveyId)
orderby ss.Submitted, cq.SortOrder
select new
{
SubmissionId = ss.Id,
**Code = selectIndex,**
Answer = ca.Answer
}
To do this, I believe I need to first convert my query to method syntax so I can use the Select((q, index) => ...) form. To my simple mind, I think it should be:
db.SurveySubmission
.Where(ss => ss.SurveyId == request.SurveyId)
.OrderBy(ss => ss.Submitted)
.Join(db.CustomAnswer, ss => ss.Id, ca => ca.SubmissionId, (ss, ca) => new { ss, ca })
.Join(db.CustomQuestion, o => o.ca.QuestionId, cq => cq.Id, (o, cq) => new { o.ss, o.ca, cq })
.OrderBy(q => q.cq.SortOrder)
.Select((q, idx) => new {
SubmissionId = q.ss.Id,
Answer = q.ca.Answer,
Code = idx
});
However, when the expression is evaluated I get an error:
LINQ to Entities does not recognize the method
'System.Linq.IQueryable1[<>f__AnonymousTypef3[System.Guid,System.String,System.Int32]]
Select[<>f_AnonymousTypee3,<>f__AnonymousTypef3]
(System.Linq.IQueryable1[<>f__AnonymousTypee3[My.Data.Namespace.SurveySubmission,
My.Data.Namespace.CustomAnswer,My.Data.Namespace.CustomQuestion]],
System.Linq.Expressions.Expression1[System.Func3[<>f_AnonymousTypee3[My.Data.Namespace.SurveySubmission,
My.Data.Namespace.CustomAnswer,My.Data.Namespace.CustomQuestion],System.Int32,<>f__AnonymousTypef3[System.Guid,
System.String,System.Int32]]])'
method, and this method cannot be translated into a store expression.
I'm hoping this is glaringly obvious to someone? I've stared at it for several hours and the only conclusion I can make is that I'm not clever enough ... can anyone help please??
EF can't translate that into SQL, because in SQL sets are unordered; the idea of an index just doesn't make any sense to it.
Instead do everything but getting the index using an EF query, and then tack on the indexes in a linq to objects query:
var query = //your original query goes here
var finalQuery = query.AsEnumerable()
.Select((answer, index) => new
{
answer.SubmissionId,
answer.Answer,
Code = index,
});

Linq SelectMany query

I have the following query:
DateTime cutoffDate = new DateTime(1997, 1, 1);
var orders =
from c in customers
where c.Region == "WA"
from o in c.Orders
where o.OrderDate >= cutoffDate
select new { c.CustomerID, o.OrderID };
How can this be written in Linq Lambda?
BTW, is this known as a SelectMany query?
Also this can be done with a join, what is the pros and cons with doing it as shown above.
Yes, this is a SelectMany. You use SelectMany to 'flatten' a nested or tiered collection (in this case, the orders are nested under customers) into a simple single-tier collection.
customers.Where(c => c.Region == "WA")
.SelectMany(c => c.Orders)
.Where(o => o.Orderdate >= cutoffDate)
.Select(x => new { x.OrderID, x.Customer.CustomerID });
If the Orders are a property of the Customer then there is no need to use a join.

How to group using LINQ, then get value of largest group

Here's what I have so far:
var bestReason =
from p in successfulReasons
group p by p.Reason into g
select new { Excuse = g.Key, ExcuseCount = g.Count() };
What I need to do now is return one reason that is the best reason, determined by which were successful in the past.
Sample data:
ID,Reason
---------
0,Weather
1,Traffic
2,Illness
3,Weather
4,Traffic
5,Traffic
6,Pirates
should return "Traffic"
Would like to do it all in one LINQ statement, if possible.
Thanks.
EDIT: If there are 7 Pirate Attacks, and 7 Traffic Accidents, I'm ok with returning either one (the first alphabetically would be fine).
var bestReason = successfulReasons
.GroupBy(r => r.Reason)
.OrderByDescending(grp => grp.Count())
.First().Key;
If I understand your question correctly, you can do:
string bestReason =
(from p in successfulReasons
orderby p.Reason
group p by p.Reason into g
orderby g.Count() descending
select g.Key).FirstOrDefault();
var group = excuses.GroupBy(m => m.Reason)
.OrderByDescending(m => m.Count())
.Select(m => m.Key)
.FirstOrDefault();
Which produces the following sql statement:
SELECT TOP (1) [t1].[Reason]
FROM (
SELECT COUNT(*) AS [value], [t0].[Reason]
From [dbo].[Excuses] As [t0]
GROUP BY [t0].[Reason]
) As [t1]
ORDER BY [t1].[value] DESC
Since this is a moderately complicated IQueryable expression, you might consider compiling it to speed up the response time:
Func<ExcusesDataContext, string> commonResult = CompiledQuery.Compile(
(ExcusesDataContext c) => c.Excuses.GroupBy(m => m.Reason).OrderByDescending(m => m.Count()).Select(m => m.Key).FirstOrDefault()
);
Console.WriteLine(commonResult(new ExcusesDataContext()));
Console.ReadLine();
You could also just call the stored procedure via a repository and snag the particular value that you're looking for. This would be the fastest path to happiness, but the least fun to maintain:
string excuse = this.repo.Excuses.MostCommonFor(ProblemList.BeingLate);

Categories