EF gets a list of parent objects that have a list of children

I need the following 2 objects in my project

public class Product
{
    public Product()
    {
        this.ProductImages = new HashSet<ProductImage>();
        this.ProductParams = new HashSet<ProductParam>();
    }
    public int ID { get; set; }
    public int BrandID { get; set; }
    public int CodeProductTypeID { get; set; }
    public string SeriaNumber { get; set; }
    public string ModelNumber { get; set; }
    public decimal Price { get; set; }
    public bool AvailableInStock { get; set; }

    public virtual Brand Brand { get; set; }
    public virtual CodeProductType CodeProductType { get; set; }
    public virtual ICollection<ProductImage> ProductImages { get; set; }
    public virtual ICollection<ProductParam> ProductParams { get; set; }

}

public class ProductParam
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public int CodeProductParamId { get; set; }
    public string Value { get; set; }

    public virtual Product Product { get; set; }
    public virtual CodeProductParam CodeProductParam { get; set; }
}

      

and I want to get a list of products that has a list of specified parameters

var prodParamCritria = new List<ProductParam>() 
{
new ProductParam(){CodeProductParamId =1, Value="Black" }, 
new ProductParam(){CodeProductParamId =2, Value="Steal"}
};

      

in sql I can do this using the EXISTS twise clause

SELECT *
FROM   Products p
WHERE  EXISTS (
           SELECT *
           FROM   ProductParams pp
           WHERE  pp.ProductId = p.ID
                  AND (pp.CodeProductParamId = 1 AND pp.[Value] = N'Black')
       )
       AND EXISTS (
               SELECT *
               FROM   ProductParams pp
               WHERE  pp.ProductId = p.ID
                      AND pp.CodeProductParamId = 2
                      AND pp.[Value] = N'Steal'
           )

      

How can I get the same result using EF or linq methods

+3


source to share


3 answers


I suppose something like this should work

db.Product.Where(x => x.ProductParams.FirstOrDefault(y => y.CodeProductParamId == 1) != null && x.ProductParams.FirstOrDefault(y => y.CodeProductParamId == 2) != null).ToList();

      

or better

db.Product.Where(x => x.ProductParams.Any(y => y.CodeProductParamId == 1) && x.ProductParams.Any(y => y.CodeProductParamId == 2)).ToList();

      



Well, if you need to query for the parameters in the prodParamCriteria list, it would look like this:

db.Product.Where(x => prodParamCritria.All(c=> x.ProductParams.Any(p=>p.CodeProductParamId == c.CodeProductParamId && p.Value== c.Value))).ToList();

      

I forgot that complex types cannot be used in the query database, so I suggest you convert your prodParamCriteria to a dictionary and use it in the query

Dictionary<int, string> dctParams = prodParamCritria.ToDictionary(x => x.CodeProductParamId , y=>y.Value);
db.Product.Where(x => dctParams.All(c => x.ProductParams.Any(p=> p.CodeProductParamId == c.Key && p.Value== c.Value))).ToList();

      

+1


source


Try the following:

  var products= db.Products.Where(p=>p.ProductParams.Any(pp=>pp.CodeProductParamId == 1 && pp.Value == "Black") && 
                                     p.ProductParams.Any(pp=>pp.CodeProductParamId == 2 && pp.Value == "Steal"));

      

Update

The problem with working with this list ProductParam

to use as a filter is that EF doesn't know how to convert the object PodructParam

to SQL, thus if you run a query like this:

 var products2 = db.Products.Where(p => prodParamCritria.All(pp => p.ProductParams.Any(e => pp.CodeProductParamId == e.CodeProductParamId && pp.Value == e.Value)));

      

You will get NotSupportedException

as you comment in @BostjanKodre's answer.



I have a solution for you, but you probably won't like it. To solve this problem, you can call the method ToList

before calling Where

. This way you will load all products into memory and you will be working with Linq for Object instead of Linq for Entities, but this is extremely inefficient because you are filtering in memory, not in DB.

var products3 = db.Products.ToList().Where(p => prodParamCritria.All(pp => p.ProductParams.Any(e => pp.CodeProductParamId == e.CodeProductParamId && pp.Value == e.Value)));

      

If you need a filter based on a single criterion, it may be easier and you can filter using a list of a specific primitive type. If, for example, you want to filter products only by CodeProductParamId

, you can do this:

  var ids = new List<int> {1, 2};
  var products = db.Products.Where(p => ids.All(i=>p.ProductParams.Any(pp=>pp.CodeProductParamId==i))).ToList();

      

This is because you are working with a primitive type and not a custom object.

+1


source


another variation:

IEnumerable<Int32> lis = prodParamCritria.Select(x => x.CodeProductParamId).ToList();
var q = Products.Select(
        x => new { 
            p = x,
            cs = x.ProductParams.Where(y => lis.Contains(y.Id))
        }
    ).Where(y => y.cs.Count() == lis.Count()).
    ToList();

      

with the named class (or perhaps without, but not in linqpad)

public class daoClass {
    public Product p {get; set;}
    public Int32 cs {get; set;}
}

IEnumerable<Int32> lis = prodParamCritria.Select(x => x.CodeProductParamId).ToList();
var q = Products.Select(
        x => new daoClass { 
            p = x,
            cs = x.ProductParams.Where(y => lis.Contains(y.Id))
        }
    ).Where(y => y.cs.Count() == lis.Count()).
    SelectMany(y => y.p).
    ToList();

      

0


source







All Articles