I have an entity called Product and inside it a navigation property called ProductAttributes.
I need to get products with multiple conditions like this:
var products = context.Products
.Include(x => x.ProductAttributes)
.ThenInclude(x => x.Attribute)
.AsNoTracking()
.Where(x => x.ProductAttributes.FirstOrDefault(x => x.AttributeId == attributesIds[0]) != null
&& x.ProductAttributes.FirstOrDefault(x => x.AttributeId == attributesIds[1]) != null
&& x.ProductAttributes.FirstOrDefault(x => x.AttributeId == attributesIds[2]) != null);
I've tried to build the expression dynamically, but all of my tries failed.
This is the code I've written:
var arg = Expression.Parameter(typeof(Product), "x");
var left = Expression.Property(arg, "ProductAttributes");
var right = Expression.Constant(null);
var exp = Expression.NotEqual(left, right);
Expression<Func<Product, bool>> expression = Expression.Lambda<Func<Product, bool>>(exp, arg);
foreach (var id in attributesIds)
{
var searchArg = Expression.Parameter(typeof(ProductAttribute), "z");
var searchLeft = Expression.Property(searchArg, "AttributeId");
var searchRight = Expression.Constant(id);
var searchExp = Expression.Equal(searchLeft, searchRight);
Expression<Func<ProductAttribute, bool>> searchExpression = Expression.Lambda<Func<ProductAttribute, bool>>(searchExp, searchArg);
Expression<Func<Product, bool>> subExpression = x => x.ProductAttributes.AsQueryable().Where(searchExpression).FirstOrDefault() != null;
var andExp = Expression.And(expression.Body, subExpression.Body);
expression = Expression.Lambda<Func<Product, bool>>(andExp, arg);
}
var products = context.Products.Include(x => x.ProductAttributes)
.ThenInclude(x => x.Attribute)
.AsNoTracking()
.Where(expression);
// .Where(x => x.ProductAttributes.FirstOrDefault(x => x.AttributeId == attributesIds[0]) != null);
return Ok(products);
The error I get is:
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.
So, how can I create this expression? Or what is the wrong in my code?
Includes unless you need to force eager loading. Second, you can writeWhere(x=>attributeIds.Contain(x.AttributeId)to generate ax.AttributeID in (1,4,7,123,...)clause in SQL. You don't need to dynamically generate anything.FirstOrDefaultis mainly aTOP 1clauses, not a filtering clause.ProductAttributes? This smells strongly of the Entity-Attribute_Value antipattern (ie very bad idea). Instead of making your code easier to modify, you just made it a lot harder to write and query. You can't fix a bad design with queries. If anything, you have to "pivot" the EAV schema back into a proper table just to query itContain(x.AttributeId)I need to get the products which have productAttributes have attributeId=1 AND attributeId=2 ...etc, and usingWhere(x=>attributeIds.Contain(x.AttributeId)I got products have productAttributes have attributeId=1 OR attributeId=2 ...Attributestable instead of columns? That's the problem here, not LINQ. You're trying to find a single product that has 3 specific attribute values but you can't perform a simpleWHERE field1=@val1 AND field2=@val2 AND field3=@val3. LINQ can't do anything that can't be done in SQL and the SQL query you'd want for this would be very complex. You don't need such a schema in the first place. SQL Server allows over 30000 sparse columns. You can use XML or JSON fields to store "bags" of extra attributes too