Performing a subquery in Entity Framework Core - entity-framework-core

I'm trying to convert some common queries that we use to an API call using EF Core but I can't seem to get it working.
The SQL that will typically be run will looking something like this:
select *
from my_table
where convert(date, datefield) = (select date
from another_table
where name == 'my_string')
My attempts at converting this into Linq have been fruitless. I've tried something like this and similar variants.
public async Task<my_table> GetStuff()
{
return await _context.my_table
.Where(m => _context.another_table
.Where(a => a.name == "my_string")
.Select(a => a.date).Equals(m.updatedate)).FirstAsync();
}
There error I get is:
System.InvalidOperationException: The LINQ expression 'DbSet<my_table>()
.Where(t => DbSet<another_table>()
.Where(t0 => t0.name == "my_string")
.Select(t0 => t0.date).Equals((object)t.updatedate))' could not be translated.
I've seen some posts about using Include or joining, but these tables are not related.
Any help would be appreciated. Thanks!

The first thing to mention is that even SQL allows the following
where convert(date, datefield) = (select date
from another_table
where name == 'my_string')
it will fail if the right subquery returns more than one result. Because in general subqueries (so is the LINQ "collections") return sequences, not single value.
So the safer and better approach would be to use either IN
where convert(date, datefield) in (select date
from another_table
where name == 'my_string')
or EXISTS
where exists (select * from another_table
where name == 'my_string' and date == convert(date, my_table.datefield))
Both these translate naturally to LINQ - IN to Contains
_context.my_table
.Where(m => _context.another_table
.Where(a => a.name == "my_string")
.Select(a => a.date)
.Contains(m.updatedate) // <--
)
and EXISTS to Any
_context.my_table
.Where(m => _context.another_table
.Any(a => a.name == "my_string" && a.date == m.updatedate) // <--
)

Related

Using string_agg in the many-to-many relation

I have entities like Product(Id, Name) and Keyword(Id, Description), and there is a many-to-many relationship between them.
The essence of my task is the following, I need to do a full-text search on Name and Description columns, using EF CORE 6
I already have some SQL code that works fine.
SELECT a."Id", a."Name" as name, k.txt
FROM "Products" AS a
LEFT JOIN (
SELECT x."ProductsId" as Id, string_agg(y."Description", ' ') as txt
FROM "ProductKeywords" x
JOIN "Keywords" y ON y."Id" = x."KeywordId"
GROUP BY 1
) k ON a."Id" = k.Id
WHERE to_tsvector(concat_ws(' ', a."Name", k.txt))
## to_tsquery('Some text');
And I need to write some LINQ code that will do something similar, but I have a problem with string_agg, and I don't understand how to implement it in LINQ and EF CORE will reflect it correctly
I tried to do the following
var products = _context.Products
.Select(e => new
{
Id = e.Id,
Name = e.Name,
Keywords = string.Join(" ", e.Keywords.Select(q => q.Description))
}).Where(e => EF.Functions.ToTsVector(e.Keywords).Matches("Some text")).ToList();
But I get an error, and it's most likely because of string.Join
could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'
Got the result, using linq2db
var query = _context.Products.ToLinqToDB()
.LeftJoin(_context.ProductsKeywords.ToLinqToDB().GroupBy(r => r.ProductId).Select(e => new {
Key = e.Key,
Txt = e.StringAggregate(",", t => t.Keyword.Description).ToValue()
}),
(product, productKeyword) => product.Id == productKeyword.Key,
(i, j) => new {
Id = i.Id,
Txt = j.Txt
}).Select(e => new {
Id = e.Id,
Txt = EF.Functions.ToTsVector(e.Txt)
}).Where(w => w.Txt.Matches("Some text"));

EF Core 5.0 where predicate is ignored when in stored in variable

after update to EF Core 5.0 I've notice long running query.
Maybe I found EF Core 5.0 bug, but maybe it's a breaking change I didn't found here https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/breaking-changes.
I have a function, that takes "where" predicate as a parameter, so this is something I cannot easily rewrite.
Code that works:
var reps = ctx.Reports.AsNoTracking()
.Include(r => r.ReportColumns)
.Where(r => r.Name == "ListOfCustomers")
.ToList();
and generates correct query (WHERE clause inluded):
SELECT [r].[ReportID], [r].[AdvancedFilterPurpose] -- ommited columns
FROM [report].[Reports] AS [r]
LEFT JOIN [report].[ReportColumns] AS [r0] ON [r].[ReportID] = [r0].[ReportID]
WHERE [r].[Name] = N'ListOfCustomers'
ORDER BY [r].[ReportID], [r0].[ReportColumnID]
Code that doesn't work:
Func<Db.Model.Report, bool> a = (r) => r.Name == "ListOfCustomers";
var reps = ctx.Reports.AsNoTracking()
.Include(r => r.ReportColumns)
.Where(a)
.ToList();
and generates query without WHERE clause
SELECT [r].[ReportID], [r].[AdvancedFilterPurpose] -- ommited columns
FROM [report].[Reports] AS [r]
LEFT JOIN [report].[ReportColumns] AS [r0] ON [r].[ReportID] = [r0].[ReportID]
ORDER BY [r].[ReportID], [r0].[ReportColumnID]
Thanks for any suggestion.
Replace type Func<Db.Model.Report, bool> by Expression<Func<Db.Model.Report,bool>>.
This won't work: Func<Db.Model.Report, bool> a = (r) => r.Name == "ListOfCustomers"
This will work: Expression<Func<Db.Model.Report, bool>> a = (r) => r.Name == "ListOfCustomers"

