EF Core 5.0 where predicate is ignored when in stored in variable - entity-framework

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"

Related

How to apply Where condition on Children Table in Entity Framework

I have following SQL Query , due to some limitation because there are many more conditions in this query I have to convert this query to LINQ.
SELECT
sh.BarCode
FROM
Bars AS sh
INNER JOIN BarDetail AS detail ON
detail.BarCode = sh.BarCode
AND
detail.IsActive = 1
INNER JOIN BarStatus AS st ON
st.BarCode = sh.BarCode
AND
st.IsActive = 1
So I have done this so far at LINQ
var queryAble = _context.BarDetail
.Include(x => x.Bar)
.Include(x => x.Bar)
.ThenInclude(y => y.BarStatus)
.Where(x => x.IsActive == true)
.AsQueryable();
I want to apply condition on barstatus as well; condition is barstatus with IsActive == true. I am unable to do it .
I don't want to do it as raw SQL with DbCommand, I'd like to do it entirely using only Linq-to-Entities.
How would I do this maybe this way but its not working
var queryAble = _context.BarDetail
.Include(x => x.Bar)
.Include(x => x.Bar)
.ThenInclude(y => y.BarStatus)
.Where(x => x.IsActive == true && x.Bar.BarStatus[SOMETHING HERE])
.AsQueryable();
This is direct translation of your SQL to LINQ. Note that Include intorduced not for building query but for loading related data.
var query =
from bar in _context.Bar
from detail in bar.Details
where detail.IsActive && bar.IsActive && bar.BarStatus.IsActive
select bar.BarCode;
As I remarked in my comment, while your original SQL query works, it's best for JOIN clauses to use only key (or tuple) equality, while other predicates should be in the WHERE clause. Following that pattern shouldn't cause any changes to your runtime query execution plan, but I feel it's keeping with the relational-calculus that SQL is based on - and it also means you can instantly check if a JOIN is correct or not because you'll always be using only primary-key and foreign-key columns (which are presumably already indexed... right?).
So your query becomes:
SELECT
b.BarCode
FROM
Bars AS b
INNER JOIN BarDetail AS d ON d.BarCode = b.BarCode
INNER JOIN BarStatus AS s ON s.BarCode = b.BarCode
WHERE
d.IsActive = 1
AND
s.IsActive = 1
...which is easier to translate into Linq-to-Entities:
Also:
You don't need the .AsQueryable() call: all non-materialized queries created from DbContext's DbSet<T> will be IQueryable<T> already.
As you have navigation-properties you don't need to do a manual Join.
IQueryable<String> q = _context.BarCode
// .Include( b => b.BarDetail )
// .Include( b => b.BarStatus )
.Where( b =>
b.BarDetail.IsActive == true
&&
b.BarStatus.IsActive == true
)
.Select( b => b.BarCode );
List<String> list = await q.ToListAsync( cancellationToken ).ConfigureAwait(false);
Update: Without b.BarDetail.IsActive == true
I can not directly add Include( b => b.BarStatus ) because BarStatus doesn't have direct relationship with BarCode, its linked with BarDetail. So first we go into BarDetail and then after that we go in BarStatus using BarDetail
You can still do a manual JOIN:
IQueryable<String> q = _context.BarCode
// .Include( b => b.BarDetail )
// .Include( b => b.BarStatus )
.Join( _context.BarStatus, s => s.BarCode, b => b.BarCode, ( s, b )
=> new { BarStatus = s, BarCode = b, BarDetail = b.BarDetail } )
.Where( t =>
t.BarDetail.IsActive == true
&&
t.BarStatus.IsActive == true
)
.Select( b => b.BarCode );
List<String> list = await q.ToListAsync( cancellationToken ).ConfigureAwait(false);

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 Update The LINQ expression 'x' could not be translated

I updated my .net core 2.2 to 5
I have a error about ef that
System.InvalidOperationException: 'The LINQ expression 'x' 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.'
public List<CustomerPageModel> GetCustomers(int AccountID)
{
return (from p in context.Customers
join f in context.Patients on p.ID equals f.CustomerID into ps
from t in ps.DefaultIfEmpty()
where p.AccountID == AccountID
select new CustomerPageModel
{
ID = p.ID,
Name = p.Name,
IsActive = p.IsActive,
TC = p.TC,
Surname = p.Surname,
Email = p.Email,
Address = p.Address,
Phone = p.Phone,
Note = p.Note,
AccountID = p.AccountID,
Pats = string.Join(",", ps.Select(x => x.Name)),
PatCount = ps.Count()
})
.GroupBy(p => p.ID)
.Select(g => g.First())
.ToList();
}
How can I convert that code?
Your problem line is:
Pats = string.Join(",", ps.Select(x => x.Name)),
Specifically, the string.Join method doesn't translate to SQL, so in previous versions of EF, it had to retrieve the data from the database then in-memory preform the string.Join function. Now EF explicitly tells you it can't run that on the database server - this is a breaking change but a design decision that tells you (the developer) that it may not have been running as efficiently as you thought it was...
To "fix" this, and based on your particular code example I'd recommend the following:
Add a pet names array property to your CustomerPageModel:
public string[] PetNames {get;set;}
And turn the Pets property into a readonly calculated string:
public string Pets { get => string.Join(",", PetNames); }
And change the problem line in your LINQ expression to:
PetNames = ps.Select(x => x.Name).ToArray()
I changed (lamb to subquery)
string.Join(",", ps.Select(x => x.Name))
to
string.Join(",", (from y in PatientList where y.CustomerID == p.ID select y.Name).ToArray()),
I made the group later (after tolist)
var test = mylist.GroupBy(p => p.ID)
.Select(g => g.First())
.ToList();
problem solved

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