LINQ: unable to use group by and sort - c#

I am new to LINQ and I need to write a query that should get the grouped records order by date. My table has columns: personId, monthAccepted, amountSent, processKeyId, dateProcessed
A personId can have multiple entries. My requirement is to get the first entry(dateProcessed) for every distinct personId order by processKeyId. This is what I have tried:
int pageNumber = 1;
int pageSize = 100;
var RecordsInQueue = from o in db.PersonTransaction
.OrderByDescending(o => o.processKeyId)
.GroupBy(g => g.personId)
select o;
return RecordsInQueue.ToPagedList(pageNumber, pageSize));
When running the above query I am getting the following error:
The method 'Skip' is only supported for sorted input in LINQ to
Entities. The method 'OrderBy' must be called before the method
'Skip'.
How can I select the correct records using LINQ?

My guess is that you need to do OrderBy on your RecordsInQueue.
Although you sorted the PersonTransaction list to produce the RecordsInQueue list, you didn't actually sort that list.
You can probably just do OrderByDescending(c => c.processKeyId).
The code would be:
int pageNumber = 1;
int pageSize = 100;
var RecordsInQueue = from o in db.PersonTransaction
.OrderByDescending(o => o.processKeyId)
.GroupBy(g => g.personId)
select o;
return RecordsInQueue.OrderByDescending(c => c.processKeyId).ToPagedList(pageNumber, pageSize));
This is just a guess as I don't know what your ToPagedList() method is doing. But it seems most likely that it is doing .Skip() to jump to the appropriate page, which it can't reliably do without knowing what order to use to skip.

you could try
var recordInQueue = db.PersonTransaction.OrderByDescending(x => x.processKeyId)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.GroupBy(g.PersonId)
.Select(r => new {r.Key, r.Value}).ToList();

Your query, which is not ordered, and returns unordered grouped objects. You need to order them before you call RecordsInQueue.ToPagedList.
Something like this:
var query =
from pt in db.PersonTransaction
group prod by pt.personId into grouping
select new
{
Id = grouping.Key,
TotalOfSomething = grouping.Sum(p => p.Something)
};
// unordered query
var ordered = query.OrderBy( pt => pt.TotalOfSomething );
//now you can skip and take
Please note that in your query the result will not contain processKeyId since it's not in the groupby expression.

Related

Filtering objects by nested list property value

