LINQ to Entities group by and Count() - c#

I have the following LINQ-to-Entities query
from r in ctx.Rs
join p in ctx.Ps on r.RK equals p.RK
group r by r.QK into gr
select new { QK = (int)gr.Key, Num = gr.Count() }
that runs against this schema
Table P Table R Table Q
PK*
RK ----> RK*
Text QK ------> QK*
Text Text
and gives this message if there is any record in Q with no corresponding record in P: "The cast to value type 'Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type."
The problem is the gr.Count() in the last line, but I cannot find a solution. I have tried to test gr for null, but cannot find a way that works.
I have seen a number of solutions to a similar problem using Sum() instead of Count(), but I have not been able to adapt them to my problem.
I tried changing my query to look like the one in Group and Count in Linq issue, but I just got a different message.
I also looked at Group and Count in Entity Framework (and a number of others) but the problem is different.

group Key can't be null
var results = ctx.Rs.Where(r => r.QK != null)
.GroupBy(r => r.QK)
.Select(gr => new { Key = (int)gr.Key, Count = gr.Count() }
.ToList();
PS.
Mostly, You don't need 'JOIN' syntax in Entity Framework. see: Loading Related Entities
Writing descriptive-meaningful variable names would significantly improve Your codes and make it understandable. Readability does matter in real world production.

I'm having trouble reading your format. But can you try:
from r in ctx.Rs
join p in ctx.Ps.DefaultIfEmpty() on r.RK equals p.RK
group r by r.QK into gr
select new { QK = (int)gr.Key, Num = gr.Count(x => x.RK != null) }
With DefaultIfEmpty and x => x.RK != null being the changes.

Related

C# Linq expressions can't loop through data results?

I am trying to loop through the IQueryable results data but I get an error at the loop?
var pivot = from f in query
group f by new
{
Account = f.Account
}
into g
select new
{
Account = g.Key.Account,
Com = g.Where(d => d.Party == "Com").Sum(d => d.Amount),
};
foreach (var item in pivot)
{
Console.WriteLine($"\t {item.Account} {item.Com}");
}
I just want to see what is my data after I manipulate it.
The error message I get is:
System.InvalidOperationException
"Processing of the LINQ expression
'AsQueryable(Where(\r\n source:
NavigationTreeExpression\r\n Value:
default(IGrouping<<>f__AnonymousType1, StepTwo>)\r\n
Expression: (Unhandled parameter: e6), \r\n predicate: (d) =>
d.Party == \"Com\"))' by 'NavigationExpandingExpressionVisitor'
failed. This may indicate either a bug or a limitation in EF Core. See
https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed
information."
Below is the query used to create query
var query = from inn in db.InputTE.Take(getRecord)
join y in db.InputYEM on inn.YPerform equals y.YPerform
select new StageTwo
{
Party = inn.Party,
Account = y.Account,
Amount = inn.Amount
};
The error message is, essentially, saying that Linq to Entities isn't able adequately translate your expression into SQL. It's going to have to load the entire data set into memory to process. The link in the error message goes into great detail about the problem--it's worth reading.
This error is new in EF Core 3.0. Previously, EF would quietly proceed loading the data set into memory, which often lead to devs writing inefficient queries without realizing it.
Try simplifying the query by moving the where clause before the group by in your query.
This expression might not be exactly what you need, but I think it's close.
from f in query
where f.Party == "Com"
group by f.Account into g
select new { Account = g.Key, Com = g.Sum(d => d.Amount) }

Linq join on two values

Suppose I have a list of {City, State}. It originally came from the database, and I have LocationID, but by now I loaded it into memory. Suppose I also have a table of fast food restaurants that has City and State as part of the record. I need to get a list of establishments that match city and state.
NOTE: I try to describe a simplified scenario; my business domain is completely different.
I came up with the following LINQ solution:
var establishments = from r in restaurants
from l in locations
where l.LocationId == id &&
l.City == r.City &&
l.State == r.State
select r
and I feel there must be something better. For starters, I already have City/State in memory - so to go back to the database only to have a join seems very inefficient. I am looking for some way to say {r.City, r.State} match Any(MyList) where MyList is my collection of City/State.
UPDATE
I tried to update based on suggestion below:
List<CityState> myCityStates = ...;
var establishments =
from r in restaurants
join l in myCityStates
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
and I got the following compile error:
Error CS1941 The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.
UPDATE 2
Compiler didn't like anonymous class in the join. I made it explicit and it stopped complaining. I'll see if it actually works in the morning...
It seems to me that you need this:
var establishments =
from r in restaurants
join l in locations.Where(x => x.LocationId == id)
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
Well, there isn't a lot more that you can do, as long as you rely on a table lookup, the only thing you can do to speed up things is to put an index on City and State.
The linq statement has to translate into a valid SQL Statement, where "Any" would translate to something like :
SELECT * FROM Restaurants where City in ('...all cities')
I dont know if other ORM's give better performance for these types of scenarios that EF, but it might be worth investigating. EF has never had a rumor for being fast on reads.
Edit: You can also do this:
List<string> names = new List { "John", "Max", "Pete" };
bool has = customers.Any(cus => names.Contains(cus.FirstName));
this will produce the necessary IN('value1', 'value2' ...) functionality that you were looking for

Error in Linq, Count()

I'm trying write this sql query to Linq:
SQL:
select c.course_name, count(s.s_name) as studenti from course c
join study_group g on g.course_id=c.id
join student s on s.study_group_id=g.id
group by c.course_name;
Linq:
var countStudents = (from s in ado.student //on g.id equals s.study_group_id
join g in ado.study_group on s.study_group_id equals g.id
join c in ado.course on g.course_id equals c.id
group s by c.course_name into cn
let count = cn.Count(co => co.s_name)
select new
{
c.course_name
course_name = cn.Key
});
and still I have an error at co => co.s_name
Error: Cannot implicitly convert type 'string' to 'bool'
Know anybody how to fix this ?
Thank you.
The SQL COUNT(column) aggregate function only counts not null values. The equivalent in LINQ would be to replace the line:
let count = cn.Count(co => co.s_name)
by
let count = cn.Count(co => co.s_name != null)
Of course, no guarantees on the generated SQL here. Either way, it is strange that a student's name may be null, though I have no intention of discussing your model.
Note
You won't be able to retrieve the desired count unless you select it in your code. Also, do check if students' names can be null, because in case they can't, just a cn.Count() would suffice.
Why do you need the Count clause in your LINQ statement? Just use a .Count() extension method on the IEnumerable that's returned from your query to get the count.
var count = countStudents.Count();

Linq to SQL - Query

I am trying to mimic below statement in Linq to SQL.
WHERE (rtrim(posid) like '%101' or rtrim(posid) like '%532')
I statement basically determine if posid ends with 101 or 532. In the above example I am only making 2 comparisons but their could be 1 to N comparisons all joined with OR. I store the comparison values (101,532,...) in a generic list that I send to my Linq to SQL method.
I have tried to mimic above SQL using a where clause unsuccessfully (example below):
var PosNum = new List<string>();
PosNum.Add("101");
PosNum.Add("532");
var q = (from a in context.tbl_sspos select a);
q = q.Where(p => PosNum.Contains(p.posid.Trim()));
The issue with the above where clause is that it tries to do an exact match rather I want an ends with comparison.
How would I mimic the SQL statement in Linq to SQL.
Thank You in advance for any help / advice you can provide.
I would use String.EndsWith();
This will check the end of the string rather than entire contents of it.
var q = (from a in context.tbl_sspos select a);
q = q.Where(p => p.posid.EndsWith("102") || p.posid.EndsWith("532"));
In EF 4 you can use the StartsWith / EndsWith methods by now. Might also work in LINQ to SQL.
UPDATE
Just realized that you are trying todo this against multiple values (PosNum), I don't think that this is directly supported currently. You can however concatenate multiple Where()clauses to get the result.
UPDATE 2
As AdamKing pointed out concatenating the where clauses was filtering against all PosNum values, here is the corrected version:
var baseQuery = (from a in context.tbl_sspos select a);
IEnumerable<YourType> q = null;
foreach(var pos in PosNum)
{
if(q == null)
q = baseQuery.Where(a => a.posid.EndsWith(pos));
else
q = q.Union(baseQuery.Where(a => a.posid.EndsWith(pos)));
}
This is not as pretty anymore, but works nonetheless.

Linq Union: How to add a literal value to the query?

I need to add a literal value to a query. My attempt
var aa = new List<long>();
aa.Add(0);
var a = Products.Select(p => p.sku).Distinct().Union(aa);
a.ToList().Dump(); // LinqPad's way of showing the values
In the above example, I get an error:
"Local sequence cannot be used in LINQ to SQL implementation
of query operators except the Contains() operator."
If I am using Entity Framework 4 for example, what could I add to the Union statement to always include the "seed" ID?
I am trying to produce SQL code like the following:
select distinct ID
from product
union
select 0 as ID
So later I can join the list to itself so I can find all values where the next highest value is not present (finding the lowest available ID in the set).
Edit: Original Linq Query to find lowest available ID
var skuQuery = Context.Products
.Where(p => p.sku > skuSeedStart &&
p.sku < skuSeedEnd)
.Select(p => p.sku).Distinct();
var lowestSkuAvailableList =
(from p1 in skuQuery
from p2 in skuQuery.Where(a => a == p1 + 1).DefaultIfEmpty()
where p2 == 0 // zero is default for long where it would be null
select p1).ToList();
var Answer = (lowestSkuAvailableList.Count == 0
? skuSeedStart :
lowestSkuAvailableList.Min()) + 1;
This code creates two SKU sets offset by one, then selects the SKU where the next highest doesn't exist. Afterward, it selects the minimum of that (lowest SKU where next highest is available).
For this to work, the seed must be in the set joined together.
Your problem is that your query is being turned entirely into a LINQ-to-SQL query, when what you need is a LINQ-to-SQL query with local manipulation on top of it.
The solution is to tell the compiler that you want to use LINQ-to-Objects after processing the query (in other words, change the extension method resolution to look at IEnumerable<T>, not IQueryable<T>). The easiest way to do this is to tack AsEnumerable() onto the end of your query, like so:
var aa = new List<long>();
aa.Add(0);
var a = Products.Select(p => p.sku).Distinct().AsEnumerable().Union(aa);
a.ToList().Dump(); // LinqPad's way of showing the values
Up front: not answering exactly the question you asked, but solving your problem in a different way.
How about this:
var a = Products.Select(p => p.sku).Distinct().ToList();
a.Add(0);
a.Dump(); // LinqPad's way of showing the values
You should create database table for storing constant values and pass query from this table to Union operator.
For example, let's imagine table "Defaults" with fields "Name" and "Value" with only one record ("SKU", 0).
Then you can rewrite your expression like this:
var zero = context.Defaults.Where(_=>_.Name == "SKU").Select(_=>_.Value);
var result = context.Products.Select(p => p.sku).Distinct().Union(zero).ToList();

Categories