Reputation: 2827
I have a database created by EF code-first, where there are two major classes: 'categories', and: 'products'.
Each category has a navigation property of a list of products (those that are associated with that category).
I've created a web api returning an 'IQueryable' - all the products associated with a specific category.
This is the api controller - which works just fine when called via a url:
[HttpGet]
public IQueryable<Product> ProductsFilteredByCategory(int categoryId)
{
return _contextProvider.Context.Categories.Include("Products").First(c => c.CategoryId == categoryId).Products.AsQueryable();
}
However, when I make a call from breeze via the following function:
var getProducts = function (productsObservable, parentCategoryId) {
var query = EntityQuery.from("ProductsFilteredByCategory")
.withParameters({ categoryId: parentCategoryId });
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
}
I get the following error: 'undefined is not a function'.
Strangely, when I change the controller on the server side to return just a plain 'IQyeryable' list of all products - there are no errors... but this is not what I need - I only need those products which are associated with a specific category...
Thanks for any help !
Elior
Upvotes: 0
Views: 527
Reputation: 2827
Thank you both very much for the support, I do appreciate it.
Kadumel, I could not use your solution because each 'Product' can belong to more than one 'Category' so there's no reference form 'Product' to 'Category'. However, I did change the controller method as you suggested and now things work.
Here's the code the represents everything so that hopefully it will also help others with similar situations. I'll be happy to hear and suggestions for improving the code.
public class Category
{
[Key]
public int CategoryId { get; set; }
[Required]
public string Name { get; set; }
public Int16? Order { get; set; }
[ForeignKey("ParentCategoryId")]
public Category ParentCategory { get; set; }
public int? ParentCategoryId { get; set; }
[ForeignKey("ParentCategory")]
[InverseProperty("ParentCategory")]
public List<Category> SubCategories { get; set; }
[ForeignKey("ProductId")]
public List<Product> Products { get; set; }
}
public class Product
{
[Key]
public int ProductId { get; set; }
[Required]
public string Name { get; set; }
public string Desc { get; set; }
public int Price { get; set; }
//[ForeignKey("CategoryId")]
//[InverseProperty("Products")]
//public List<Category> Categories { get; set; }
}
(as you can see I commented out the backwards list of links from 'Product' to 'Category' which could have been used as a backwards reference from 'Product' to 'Category' as Kdumel suggested I use these links, but it seems to me it's too 'heavey' to have so many references when I can do without them, do you agree?)
This is the code in the controller:
[HttpGet]
public IQueryable<Category> CategoryWithProducts(int categoryId)
{
return _contextProvider.Context.Categories.Include("Products").Where(c => c.CategoryId == categoryId);
}
and this is the breeze code:
var getProducts = function (productsObservable, parentCategoryId) {
var query = EntityQuery.from("CategoryWithProducts")
.withParameters( {categoryId: parentCategoryId } )
.select("Products");
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (productsObservable) {
productsObservable(data.results[0].products);
}
log('Retrieved [Products] from remote data source',
data, true);
}
};
As you can see, the result is a single 'Category' from which I retrieve the 'products' in the 'querySuccedded()' function.
This works, but I was hoping to use a different approach which did NOT work: instead of passing 'parentCategoryId' I tried to pass the actual object and not the ID as 'parentCategoryObj', and then I thought of using the following line to load the 'products' navigation property without making an explicit call to the breeze controller:
parentCategoryObj.entityAspect.loadNavigationProperty("products")
However, this resulted in no 'products' being loaded as if the navigation property is null. Oddly, when I changed the word "products" to "subCategories" in this line just to check if navigation properties are the problem - the 'subCategories' data was loaded properly. I did not understand why one navigation property behaves differently from another (both are lists). I read more about this and noticed the currently Breeze does not support "Many to Many" relationships, I assume this is the reason, am I right?...
Thanks you guys again, It is a releif to know good people are willing to help !
Elior
Upvotes: 0
Reputation: 14995
You don't have to use the parameter in your controller method, you can remove the parameter and declare it in your breeze entity query with .where() the good thing about doing it this way is your controller doesn't have to be so specific.
Also, you don't need to use .include() as breeze allows you to expand the navigation property with .expand()
This leverages more of what breeze can help you with in your project.
Change ProductsFilteredByCategory(int categoryId) to
Public IQueryable<Product> Products()
{
return _contextProvider.Context.Products();
}
And your breeze query to
var query = EntityQuery.from('Products')
.where('categoryId', '==', categoryId)
.expand('Category');
If you don't want to do this, then I think you need to redo your controller method. You are supposed to be returning Product entity but you are querying for categories.
Upvotes: 1