score:3
Most likely that LINQ query couldn't be translated in EF Core 2.2 either, because of some limitations that the GroupBy
operator has.
From the docs:
Since no database structure can represent an IGrouping, GroupBy operators have no translation in most cases. When an aggregate operator is applied to each group, which returns a scalar, it can be translated to SQL GROUP BY in relational databases. The SQL GROUP BY is restrictive too. It requires you to group only by scalar values. The projection can only contain grouping key columns or any aggregate applied over a column.
What happened in EF Core 2.x is that whenever it couldn't translate an expression, it would automatically switch to client evaluation and give just a warning.
This is listed as the breaking change with highest impact when migrating to EF Core >= 3.x :
Old behavior
Before 3.0, when EF Core couldn't convert an expression that was part of a query to either SQL or a parameter, it automatically evaluated the expression on the client. By default, client evaluation of potentially expensive expressions only triggered a warning.
New behavior
Starting with 3.0, EF Core only allows expressions in the top-level projection (the last Select() call in the query) to be evaluated on the client. When expressions in any other part of the query can't be converted to either SQL or a parameter, an exception is thrown.
So if the performance of that expression was good enough when using EF Core 2.x, it will be as good as before if you decide to explicitly switch to client evaluation when using EF Core 5.x. That's because both are client evaluated, before and now, with the only difference being that you have to be explicit about it now. So the easy way out, if the performance was acceptable previously, would be to just client evaluate the last part of the query using .AsEnumerable()
or .ToList()
.
If client evaluation performance is not acceptable (which will imply that it wasn't before the migration either) then you have to rewrite the query. There are a couple of answers by Ivan Stoev that might get you inspired.
I am a little confused by the description of what you want to achieve: I need to find the products with unique title for a particular category
and the code you posted, since I believe it's not doing what you explained. In any case, I will provide possible solutions for both interpretations.
This is my attempt of writing a query to find the products with unique title for a particular category
.
var uniqueProductTitlesForCategoryQueryable = currentContext.Products
.Where(x => x.CategoryId == categoryId)
.GroupBy(x => x.Title)
.Where(x => x.Count() == 1)
.Select(x => x.Key); // Key being the title
var productsWithUniqueTitleForCategory = currentContext.Products
.Where(x => x.CategoryId == categoryId)
.Where(x => uniqueProductTitlesForCategoryQueryable .Contains(x.Title))
.Select(x => new ProductViewModel
{
Id = x.Id,
Title = x.Title,
CategoryId= x.CategoryId
}).ToList();
And this is my attempt of rewriting the query you posted:
currentContext.Products
.Select(product => product.Title)
.Distinct()
.SelectMany(uniqueTitle => currentContext.Products.Where(product => product.Title == uniqueTitle ).Take(1))
.Select(product => new ProductViewModel
{
Id = product.Id,
Title = product.Title,
CategoryId= product.CategoryId
})
.ToList();
I am getting the distinct titles in the Product
table and per each distinct title I get the first Product
that matches it (that should be equivalent as GroupBy(x => x.Title)
+ FirstOrDefault
AFAIK). You could add some sorting before the Take(1)
if needed.
score:-3
You should use .GroupBy() AFTER materialization. Unfortunately, EF core doesn't support GROUP BY. In version 3 they introduced strict queries which means you can not execute IQeuriables that can't be converted to SQL unless you disable this configuration (which is not recommended). Also, I'm not sure what are you trying to get with GroupBy() and how it will influence your final result. Anyway, I suggest you upgrade your query like this:
currentContext.Products
.Select(x=> new {
x.Id,
x.Title,
x.Category
})
.ToList()
.GroupBy(x=> x.Title)
.Select(x => new Wrapper
{
ProductsTitle = x.Key,
Products = x.Select(p=> new ProductViewModel{
Id = p.Id,
Title = p.Title,
CategoryId= p.CategoryId
}).ToList()
}).ToList();
score:-2
Short answer is you deal with breaking changes in EF Core versions.
You should consider the total API and behavior changes for migration from 2.2
to 5.0
as I provided bellow:
You may face other problems to write valid expressions using the newer version. In my opinion, upgrading to a newer version is not important itself. This is important to know how to work with a specific version.
score:0
You can use Join
for this query as below :
currentContext.Products
.GroupBy(x => x.Title)
.Select(x => new ProductViewModel()
{
Title = x.Key,
Id = x.Min(b => b.Id)
})
.Join(currentContext.Products, a => a.Id, b => b.Id,
(a, b) => new ProductViewModel()
{
Id = a.Id,
Title = a.Title,
CategoryId = b.CategoryId
}).ToList();
If you watch or log translated SQL query, it would be as below:
SELECT [t].[Title], [t].[c] AS [Id], [p0].[CategoryId] AS [CategoryId]
FROM (
SELECT [p].[Title], MIN([p].[Id]) AS [c]
FROM [Product].[Products] AS [p]
GROUP BY [p].[Title]
) AS [t]
INNER JOIN [Product].[Products] AS [p0] ON [t].[c] = [p0].[Id]
As you can see, the entire query is translated into one SQL query and it is highly efficient because GroupBy operation is being performed in database and no additional record is fetched by the client.
score:0
As mentioned by Ivan Stoev, EFC 2.x just silently loads full table to the client side and then apply needed logic for extracting needed result. It is resource consuming way and thanks that EFC team uncovered such potential harmful queries.
Most effective way is already known - raw SQL and window functions. SO is full of answers like this.
SELECT
s.Id,
s.Title,
s.CategoryId
FROM
(SELECT
ROW_NUMBER() OVER (PARTITION BY p.Title ORDER BY p.Id) AS RN,
p.*
FROM Products p) s
WHERE s.RN = 1
Not sure that EFC team will invent universal algorithm for generating such SQL in nearest future, but for special edge cases it is doable and maybe it is their plan to do that for EFC 6.0
Anyway if performance and LINQ is priority for such question, I suggest to try our adaptation of linq2db
ORM for EF Core projects: linq2db.EntityFrameworkCore
And you can get desired result without leaving LINQ:
urrentContext.Products
.Select(x => new
{
Product = x,
RN = Sql.Ext.RowNumber().Over()
.PartitionBy(x.Title)
.OrderBy(x.Id)
.ToValue()
})
.Where(x => x.RN == 1)
.Select(x => x.Product)
.Select(x => new ProductViewModel
{
Id = x.Id,
Title = x.Title,
CategoryId = x.CategoryId
})
.ToLinqToDB()
.ToList();
Source: stackoverflow.com
Related Articles
- Entity framework 5.0 First or Group By Issue- After upgrading from 2.2 to 5.0
- IQueryable two tables from Code First Entity Framework
- Map extra column from stored procedure to Entity Framework code first model
- Entity Framework 6 Code First Custom Functions
- Left join after a into group in Linq using entity framework (core)
- Entity Framework Code First without app.config
- Query Xml from SQL using Entity Framework Database First
- Entity Framework Code First using context in Controller
- many to many mapping in entity framework code first
- How to Create a Run-Time Computed (NotMapped) Value in Entity Framework Code First
- updating data in many-to-many relationship in entity framework in code first existing database
- Entity Framework Code First String Comparison with Oracle Db
- SQL subquery result in LINQ and Entity Framework Code First
- most efficient Entity Framework Code First method of flattening / projecting parent entity with specific child
- Entity Framework Code First - Get blog posts which have certain tags
- How to Query Icollections of Entity Framework Code First Data
- Entity Framework code first - Many to Many - Include conditional
- Create a entity framework LINQ group join query to return a subset of properties from DTO and a list of another DTO, ASP.net core
- Entity Framework Code First ToList method timing out on SQL Azure
- How to make a property unique in Entity Framework Code First
- Entity Framework Code First override onModelCreating() for TPT Inheritance Screwing Up Identity User and Role Models
- How to loop through a child list of objects in Entity Framework 4.1 code first
- how to select data by linq in many-to-many relationship in First code Entity framework 5
- Entity framework Select first item in group that satisfies condition or just first item
- Why Entity Framework Code First one to many Doesn't work properly
- LINQ - Entity framework code first - Grouped results to custom class
- map one to one with multiple primary key columns entity framework code first
- Does Linq in Entity Framework code first use SQL or does it get the whole table first?
- How to get first row from DB using Entity Framework where column starts with certain string and continues with numeric characters
- Fixing Loop Reference in Entity Framework Code First when Serializing to Json
- Convert My Code SQL to LINQ?
- Generic Expression Filter
- LINQ filter query with groubBy
- Using Linq to identify the encoding declaration
- EF LINQ many to one filter/where
- Merge XML based node value
- Filtering out null collection property using LINQ
- DB4o Linq query - How to check for null strings
- Linq any - How to select
- IQueryable Union, Unable to create a null constant value of type 'System.Collections.Generic.IEnumerable`1[[System.Int32
- Query LINQ don't work
- Create single permutation with LINQ in C#
- Query Nested Dictionary
- Get a UserControl with a certain interface inside a hierarchy
- Having problems converting conditional where clause in LINQ back over to SQL
- OrderBy selected DateTime column(Column name string) from datatable C#
- .Net core entity framework LINQ expression couldn't be translated
- MVC pass viewmodel view with viewmodel containing parent values and child list
- Sitecore Content Search, converting to List<item> and editing
- Linq can these sub queries be optimised?