Home > OS >  use Intersect into IQueryable and EfCore
use Intersect into IQueryable and EfCore

Time:03-22

I'm trying to use LinQ Intersect (or equivalent) into an IQueryable method but it seems like I'm doing it wrong.

I have some PRODUCTS that match some SPECIFITY (like colors, materials, height...), those specifications have different values, for example:

color : blue, red, yellow height : 128cm, 152cm...

I need to get the products that match ALL the list of couple specifityId / specifityValue I provide.

Here what I'm trying to do:

// The list of couple SpecifityID (color, material..) / SpecifityValue (red, yellow, wood...)
List<string> SpecId_SpecValue = new List<string>();

SpecId_SpecValue.Add("3535a444-1139-4a1e-989f-795eb9be43be_BEA");
SpecId_SpecValue.Add("35ad6162-a885-4a6a-8044-78b68f6b2c4b_Purple");

int filterCOunt = SpecId_SpecValue.Count;

var query =
            Products
                    .Include(pd => pd.ProductsSpecifity)
                .Where(z => SpecId_SpecValue
                    .Intersect(z.ProductsSpecifity.Select(x => (x.SpecifityID.ToString()   "_"   x.SpecifityValue)).ToList()).Count() == filterCOunt);

I got the error : InvalidOperationException: The LINQ expression 'DbSet() 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. which mean it can't be translated to SQL and I need to ToList before my filter.

The problem is, I don't want to call ToList() because I got huge number of products in my Database and I don't want to load them in memory before filtering them.

Is there an other way to achieve what I need to do?

CodePudding user response:

The query expresses "products of which all x.SpecifityID.ToString() "_" x.SpecifityValue combinations exactly match some given combinations".

Set combination operators like Except often don't play nice with EF for various reasons I'm not going into here. Fortunately, in many of these cases a work-around can be found by using Contains, which EF does support well. In your case:

var query = Products.Include(pd => pd.ProductsSpecifity)
    .Where(z => z.ProductsSpecifity
        .Select(x => x.SpecifityID.ToString()   "_"   x.SpecifityValue)
            .Count(s => SpecId_SpecValue.Contains(s)) == filterCount);

Please note that the comparison is not efficient. Transforming database values before comparison disables any use of indexes (is not sargable). But doing this more efficiently isn't trivial in EF, see this.

CodePudding user response:

I ended up using a solution found in the link @Gert Arnold provide here.

I used BlazarTech.QueryableValues.SqlServer @yv989c's answers

Here's what is now working like a charm :

// The list of couple SpecifityID (color, material..) / SpecifityValue (red, yellow, wood...)
            Dictionary<Guid, string> SpecId_SpecValue = new Dictionary<Guid, string>();

            SpecId_SpecValue.Add(new Guid("3535a444-1139-4a1e-989f-795eb9be43be"), "BEA");
            SpecId_SpecValue.Add(new Guid("35ad6162-a885-4a6a-8044-78b68f6b2c4b"), "Purple");


            // BlazarTech.QueryableValues.SqlServer
            var queryableValues = DbContext.AsQueryableValues(SpecId_SpecValue);

            var query = Products.Include(pd => pd.ProductsSpecifity)
                .Where(x => x.ProductsSpecifity
                    .Where(e => queryableValues
                        .Where(v =>
                            v.Key == e.SpecifityID &&
                            v.Value == e.SpecifityValue
                        )
                        .Any()
                    ).Count() == dynamicFilter.Count);
  • Related