Entity Framework LINQ Query match all members of child collection - entity-framework

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();

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"));

Performing a subquery in 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) // <--
)

EF Core can't translate an expression to compare two collections which EF 6 could

I have the following query in the old Entity Framework (.NET Framework):
db.ProductVariations
.Where(pv => pv.Product.Categories
.Any(cat => categorySearchStrings
.Any(categorySearchString => cat.SearchTree.StartsWith(categorySearchString))));
I realize this isn't pretty, but I'm refactoring a legacy app and we have to choose our battles.
So what happens is that you can pass a list of search string (the categorySearchStrings), e.g.:
"38.54.", "45."
This is basically an implementation of a search tree where each category in our database has a SearchTree property. So a category with search tree 38.54.99 would match, but 38. would not.
A product can have multiple categories and we can pass in multiple search tree strings to the query. So we're comparing two collections.
This gets translated to
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[ProductVariation] AS [Extent1]
WHERE EXISTS (SELECT
1 AS [C1]
FROM ( SELECT
[Extent3].[SearchTree] AS [SearchTree]
FROM [dbo].[ProductCategory] AS [Extent2]
INNER JOIN [dbo].[Category] AS [Extent3] ON [Extent2].[CategoryId] = [Extent3].[Id]
WHERE [Extent1].[ProductId] = [Extent2].[ProductId]
) AS [Project1]
WHERE EXISTS (SELECT
1 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
WHERE ( CAST(CHARINDEX(N'38.', [Project1].[SearchTree]) AS int)) = 1
)
)
) AS [GroupBy1]
I'm trying to migrate to Entity Framework Core (6, running on .NET 6) but this now gives me the following error:
System.InvalidOperationException : The LINQ expression 'categorySearchString => categorySearchString == "" || EntityShaperExpression:
Company.Data.Models.Category
ValueBufferExpression:
ProjectionBindingExpression: Inner
IsNullable: False
.SearchTree != null && categorySearchString != null && EntityShaperExpression:
Company.Data.Models.Category
ValueBufferExpression:
ProjectionBindingExpression: Inner
IsNullable: False
.SearchTree.StartsWith(categorySearchString)' 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'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Switching to client evaluation isn't really an option I believe, because there's too many data that will be retrieved. Plus, there's more going on than just this Where clause. I simplified it.
I also tried rewriting it as this:
.Where(pv => pv.Product.Categories.Select(c => c.SearchTree).Any(st => categorySearchStrings.Any(ss => st.StartsWith(ss))));
But I just get the same error.
Is it possible to do this with EF Core?
I'd be inclined to build a dynamic expression tree to represent the filter:
var cat = Expression.Parameter(typeof(Category), "cat");
var parts = new List<Expression>(categorySearchStrings.Count);
var startsWithMethod = typeof(string).GetMethod(nameof(string.StartsWith), new[] { typeof(string) });
foreach (string categorySearchString in categorySearchStrings)
{
var searchTree = Expression.Property(cat, nameof(Category.SearchTree));
var value = Expression.Constant(categorySearchString);
var startsWith = Expression.Call(searchTree, startsWithMethod, value);
parts.Add(startsWith);
}
var body = parts.Aggregate(Expression.OrElse);
var categoryFilter = Expression.Lambda<Func<Category, bool>>(body, cat);
var pv = Expression.Parameter(typeof(ProductVariation), "pv");
var product = Expression.Property(pv, nameof(ProductVariation.Product));
var categories = Expression.Property(product, nameof(Product.Categories));
var any = Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new[] { typeof(Category) }, categories, categoryFilter);
var finalFilter = Expression.Lambda<Func<ProductVariation, bool>>(any, pv);
db.ProductVariations
.Where(finalFilter)
...
You should also report this as an issue on the efcore repository, to see if it can be fixed in a future version.
Update: the issue was created but was a duplicate of an existing issue.

Take each first element of the group by

How can I take each first element in the group with EF 5 ?
var result = await context.SomeDbSet
.Where(...)
.GroupBy(x => new { x.SomeField, ... })
.Select(x => x.First())
.ToListAsync();
I am getting not supported exception.
How to correctly rewrite query? Thanks.
You cannot do that with grouping. SQL has a limitation - with GROUP BY you can select only grouping keys and aggregation result. This limitation for sure extended to LINQ to Entities - after GroupBy you can select only grouping keys and aggregation result.
Such result can be achieved by SQL and Window functions:
SELECT
r.*,
FROM
(
SELECT
s.*,
ROW_NUMBER() OVER(PARTITION BY s.SomeField1, s.SomeField2 ORDER BY s.SomeDate) AS RN
FROM SomeDbSet s
WHERE ...
) r
WHERE r.RN = 1
For those who want to stay with LINQ, I propose extension (disclaimer: I'm extension creator) linq2db.EntityFrameworkCore
And you can write query above via LINQ
var rnQuery =
from s in context.SomeDbSet
where ...
select new
{
Data = s,
RN = Sql.Ext.RowNumber().Over()
.PartitionBy(s.SomeField1, s.SomeField2)
.OrderBy(s.SomeDate)
.ToValue()
}
var resultQuery = await rnQuery
.Where(r => r.RN == 1)
.Select(r => r.Data)
.ToLinqToDB();
var result = resultQuery.ToList();
// async variant may need ToListAsyncLinqToDB() call
// because of collision in async extension methods between EF Core and linq2db
var result = await resultQuery.ToListAsyncLinqToDB();

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();