I have a data grid with a column (Quantity) that is bound to a nested list int type property as follows:
data.PackageData.Contents.FirstOrDefault().orderedQuantity
When user apply a filter object (FilterValue) to that column I have to filter data using IQueryable query. What I have tried was something like this.
query = query.Where(e => e.PackageData.Contents.FirstOrDefault().orderedQuantity.Equals((Int16)FilterValue))
but when I trying to fetch data, I get an error.
data = query.ToList();
The LINQ expression 'DbSet().Where(p => (int)EF.Property<List>(EF.Property(p, "PackageData"), "Contents").AsQueryable().Select(o => (int?)o.orderedQuantity).FirstOrDefault().Equals((int)__p_0))' could not be translated.
How can I achieve this using LINQ?
Update
I tried with following LINQ. but still I get an error.
var filteredQty = (Int16)filter.FilterValue;
query = query.Where(e => e.PackageData.Contents.Any(i => i.orderedQuantity.Equals(filteredQty)));
Try to create external variable, as:
var filterValueInt = (short)FilterValue;
var query = query.Where(e => e.PackageData.Contents
.FirstOrDefault().orderedQuantity == filterValueInt);
EF try to translate to SQL. In the simplest cases, as example:
LINQ:
var myUsers = Users
.Where(user => user.Id >= 5)
.Select(user => user.Name)
.ToList();
SQL:
SELECT Name FROM Users WHERE Id >= 5;
(The result in SQL is more complex than that in reality but that's the idea)
When EF won't be able to translate to SQL, I think there's no choice left but to go through IEnumerable (to run LINQ in memory)
var list = query.ToList()
var filterValueInt = (short)FilterValue;
list = list.Where(e => e.PackageData.Contents
.FirstOrDefault().orderedQuantity == filterValueInt)`
.ToList()

Is it possible to assign single linq query result to more than one variable?

I can do this in TSQL
SELECT
#TotalDays = COUNT(Days),
#TotalHours = SUM(Hours)
FROM
Schedule
WHERE
GroupID = 1
How to achieve this in linq in single query, my current code;
var totalDays = 0;
var totalHours = 0;
totalDays = _schedule.Count(c => c.GroupID == 1);
totalHours = _schedule.Where(w => w.GroupID == 1).Sum(s => s.Hours);
This is not effective because it call 2 separate queries in the database
You could try something like this:
var result = _schedule.Where(s => s.GroupID == 1)
.GroupBy(x => x.GroupID)
.Select(gr => new
{
TotalDays = gr.Count(),
TotalHours = gr.Sum(s=>s.Hours);
});
Initially, you filter your data based on the GroupID. You pick those with GroupID equals to 1. Then you GroupBy them by their ID. This mihgt seams a bit silly, but this way you create a group of your data. So then you count just count the item in the group and calculate the sum you want. Last but not least after having made the GroupBy, you select an anonymous type with two properties, one for the TotalDays and one for the TotalHours.
Then you can consume the above result as below:
var totalDays = 0;
var totalHours = 0;
var first = result.FirstOrDefault();
if(first!=null)
{
totalDays = first.TotalDays,
totalHours = first.TotalHours
};
The problem, sometimes, trying to make a single LINQ query is that it actually gets translated into multiple database calls. Sometimes it is better to pull all of your raw data into memory in a single database call and then perform the calculations.
This will ensure only one database call:
var data = _schedule.Where(w => w.GroupID == 1).Select(w => w.Hours).ToArray();
var totalDays = data.Count();
var totalHours = data.Sum();
The key to making this work is the .ToArray() which forces the evaluation of the database query. If there are a lot of items this call can become inefficient, but in lot of cases it is still very fast.
You can use the next code
//one request to data base
var lstResult=_schedule.Where(w => w.GroupID == 1).ToArray();
//one loop in array for Count method
totalDays = lstResult.Count();
//One loop in array for Sum method
totalHours = lstResult.Sum(s => s.Hours);

Lambda Expression Select Min and Max at the same time

How do I convert this code to a Lambda expression when there are two columns to select at the same time?
LINQ
var lq=(from a in tbl
group a by 0 into b
select new { intYear = b.Min(p => p.intYear), tintMonth = b.Max(p => p.tintMonth) }
).SingleOrDefault();
T-SQL
SELECT MIN(intYear), MAX(tintMonth)
FROM tbl
Lambda Expression
tbl.Select(x => x.intYear).Min(); //Can't figure how to select if 2 columns
If you're intent on returning a "row" rather than two values, you can group all the rows together as you do in the first LINQ expression:
tbl.GroupBy(t => 1)
.Select(g => new { intYear = g.Min(p => p.intYear), tintMonth = g.Max(p => p.tintMonth) })
Note, I assume this is for LINQ-to-SQL. For plain old objects, this would most likely result in three iterations through the collection. One to do the grouping, one for the Min(), and one for Max(). For large collections, you would be be better off looping once and doing it the good-ol'-fashioned way with a single foreach over the collection.

c# only show numbers after decimal point with linq

I have a linq query that executes successfully, one of the columns returned is a decimal type that is used to represent prices in pounds and pence (there will never be any negative values)
I want to be able to strip out the pounds and pence into separate Properties of my projection, however when using functionality such as
var result= from j in context.Products
select
new{
Price = t.Price,
PricePounds = Math.Truncate(t.Price)
};
I get an error that Math.truncate is not supported as it cannot be translated into a store expression. How can I get the pounds value from this query?
If you don't need to do anything else in the database after that, the simplest approach is just to perform the truncation client-side:
var query = context.Products
.AsEnumerable() // Everything from here is LINQ to Objects
.Select(p => new {
p.Price,
PricePounds = Math.Truncate(p.Price)
});
Note that you might also want to just cast to int - and that might be supported in EF already.
EDIT: As noted in comments, you may want to perform a projection first, e.g.
var query = context.Products
.Select(p => new { p.Price, p.SomethingElse })
.AsEnumerable() // Everything from here is LINQ to Objects
.Select(p => new {
p.Price,
PricePounds = Math.Truncate(p.Price),
p.SomethingElse
});
(Where SomethingElse is another property you're interested in, as an example - I doubt that you only want the price.)
This will avoid the entire entity being fetched when you only want a few properties.
You may try:
var result= from j in context.Products
select
new {
Price = t.Price,
PricePounds = EntityFunctions.Truncate(t.Price, 0)
};
The case is Math.Truncate cannot be translated into SQL where EntityFunctions.Truncate should be.

How to cast a Linq Dynamic Query result as a custom class?

Normally, I do this:
var a = from p in db.Products
where p.ProductType == "Tee Shirt"
group p by p.ProductColor into g
select new Category {
PropertyType = g.Key,
Count = g.Count() }
But I have code like this:
var a = Products
.Where("ProductType == #0", "Tee Shirt")
.GroupBy("ProductColor", "it")
.Select("new ( Key, it.Count() as int )");
What syntax could I alter to produce identical results, i.e., how do I do a projection of Category from the second Linq statement?
I know in both that g and it are the same and represent the entire table record, and that I am pulling the entire record in just to do a count. I need to fix that too. Edit: Marcelo Cantos pointed out that Linq is smart enough to not pull unnecessary data. Thanks!
Why would you have to do it at all? Since you still have all of the information after the GroupBy call, you can easily do this:
var a = Products
.Where("ProductType == #0", "Tee Shirt")
.GroupBy("ProductColor", "it")
.Select(c => new Category {
PropertyType = g.Key, Count = g.Count()
});
The type of Products should still flow through and be accessible and the regular groupings/filtering shouldn't mutate the type that is flowing through the extension methods.

Categories

Resources