I need an expert solution to optimize some queries in Entity Core, SQL Server 2016 (Azure or virtual) for a few real world applications.
I have tested the app with 10 Products, 4 Attributes, 10 Atribute Values and i get a very big time for query execution: 2 seconds – 3 seconds and this is not ok. I think I am missing
something.
Example:
We have an app who need to filter the products by attributes and filter the attributes by current selected attributes . Ex: Query Products with (2 Gb or 4 Gb) and (Red Colour or
Blue Colour)
Memory
Dimmension
Colour
The Database structure is:
Products (ProductID)
Atributes (AtributeID, Title) (ex: Memory),
AtributeValues(AtributeValueID, AtributeID, Title) (ex: 2 GB)
Product Atributes (ProductAtributeID, ProductID, AtributeID, AtributeValueID)
We need to optimize the Queries
1. Filter Products By Selected AtributeValues
1.1 Select Product List By Selected AtributeValues
public static async Task<List<Product>> GetProductsByAtributes(this DbSet<Product> source, List<AtributeValue> Filters, int? Page, int PageSize)
{
List<Product> rets;
var query = source. AsQueryable();
foreach (var filtersGroup in Filters.GroupBy(p => p.AtributeID))
{
int atributeID = filtersGroup.Key;
var atributeValues = filtersGroup.Select(p => p.AtributeValueID);
query = query.Where(p => p.ProductAtributes.Any(y => y.AtributeID == atributeID && atributeValues.Contains(y.AtributeValueID)));
}
rets = await query.Skip(PageIndex * PageSize).Take(PageSize).AsNoTracking().ToListAsync();
return rets;
}
SQL Translation:
Executed DbCommand (1,746ms)
SELECT *...
FROM [Products] AS [p]
WHERE ( EXISTS (
SELECT 1
FROM [ProductAtributes] AS [y]
WHERE (([y].[AtributeID] = @__atributeID_5) AND [y].[AtributeValueID] IN (25)) AND ([p].[ProductID] = [y].[ProductID]))) AND EXISTS (
SELECT 1
FROM [ProductAtributes] AS [y0]
WHERE (([y0].[AtributeID] = @__atributeID_7) AND [y0].[AtributeValueID] IN (32)) AND ([p].[ProductID] = [y0].[ProductID]))) AND EXISTS (
SELECT 1
FROM [ProductAtributes] AS [y1]
WHERE (([y1].[AtributeID] = @__atributeID_9) AND [y1].[AtributeValueID] IN (50)) AND ([p].[ProductID] = [y1].[ProductID]))
ORDER BY [p].[ProductID] DESC
OFFSET @__p_11 ROWS FETCH NEXT @__p_12 ROWS ONLY
1.2 SelectProducts Count By Selected AtributeValues (Need for paging)
public static async Task<int> GetProductsCountByAtributes(this DbSet<Product> source, List<AtributeValue> Filters int? Page, int PageSize)
{
int ret;
var query = source. AsQueryable();
foreach (var filtersGroup in Filters.GroupBy(p => p.AtributeID))
{
int atributeID = filtersGroup.Key;
var atributeValues = filtersGroup.Select(p => p.AtributeValueID);
query = query.Where(p => p.ProductAtributes.Any(y => y.AtributeID == atributeID && atributeValues.Contains(y.AtributeValueID)));
}
ret = await query.CountAsync();
return ret;
}
SQL Translation
Executed DbCommand (1,948ms)
SELECT COUNT(*)
FROM [Products] AS [p]
WHERE ( EXISTS (
SELECT 1
FROM [ProductAtributes] AS [y]
WHERE (([y].[AtributeID] = @__atributeID_5) AND [y].[AtributeValueID] IN (25)) AND ([p].[ProductID] = [y].[ProductID]))) AND EXISTS (
SELECT 1
FROM [ProductAtributes] AS [y0]
WHERE (([y0].[AtributeID] = @__atributeID_7) AND [y0].[AtributeValueID] IN (32)) AND ([p].[ProductID] = [y0].[ProductID]))) AND EXISTS (
SELECT 1
FROM [ProductAtributes] AS [y1]
WHERE (([y1].[AtributeID] = @__atributeID_9) AND [y1].[AtributeValueID] IN (50)) AND ([p].[ProductID] = [y1].[ProductID]))
2.Select and Count Atributes and AtributeValues by Selected Atributes
public static async Task<List<Atribute>> GetAtributesBySelectedFiltersAsync(this DbSet<Atribute> source, ApplicationDbContext context, List<AtributeValue> Filters)
{
// get the attribute List
List<Atribute> rets = await context.Atributes.AsNoTracking().ToListAsync();
if (rets != null)
{
var queryAtributesInt = rets.Select(p => p.AtributeID);
// Count Products By Selected Filters
var queryProductsCount = context.Products.AsQueryable();
foreach (var filtersGroup in Filters.GroupBy(p => p.AtributeID))
{
int atributeID = filtersGroup.Key;
var atributeValues = filtersGroup.Select(p => p.AtributeValueID);queryProductsCount =
queryProductsCount.Where(p => p.ProductAtributes.Any(y => y.AtributeID == atributeID && atributeValues.Contains(y.AtributeValueID)));
}
var query = context.AtributeValues.Where(p => queryAtributesInt.Contains(p.AtributeID)).AsQueryable();
var querySelect = (filters.Count() > 0) == true ?
// if there are selected filters
query.Select
(
a => new
{
AtributeValueID = a.AtributeValueID,
Title = a.Title,
Importance = a.Importance,
AtributeID = a.AtributeID,
Count = queryProductsCount.Where(p => p.ProductAtributes.Any(av => av.AtributeValueID == a.AtributeValueID)).Count()
}
).AsQueryable()
:
// if there are not selected filters
query.Select
(
a => new
{
AtributeValueID = a.AtributeValueID,
Title = a.Title,
Importance = a.Importance,
AtributeID = a.AtributeID,
Count = a.ProductAtributes.Count()
}
).AsQueryable();
var queryResult = await querySelect.OrderBy(p => p.Importance).AsNoTracking().ToListAsync();
// convert from anomymous
var atributeValueResults= new List<AtributeValue>();
foreach (var item in queryResult)
{
AtributeValue ret = new AtributeValue();
ret.AtributeID = item.AtributeID;
ret.AtributeValueID = item.AtributeValueID;
atributeValueResults.Add(ret);
}
foreach (var item in rets)
{
item.AtributeValues = atributeValueResults.Where(p => p.AtributeID == item.AtributeID).ToList();
}
}
return rets;
}
SQL Translation
Executed DbCommand (3,298ms)
SELECT [p].[AtributeValueID], [p].[Title], [p].[Importance], [p].[AtributeID], (
SELECT COUNT(*)
FROM [Products] AS [p1]
WHERE (((([p1].[IsPublished] = 1) AND EXISTS (
SELECT 1
FROM [ProductAtributes] AS [y2]
WHERE ((([y2].[AtributeID] = @__atributeID_1) AND ([y2].[ProductCategoryID] = @__8__locals1_ProductCategoryID_2)) AND [y2].[AtributeValueID] IN (25)) AND ([p1].[ProductID] = [y2].[ProductID]))) AND (([p1].[IsPublished] = 1) AND EXISTS (
SELECT 1
FROM [ProductAtributes] AS [y3]
WHERE ((([y3].[AtributeID] = @__atributeID_4) AND ([y3].[ProductCategoryID] = @__8__locals1_ProductCategoryID_5)) AND [y3].[AtributeValueID] IN (32)) AND ([p1].[ProductID] = [y3].[ProductID])))) AND (([p1].[IsPublished] = 1) AND EXISTS (
SELECT 1
FROM [ProductAtributes] AS [y4]
WHERE ((([y4].[AtributeID] = @__atributeID_7) AND ([y4].[ProductCategoryID] = @__8__locals1_ProductCategoryID_8)) AND [y4].[AtributeValueID] IN (50)) AND ([p1].[ProductID] = [y4].[ProductID])))) AND EXISTS (
SELECT 1
FROM [ProductAtributes] AS [av0]
WHERE (([av0].[ProductCategoryID] = @__8__locals1_ProductCategoryID_10) AND ([av0].[AtributeValueID] = [p].[AtributeValueID])) AND ([p1].[ProductID] = [av0].[ProductID]))
)
FROM [AtributeValues] AS [p]
WHERE [p].[AtributeID] IN (31, 15, 18, 26, 28, 27)
ORDER BY [p].[Importance]