Creating LINQ query with DISTINCT, OFFSET and FETCH NEXT - c#

I am trying to implement an SQL query using LINQ (and EF Core 2.2.2), but I am stuck.
The SQL query is:
SELECT DISTINCT([client].[Id]), [client].[Name]
FROM [Clients] AS [client]
LEFT JOIN [Documents] as [doc] ON [client].[Id] = [doc].[ClientId]
WHERE client.Name LIKE N'%test%' OR
doc.Name LIKE N'%test%' OR
ORDER BY client.Id DESC
OFFSET 10 ROWS
FETCH NEXT 10 ROWS ONLY
and I've started writing the query using LINQ:
from client in context.Set<Client>()
from doc in client.Documents.DefaultIfEmpty()
where client.Name.Contains("test")
or doc.Name.Contains("test")
group client by client.Id into c // this should act as 'distinct'
select c.First()
But now I am stuck with how to add ORDER BY, OFFSET and FETCH.
I tried to write something like this:
(
from client in context.Set<Client>()
from doc in client.Documents.DefaultIfEmpty()
where client.Name.Contains("test")
or doc.Name.Contains("test")
group client by client.Id into c // this should act as 'distinct'
select c.First()
)
.OrderByDescending(c => c.Id) // this is still working on IQueryable
.Skip(10)
.Take(10)
.ToListAsync();
And it returned the correct results, but when I looked at the SQL profiler, the query didn't include ordering and skipping at all (it was done in the application layer, on the entire set returned by the query).
Is there a way to do this using LINQ, or I need to use plain SQL in my codebase?

Distinct is not possible with query syntax, so just combine them:
var query =
from client in _clientService.Query()
from doc in client.Documents.DefaultIfEmpty()
where client.Name.Contains("test") || doc.Name.Contains("test")
select new { client.Id, client.Name };
var result = await query
.Distinct()
.OrderBy(x => x.Id)
.Skip(10)
.Take(10)
.ToListAsync();

Related

