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 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.
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
};
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.
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"))
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();