LINQ select statement with many to many - c#

I have a relationship, project categories. This relation is many to many so i have three tables: Project / project_has_category / categories.
I need to select all projects that has a relation with a certain category (by its id)
Project class
public class Project
{
public int ProjectID { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
Category class
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<Project> Projects { get; set; }
}
I have tried the following:
[HttpPost]
public ActionResult Projects(string catID, string strSearch)
{
var cats = Adapter.CategoryRepository.Get();
var projects = Adapter.ProjectRepository.Get().Where(x => x.Categories.Contains(catID));
/*also*/
var projects = Adapter.ProjectRepository.Get().Where(x => cats.Contains(catID));
return View(projects);
}
But this gives the error:
The best overloaded method match for
'System.Collections.Generic.ICollection.Contains(LibModels.Category)'
has some invalid
arguments C:\Users\thomas\Desktop\Freelauncher1005\Freelauncher\Controllers\ProjectController.cs
What am I doing wrong?

Categories is a list of Category objects, you can't search for integer id with Contains method (check signature of this method - it requires Category object to search for):
var projects = Adapter.ProjectRepository.Get()
.Where(x => x.Categories.Contains(catID)) // error
Use Any to check if there is Category object with id equal to your value:
var projects = Adapter.ProjectRepository.Get()
.Where(x => x.Categories.Any(c => c.CategoryID == catID))

You need to cast the ID to int first and use Any then
var id = int.Parse(catID);
Adapter.ProjectRepository.Get().Where(x => x.Categories.Any(y => y.CategoryID == id))

var projects = Adapter.ProjectRepository
.Get()
.Where(p => p.Categories
.Any(c => c.CategoryID == catID));
or possibly
var projects = Adapter.CategoryRepository
.Get()
.Where(c => c.CategoryID == catID)
.Select(c => c.Projects)
.Distinct();
to query it from the other direction

Related

how to select entity with relationship to other entity in EF core

How can I get the list of products for specific project without including project? Something similar to
select p.Id, p.Name
from product p
inner join ProjectProduct j on p.Id = j.ProductId and j.ProjectId = #Id
The closest solution that I have is
var products = await (from proj in context.Project select proj)
.Where(proj => proj.Id == 1)
.Select(proj => proj.Product)
.ToListAsync();
but this returns a List<List<Product>>
in addition I need to be able to add the order by and pagination to that
I know that I can do it by raw sql like this
var param1 = new SqlParameter("#Id", id);
var query = #"select * from product p
inner join ProjectProduct j
on p.Id = j.ProductId and j.ProjectId = #Id ";
var queryable = context.product.FromSqlRaw(query, param1).AsQueryable();
await HttpContext.AddPaginationToHeader(queryable);
var products = await queryable.OrderBy(x => x.Name).Paginate(paginationDTO).ToListAsync();
But I was hoping to do it by linq to entity
public class Project
{
public int Id { get; set; }
public string Name { get; set; }
public List<Product> Product { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public List<Project> Project { get; set; }
}
public class ProjectProduct
{
public int ProjectId { get; set; }
public int ProductId { get; set; }
}
Thanks to Harald Coppoolse's response on another post
and for those that have the same problem
here are the steps
1- we define our query as queryable
var queryable = context.Project
.Where(proj => proj.Id == id)
.SelectMany(p => p.Product).AsQueryable();
2- Now we can pass the query to SQL server like this
var products = await queryable.OrderBy(x =>
x.Name).Skip(...).Take(...).ToListAsync();
for Skip and Take we can create a generic Extension for IQueryable
and add a method to that with return of
return queryable
.Skip((paginationDTO.Page - 1) * paginationDTO.PageSize)
.Take(paginationDTO.PageSize);
and of course our paginationDTO is nothing more than a POCO class with 2 properties of page and pageSize
try this:
var products = await context.Project
.Where(proj => proj.Id == 1)
.Select( proj=>proj.Products)
.FirstOrDefaultAsync();
if you want to order product after this you can do this common way
products= products.OrderBy(p=>p.Name).ToList();
it can be put in one line but EF sometimes generates a very ridiculous Sql script, so better to do this separately.

Using LINQ to query four nested entities. - Include Where condition

I have four classes Offer, Section, Field, and Option:
Where the offer has sections and every section has some fields and each field has some options as shown:
public class Offer
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Section> Sections { get; set; }
}
public class Section
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Field> Fields { get; set; }
}
public class Field
{
public int Id { get; set; }
public string Type { get; set; } //[question, group]
public ICollection<Option> Options { get; set; }
}
public class Option
{
public int Id { get; set; }
public string Name { get; set; }
}
I tried to get the offer by id including the nested entities and this code works perfectly:
var offer = _context.Offers
.Include(o => o.Sections
.Select(s => s.Fields
.Select(f => f.Options)))
.FirstOrDefault(o => o.Id == offerId);
The problem is when I try to filter the Fields by 'type' like this:
var offer = _context.Offers
.Include(o => o.Sections
.Select(s => s.Fields.Where(f => f.Type == "question")
.Select(f => f.Options)))
.FirstOrDefault(o => o.Id == offerId);
and I get this error:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path
I've reviewed lots of questions and still cannot achieve that :(
Linq: query with three nested levels
EF LINQ include nested entities [duplicate]
Using LINQ to query three entitites. - Include path expression must refer to a navigation property defined on the type
Include() is used for Eager loading. It is the process whereby a query for one type of entity also loads related entities as part of the query, so that we don't need to execute a separate query for related entities. Where() is currently not supported inside Include.
If you want to just filter the result you can do something like this :
var offer = _context.Offers
.Include(o => o.Sections
.Select(s => s.Fields
.Select(f => f.Options)))
.FirstOrDefault(o => o.Id == offerId);
var filtered_offer =
new Offer
{
Sections = offer.Sections.Select(S => new Section
{
Id = S.Id,
Name = S.Name,
Fields = S.Fields.Where(f => f.Type == "question").ToList()
}).ToList(),
Id = offer.Id,
Name = offer.Name
};

