I have a list of items (linq to sql), which can be ordered by various properties, not just by Id asc/desc
If the list was ordered only by Id,
I could call list.Where(o => o.Id > 123).Take(10)
is there any linq methods/functionality that would allow to ?
Take 10 items after id with value = 123
SQL has no concept of row ordering so it you can't request after a particular row from SQL. However, LINQ to Objects does so you can use that:
If list is a List<T> created from your query, just use SkipWhile:
list.SkipWhile(o => o.Id != 123).Skip(1).Take(10)
If list is a LINQ to SQL Query, then first use AsEnumerable but realize this will bring over the entire query to the client:
list.AsEnumerable().SkipWhile(o => i.Id != 123).Skip(1).Take(10)
If you know the ordering and can test against it, you could filter and reduce the amount of data returned to the client by 1. Computing the maximum duplicates of the ordering for a given Id and 2. Only returning the records in the desired order that are greater than or equal to the sort value(s) for the desired Id:
var maxdup = list.GroupBy(o => o.someProp).Select(og => og.Count()).Max();
var ans = list.Orderby(o => o.someProp)
.Where(o => o.someProp >= list.Where(o2 => o2.Id == 123).First().someProp)
.Take(10+maxdup)
.AsEnumerable()
.SkipWhile(o => o.Id != 123).Skip(1)
.Take(10);
Related
Hello this is a LINQ Query but it doesn't sort properly because four different dates are involved.
var EventReportRemarks = (from i in _context.pm_main_repz
.Include(a => a.PM_Evt_Cat)
.Include(b => b.department)
.Include(c => c.employees)
.Include(d => d.provncs)
where i.department.DepartmentName == "Finance"
orderby i.English_seen_by_executive_on descending
orderby i.Brief_seen_by_executive_on descending
orderby i.French_seen_by_executive_on descending
orderby i.Russian_seen_by_executive_on descending
select i).ToList();
All i want is that it should somehow combine the four dates and sort them in group not one by one.
For Example, at the moment it sorts all English Reports based on the date that executive has seen it, then Brief Report and So on.
But i want that it should check which one is seen first and so on. For example if the first report which is seen is French, then Brief, then English then Russian, so it should sort it accordingly.
Is it Possible??
You need to have them all in one column. The approach I would do, assuming that the value of the respective cells is null, when you don't want them to show up in the order by:
var EventReportRemarks = (from i in _context.pm_main_repz
.Include(a => a.PM_Evt_Cat)
.Include(b => b.department)
.Include(c => c.employees)
.Include(d => d.provncs)
where i.department.DepartmentName == "Finance"
select new
{
Date =
(
i.English_seen_by_executive_on != null ? i.English_seen_by_executive_on :
i.Brief_seen_by_executive_on != null ? i.Brief_seen_by_executive_on :
i.French_seen_by_executive_on != null ? i.French_seen_by_executive_on :
i.Russian_seen_by_executive_on
)
}).ToList().OrderBy(a => a.Date);
In the select clause you could add more columns if you whish.
Reference taken from here.
Why not just use .Min() or .Max() on the dates and then .OrderBy() or .OrderByDescending() based on that?
Logic is creating a new Enumerable (here, an array) with the 4 dates for the current line, and calculate the Max/Min of the 4 dates: this results in getting the latest/earliest of the 4. Then order the records based on this value.
var EventReportRemarks = (from i in _context.pm_main_repz
.Include(a => a.PM_Evt_Cat)
.Include(b => b.department)
.Include(c => c.employees)
.Include(d => d.provncs)
where i.department.DepartmentName == "Finance"
select i)
.OrderBy(i => new[]{
i.English_seen_by_executive_on,
i.Brief_seen_by_executive_on,
i.French_seen_by_executive_on,
i.Russian_seen_by_executive_on
}.Max())
.ToList();
Your problem is not a problem if you use method syntax for your LINQ query instead of query syntax.
var EventReportRemarks = _context.pm_main_repz
.Where(rep => rep.Department.DepartmentName == "Finance")
.OrderByDescending(rep => rep.English_seen_by_executive_on)
.ThenByDescending(rep => rep.Brief_seen_by_executive_on)
.ThenByDescending(rep => rep.French_seen_by_executive_on descending)
.ThenByDescending(rep => resp.Russian_seen_by_executive_on descending)
.Select(rep => ...);
Optimization
One of the slower parts of a database query is the transport of selected data from the DBMS to your local process. Hence it is wise to limit the transported data to values you actually plan to use.
You transport way more data than you need to.
For example. Every pm_main_repz (my, you do love to use easy identifiers for your items, don't you?), every pm_main_repz has zero or more Employees. Every Employees belongs to exactly one pm_main_repz using a foreign key like pm_main_repzId.
If you use include to transport pm_main_repz 4 with his 1000 Employees every Employee will have a pm_main_repzId with value 4. You'll transport this value 1001 times, while 1 time would have been enough
Always use Select to select data from the database and Select only the properties you actually plan to use. Only use Include if you plan to update the fetched objects
Consider using a proper Select where you only select the items that you actually plan to use:
.Select(rep => new
{
// only Select the rep properties you actually plan to use:
Id = rep.Id,
Name = rep.Name,
...
Employees = rep.Employees.Select(employee => new
{
// again: select only the properties you plan to use
Id = employee.Id,
Name = employee.Name,
// not needed: foreign key to pm_main_repz
// pm_main_repzId = rep.pm_main_repzId,
})
.ToList(),
Department = new
{
Id = rep.Department,
...
}
// etc for pm_evt_cat and provencs
});
** Found the actual problem that was happening...due to records it wasn't obvious at first this was the case LINQ Query returns multiple copies of first result **
As the title says, the "pgnIDs.Contains(item.pgn)" part below is only selecting based on the first element of the hashset. I'd expect it to select a record if "item.pgn" is ANY of the values in pgnIDs.
HashSet<string> pgnIDs = new HashSet<string> { "EFFA", "EFFF", "FEE8", "FEE6", "FEF3", "FFF8", "FFFF" };
//...
var innerQ = (db1.can_raw_data_grainquality_2017
.Where(item => item.ref_id == q.ref_id &&
pgnIDs.Contains(item.pgn))
.OrderBy(item => item.ts_sec)
.ThenBy(item => item.ts_usec)
.Select(item => item)).ToList();
** update **
I tried with a straight "OR" for two of the values in the hashset and LINQ still only selected based on one of them. Is this behavior expected? Either one of the values below will work alone (in that records will be found).
var innerQ = (db1.can_raw_data_grainquality_2017
.Where(item => item.ref_id == 29225 &&
(item.pgn == "FFF8" || item.pgn == "EFFA"))
.OrderBy(item => item.ts_sec)
.ThenBy(item => item.ts_usec)
.Select(item => item.pgn)).ToList();
Per Ivan's comment (thanks!) I got the raw SQL query out and even tried it in SSMS. It did in fact work as expected in SSMS/raw SQL???
SELECT
[Extent1].[pgn] AS [pgn]
FROM (SELECT
[can_raw_data_grainquality_2017].[ts_sec] AS [ts_sec],
[can_raw_data_grainquality_2017].[ts_usec] AS [ts_usec],
[can_raw_data_grainquality_2017].[channel] AS [channel],
[can_raw_data_grainquality_2017].[mid] AS [mid],
[can_raw_data_grainquality_2017].[pgn] AS [pgn],
[can_raw_data_grainquality_2017].[sa] AS [sa],
[can_raw_data_grainquality_2017].[dlc] AS [dlc],
[can_raw_data_grainquality_2017].[d0] AS [d0],
[can_raw_data_grainquality_2017].[d1] AS [d1],
[can_raw_data_grainquality_2017].[d2] AS [d2],
[can_raw_data_grainquality_2017].[d3] AS [d3],
[can_raw_data_grainquality_2017].[d4] AS [d4],
[can_raw_data_grainquality_2017].[d5] AS [d5],
[can_raw_data_grainquality_2017].[d6] AS [d6],
[can_raw_data_grainquality_2017].[d7] AS [d7],
[can_raw_data_grainquality_2017].[ref_id] AS [ref_id]
FROM [dbo].[can_raw_data_grainquality_2017] AS [can_raw_data_grainquality_2017]) AS [Extent1]
WHERE (29225 = [Extent1].[ref_id]) AND ([Extent1].[pgn] IN (N'FFF8',N'EFFA'))
ORDER BY [Extent1].[ts_sec] ASC, [Extent1].[ts_usec]
I'm trying to find all customer codes where the customer has a status of "A" and whose code does not contain any letter using LINQ query.
var activeCustomers = Customers.Where(x => x.Status == "A" && x.Code.Any(n => !char.IsLetter(n))).Select(x => x.Code);
When I run this query in LinqPad I get the following error:
You'll need to do this as a two part query. First, you could get all the users who's status is "A":
var activeCustomers = Customers.Where(x => x.Status == "A").ToList();
After you've got those in-memory, you can create an additional filter for char.IsDigit:
var codes = activeCustomers.Where(x => x.Code.Any(n => !char.IsLetter(n)))
.Select(x => x.Code)
.ToArray();
As commenters have stated, IsLetter() cannot be translated to SQL. However, you could do the following, which will first retrieve all items with Status "A" from the database, then will apply your criteria after retrieval:
var activeCustomers = Customers.Where(x => x.Status == "A").AsEnumerable().Where(x => x.Code.Any(n => !char.IsLetter(n))).Select(x => x.Code);
You'll have to determine if it's acceptable (from a performance perspective) to retrieve all customers with "A" and then process.
The AsEnumerable() transitions your LINQ query to working not with IQueryable (which works with SQL) but with IEnumerable, which is used for plain LINQ to objects.
Since it is LINQ 2 SQL, there is no natural way to translate char.IsLetter to something SQL can understand. You can hydrate a query that retrieves your potential candidates and then apply an addition in-memory filter. This also solves the issue where LINQ 2 SQL has a preference for a string and you are dealing with chars
var activeCustomers = Customers.Where(x => x.Status == "A").ToList();
var filteredCustomers = activeCustomers.Where(x =>
x.Code.Any(n => !char.IsLetter(n))).Select(x => x.Code).ToList();
There are two performance hits here. First, you're retrieving all potential records, which isn't too desirable. Second, in your above code you were only interested in an enumerable collection of codes, which means our query is including far more data than we originally wanted.
You could tighten up the query by only returning back to columns necessary to apply your filtering:
var activeCustomers = Customers.Where(x => x.Status == "A")
Select(x => new Customer{ Status = x.Status, Code = x.Code }).ToList();
You still return more sets than you need, but your query includes fewer columns.
I have a LINQ query, which is working, as below. The only problem is that sometimes I get repeating MEMIds. How can I get only the first MemID from this query in a single database trip?
I am using SQL Server 2008 R2 as my backend database, and C# as the programming language.
var query = (from m in e.Memberships
where m.MEMID != null
&& (SqlFunctions.StringConvert((double)m.MEMID).Contains(memIdOrName)
|| m.NAME.Contains(memIdOrName))
select new {
m.MEMID,
NAME = m.NAME.TrimEnd(),
m.CITY,
m.STATE,
m.SYSTEMID,
SYSTEMNAME = m.SYSTEMNAME.TrimEnd()
})
.Distinct()
.OrderBy(s => s.NAME)
.ThenBy(s => s.CompanyID)
.ThenBy(s => s.CITY)
.ThenBy(s => s.MEMID);
var a = query.Skip(startRowIndex).Take(maximumRows).ToList();
Group on that value and then select out just one item from that group. If you don't care which, you can just grab the first. If you want a particular one, then you can re-order them before taking the first item.
So replace Distinct with;
//everything before `Distinct`
.GroupBy(s => s.MEMID)
.Select(group => group.FirstOrDefault())//or some other query to get one item in the group
//rest of your query
I have a database of documents in an array, each with an owner and a document type, and I'm trying to get a list of the 5 most common document types for a specific user.
var docTypes = _documentRepository.GetAll()
.Where(x => x.Owner.Id == LoggedInUser.Id)
.GroupBy(x => x.DocumentType.Id);
This returns all the documents belonging to a specific owner and grouped as I need them, I now need a way to extract the ids of the most common document types. I'm not too familiar with Linq to Sql, so any help would be great.
This would order the groups by count descending and then take the top 5 of them, you could adapt to another number or completely take out the Take() if its not needed in your case:
var mostCommon = docTypes.OrderByDescending( x => x.Count()).Take(5);
To just select the top document keys:
var mostCommonDocTypes = docTypes.OrderByDescending( x => x.Count())
.Select( x=> x.Key)
.Take(5);
You can also of course combine this with your original query by appending/chaining it, just separated for clarity in this answer.
Using the Select you can get the value from the Key of the Grouping (the Id) and then a count of each item in the grouping.
var docTypes = _documentRepository.GetAll()
.Where(x => x.Owner.Id == LoggedInUser.Id)
.GroupBy(x => x.DocumentType.Id)
.Select(groupingById=>
new
{
Id = groupingById.Key,
Count = groupingById.Count(),
})
.OrderByDescending(x => x.Count);