How to Convert SQL query consisting 'not in' into LINQ?

SELECT distinct [Taskid] FROM[Dbimg].[dbo].[table1] where Taskid not in (select Taskid from[Dbimg].[dbo].[table1] where ValidationFlag is null)
Please help to convert above sql query in linq c#.
You can try the below code
db.table1.Where(x => !db.table1.Where(y => y.ValidationFlag == null).Select(z => z.Taskid).Contains(x.TaskId))
.Select(x => x.TaskId).Distinct();
But in your case you can also modify the above LINQ as below as you are using the same table
db.table1.Where(y => y.ValidationFlag != null).Select(z => z.Taskid).Distinct();

ef core select records from tableA with matching fields in tableB

I'm trying to translate a query from raw sql to linq statement in EF Core 2
Query is something like this
SELECT * FROM TableA
WHERE FieldA = ConditionA
AND FieldB IN (SELECT FieldC FROM TableB WHERE FieldD = ConditionB)
FieldA and FieldB are from TableA, FieldC and FieldD are from TableB
I need all fields from TableA and none from TableB returned, so it should be something like
return Context.TableA
.Where(x => x.FieldA == ConditionA)
.[ some code here ]
.ToList()
my current solution is to get two distinct result sets and join them in code, something like this
var listA = Context.TableA
.Where(x => x.FieldA == ConditionA).ToList();
var listB = Context.TableB
.Where(x => x.FieldD == ConditionB).Select(x => x.FieldC).ToList();
listA.RemoveAll(x => !listB.Contains(x.FieldB);
return listA;
i hope it works, i still have to run tests on it, but i'm looking for a better solution (if anything exists)
You can simply use Contains function in query like this :
var ids = Context.TableB
.Where(p => p.FieldD == conditionD)
.Select(p => p.FieldC);
var result = Context.TableA
.Where(p => ids.Contains(p.FieldB))
.ToList();
It's a simple join that needs to be applied -
var result = (from a in Context.TableA.Where(x => x.FieldA == ConditionA )
join b in Context.TableB.Where(x => x.FieldD == ConditionB) on a.FieldB equals b.FieldC
select new {a}
).ToList();

Entity Framework LINQ Query match all members of child collection

I have a Site table that has a many-to-many relationship with a UtilityServiceConnection table using a linking table called LinkSiteUtilityServiceConnection. Given a set of ServiceConnectionIds, I need to locate the Site that is exclusively linked to all of them and no more. I think I should be able to write the query using All on the collection but it's not working as expected.
var serviceConnectionIds = new[] { 546892, 546911, 546923 };
var sites1 = db.Sites
.Where(x => x.LinkSiteUtilityServiceConnections.All(y => serviceConnectionIds.Contains(y.UtilityServiceConnectionId)))
.ToList();
Assert.AreEqual(1, sites1.Count); //fails
This produces the query below that returns ~250,000 records when I expect to get one.
SELECT [Extent1].*
FROM [dbo].[Site] AS [Extent1]
WHERE NOT EXISTS (SELECT 1 AS [C1]
FROM [dbo].[LinkSiteUtilityServiceConnection] AS [Extent2]
WHERE ([Extent1].[SiteId] = [Extent2].[SiteId])
AND ((NOT ([Extent2].[UtilityServiceConnectionId] IN (546892, 546911, 546923)))
OR (CASE
WHEN ([Extent2].[UtilityServiceConnectionId] IN (546892, 546911, 546923)) THEN cast(1 as bit)
WHEN (NOT ([Extent2].[UtilityServiceConnectionId] IN (546892, 546911, 546923))) THEN cast(0 as bit)
END IS NULL)))
Why isn't All working as I expect? What's the best way to write this query?
check this code:
query 1:
var sites1 = db.Sites
.Where(x => serviceConnectionIds.All(y =>
x.LinkSiteUtilityServiceConnections
.Select(u => u.UtilityServiceConnectionId).Contains(y)))
.ToList();
query 2:
var query = db.Posts.AsQueryable();
var sites1 = serviceConnectionIds.Aggregate(query,
(current, item) => current.Where(e => e.LinkSiteUtilityServiceConnections
.Any(c => c.UtilityServiceConnectionId== item))).ToList();