Linq to entities with join

I'm having some problem with my linqToEntities query. The Product is missing in the query result. Is there any way to return the ProductQuantity with the Product property correctly with a linqToEntities expression?
public class ProductQuantity
{
public string Id { get; set; }
public string SomeProperty { get; set; }
public Product Product { get; set; }
public Guid ProductId { get; set; }
}
public class Product
{
public Guid Id { get; set; }
public string SomeProperty { get; set; }
//...
}
// MyId is the ProductId I need
// The following will return all productQuantity detail but the Product property will be null
var result = myEntities.ProductQuantities.Include(x => x.Product).Where(x => x.ProductId == MyId)
// The following will work but I want to avoid refilling the object like this :
var result = myEntities.ProductQuantities.Include(x => x.Product).Where(x => x.ProductId == MyId)
.Select(y => new ProductQuantity{ SomeProperty = y.SomeProperty, Product = y.Product});
What is the proper way to do this with linq to entities? Why the product is not just simply returned with the query ?
Thanks
EDIT 1
Look like my problem is releated to .Include() when using more than one include.
Just add a Category to ProductQuantity in the preceding example :
//This will return the product but not the category
var result = myEntities.ProductQuantities.Include(x => x.Product).Include(x=> x.Category).Single(x => x.ProductId == MyId)
//This will return the category but not the product
var result = myEntities.ProductQuantities.Include(x => x.Category).Include(x=> x.Product).Single(x => x.ProductId == MyId)
Why only one include can be used and only the first one is working??????? (a saw tons of similar example on the net?)
Any help?
Seems like there is a problem when the same entity is used in any other include. (ex: Product.Unit and Product.AlternateUnit cannot be retreived at the same time if the same entity is used ie:unit) I dont really understand why but I use separate query to fetch the data that cannot be retrieved by the include.

Where clause on list property

I have two classes :
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Product> Product { get; set; }
}
public class Product
{
public string ProductNumber { get; set; }
public string ProductColor { get; set; }
}
I want to create a clause where on property Product (Product.ProductColor == "") I do :
c.Where(x => x.Product.????? == "11").Select(x => x).ToList();
How do this ?
I assume you want to find customers, that have a product with Number 11. If so, you can use function Any:
var result = c
.Where(x => x.Product.Any(p => p.ProductNumber == "11"))
.ToList();
The code filters only those customers, that have at least one product that satisfies condition ProductNumber == "11"
Or if you want to find customers that have specific color then use different expression:
var result = c
.Where(x => x.Product.Any(p => p.ProductColor == "Color"))
.ToList();
Since Product (which really should be named Products) is also a collection, you'd have to drill down into that collection. For example, if you want all Customers from a list of customers where any product color is "11", it might looks like this:
customers.Where(c => c.Product.Any(p => p.ProductColor == "11"))

LINQ to Entities, Where Any In

How to write 'Where Any In' in LINQ to Entity?
Here is my model :
class Chair
{
public int Id { get; set; }
public int TableId { get; set; }
public Table Table { get; set; }
}
class Table
{
public int Id { get; set; }
public ICollection<Chair> Chairs { get; set; }
public ICollection<Category> Categories { get; set; }
public Table()
{
Chairs = new List<Chair>();
Categories = new List<Category>();
}
}
class Category
{
public int Id { get; set; }
public ICollection<Table> Tables { get; set; }
}
I also got a simple list of Category :
List<Category> myCategories = new List<Category>(c,d,e);
I want to get only that Chairs that belongs to Table that got one of the Category from myCategories List. Thats what im trying to do :
var result =
ctx.Chairs.Where(x => x.Table.Categories.Any(y => myCategories.Any(z => z.Id == y.Id))).ToList();
I think its ok but what i get is error :
"Unable to create a constant value of type 'ConsoleApplication1.Category'. Only primitive types or enumeration types are supported in this context"
Try to compare with in-memory categories Ids collection, instead of categories collection.
var myCategoriesIds = myCategories.Select(c => c.Id).ToArray();
var result =
context.Chairs
.Where(
x => x.Table.Categories.Any(
y => myCategoriesIds.Contains(y.Id)))
.ToList();
this is because ctx.Chairs is a collection that is in database, you should retrieve that collection first in order to compare it with in-memory data:
var result = ctx
.Chairs
.AsEnumerable() // retrieve data
.Where(x =>
x.Table.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)))
.ToList();
EDIT: that wouldn't be the correct thing to do if you have a lot of entities on database, what you can do is to split it into two queries:
var tables = ctx.Tables
.Where(x =>
x.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)));
var result = ctx.Chairs
.Where(x =>
tables.Any(t=> t.Id == x.TableId))
.ToList();
You can select Ids from myCategories and use it last statement.
var CategoryIds = myCategories.Select(ct => ct.Id);
var result = ctx.Chairs.Where(x => x.Table.Categories.Any(y => CategoryIds.Any(z => z == y.Id))).ToList();

Categories