Query that matches multiple serverside (C#) values

I would like to write a LINQ query using EF Core that would get results from multiple matching columns in a SQL table based on an array of serverside data that I would provide for matching. SQL query should look/do something like this:
SELECT * FROM (VALUES ('a', 'one'), ('b', 'two'), ('c', 'three')) AS myserverdata (TransactionId, OrderId)
join ReportExecution re ON myserverdata.OrderId = re.OrderId AND myserverdata.TransactionId = re.TransactionId
Is this even possible?
I had few attempts where all end up crashing on generaing a SQL from expression:
using join
var query =
from execution in context.ReportExecutions
join candidate in insertCandidates // serverside array of data I'd like to match in SQL
on new {execution.OrderId, execution.TransactionId} equals new{candidate.OrderId, candidate.TransactionId}
select execution;
return query.ToListAsync();
using Contains and similarly with .Any (this would work for array of strings and then generate WHERE IN (...), but I can't pull it off for matching multiple columns.
var keys = candidates.Select(x => new { x.TransactionId, x.OrderId });
return context.ReportExecutions
.Where(rme => keys.Contains(new { rme.TransactionId, rme.OrderId } ))
.ToListAsync();
Thanks for the feedback.
EF Core supports only Contains with local collection, but there is workaround. You can use my function FilterByItems
and rewire query in the following way:
var query =
from execution in context.ReportExecutions
.FilterByItems(insertCandidates, (e, c) => e.OrderId == c.OrderId && e.TransactionId == c.TransactionId, true)
select execution;

How to use Contain with multiple values in C#, LINQ to pull record

I am working on .NET 6 application with entity framework core. I am creating record search query using LINQ where I am expecting to receive List of string. No of string values are not fixed and will varies. How I can use List in LINQ contain?
List<string> Roles = new List<string>() { "Business Analyst", "Business Analysis Lead", "Application Support Analyst" };
var records = (from jobProfile in db.JobProfiles
where jobProfile.Role.Contains(Roles)
select jobProfile).ToList();
Something like this:
var records = (
from jobProfile in db.JobProfiles
where jobProfile.Role.Any(r => Roles.Contains(r.Name))
select jobProfile
).ToList();
or with fluent interface:
var records =
db
.JobProfiles
.Where(jp => jp.Role.Any(r => Roles.Contains(r.Name)))
.ToList();
Roles can be any IEnumerable. EF will convert the method call to an IN clause.
Note that if the source (here db.JobProfiles) stopped being an IQueryable and was instead an IEnumerable, then you would be using an O(n) .Contains call. As long as it's EF you can use an IEnumerable for Roles since .Contains is not actually called in that case, but for LINQ to Objects you would want to make sure that it's a Set of some kind instead.
If Role is a string property rather than an entity, then it's a bit simpler:
var records = (
from jobProfile in db.JobProfiles
where Roles.Contains(jobProfile.Role)
select jobProfile
).ToList();
or with fluent interface:
var records =
db
.JobProfiles
.Where(jp => Roles.Contains(jp.Role))
.ToList();
You need to filter by where your Role-list contains the job profile role:
var records = (from jobProfile in db.JobProfiles
where Roles.Contains(jobProfile.Role)
select jobProfile).ToList();
...or in fluent:
var records = db.JobProfiles
.Where(x => Roles.Contains(x.Role))
.ToList();

Query always returns all records

I have this query
_context.LaserData
.OrderBy(x => x.F1).ThenBy(x => x.Kant)
.Where(x => x.Avdelning.StartsWith("AME"))
.Load();
result = _context.LaserData.Local;
The query will create the following SQL (I'm using _context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s); to get the SQL)
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[F1] AS [F1],
[Extent1].[Kant] AS [Kant],
[Extent1].[Avdelning] AS [Avdelning],
[Extent1].[F2] AS [F2],
[Extent1].[F3] AS [F3],
[Extent1].[F4] AS [F4],
[Extent1].[F5] AS [F5],
[Extent1].[F6] AS [F6],
[Extent1].[F7] AS [F7],
[Extent1].[F8] AS [F8],
[Extent1].[F9] AS [F9],
[Extent1].[F10] AS [F10],
[Extent1].[BC1] AS [BC1],
[Extent1].[BC2] AS [BC2],
[Extent1].[Template] AS [Template],
[Extent1].[P1] AS [P1],
[Extent1].[P2] AS [P2],
[Extent1].[P3] AS [P3],
[Extent1].[P4] AS [P4],
[Extent1].[P5] AS [P5],
[Extent1].[P6] AS [P6],
[Extent1].[FixtureId] AS [FixtureId],
[Extent1].[ExternTest] AS [ExternTest],
[Extent1].[EnableTO] AS [EnableTO],
[Extent1].[TOnr] AS [TOnr]
FROM [dbo].[LaserData] AS [Extent1]
WHERE [Extent1].[Avdelning] LIKE N'AME%'
ORDER BY [Extent1].[F1] ASC, [Extent1].[Kant] ASC
If I run the SQL query in Microsoft SQL Server Management Studio I get 4 records.
If I run the query in my program I get 1823 records, which is all the records in the table.
Any one that can see what I'm doing wrong?
EDIT: The query is used to populate an ObservableCollection that is bound to an DataGrid.
My guess is that your context already has the LaserData items tracked locally from a previous query. Instead, you should just query the database directly:
result = _context.LaserData
.OrderBy(x => x.F1).ThenBy(x => x.Kant)
.Where(x => x.Avdelning.StartsWith("AME"))
.ToList();

How to write an linq statement to get the last of a group of records

I have 2 SQL statements that basically do the same thing, that is, retrieve the last record from a table based on a datetime field for a group of records. I am using the data-first Entity Framework model. How would I write either of these SQL statements using LINQ Lambda functions?
ie,
var u = db.AccessCodeUsage.Where(...).GroupBy(...)
rather than
var u = from a in db.AccessCodeUsage
where ...
group by ...
SQL Statements:
SELECT *
FROM AccessCodeUsage a
WHERE NOT EXISTS (SELECT 1
FROM AccessCodeUsage
WHERE LocationId = a.LocationId
AND Timestamp > a.Timestamp)
SELECT a.*
FROM AccessCodeUsage a
WHERE a.Timestamp =
(SELECT MAX(Timestamp)
FROM AccessCodeUsage
WHERE a.LocationId = LocationId
AND a.AccessCode = AccessCode
GROUP By LocationId, AccessCode)
If you need to have the method-call form, but are finding it tricky to work out, then use the other syntax first:
from a in db.AccessCodeUsage
orderby a.TimeStamp descending
group a by a.LocationId into grp
from g in grp select g.First();
Then convert to method calls by taking each clause one at a time:
db.AccessCodeUsage
.OrderByDescending(a => a.TimeStamp)
.GroupBy(a => a.LocationId)
.Select(g => g.First());
From which I can workout the second without bothering to write out the linq-syntax form first:
db.AccessCodeUsage
.OrderByDescending(a => a.TimeStamp)
.GroupBy(a => new {a.LocationId, a.AccessCode})
.Select(g => g.First());
(Except it doesn't include what may be a bug, in that if timestamps aren't guaranteed unique, the SQL given in the question could include some extra inappropriate results).
I can't check on the SQL produced right now, but it should hopefully be equivalent in results (if not necessarily matching). There's cases where grouping doesn't translate to SQL well, but I certainly don't think this would be one.
I ended up using the following which corresponds to the first SQL statement.
// Retrieve only the latest (greatest value in timestamp field) record for each Access Code
var last = AccessCodeUsages.Where(u1 => !AccessCodeUsages
.Any(u2 => u2.LocationId == u1.LocationId &&
u2.AccessCode == u1.AccessCode &&
u2.Timestamp > u1.Timestamp));

Convert query from SQL to LINQ

I need to convert this SQL query to LINQ:
SELECT COUNT(result1.Id) AS total,
result1.OccurrenceDate,
result1.Path,
result1.Message,
result1.StackTrace
FROM (SELECT * FROM tbllog ORDER BY OccurrenceDate DESC) AS result1
GROUP BY result1.Path, result1.Message ORDER BY total DESC
But I'm not getting success. I've tried in so many ways, nothing works.
Some help?
Create a linq query out of the following:
SELECT * FROM tbllog ORDER BY OccurrenceDate DESC AS result1
GROUP BY result1.Path, result1.Message ORDER BY total DESC
Once that is done, write some linq to get your counts.
var results = _dbContext.tbllog
.GroupBy( t => new { t.Path, t.Message })
.Select( g => new
{
Total = g.Count(),
Path = g.Key.Path,
Message = g.Key.Message
}).OrderBy(p => p.Total);
It is worth to mention that your sql query is invalid because the properties OccurrenceDate and StackTrace is not in the GROUP BY clause nor with in an aggregate function, therefore it is invalid query. The same with the LINQ query. You should determine what you want to do with them. Either include them in the group by or use an aggregate function to select the appropriate value for each group.
I don't know what the context is for your question, but if you don't need to modify the query such that it executes with additional parameters, I would highly recommend executing the query as SQL. This is supported on all the ORMs that I am aware of (Entity Framework, NHiberate, LINQ to SQL, etc).
Erick

Categories

Resources