Entity Framework Access collection of collection - asp.net-mvc

I'm trying to access a collection of a collection of my main model in my Details action of my controller. But I keep getting the following error
System.InvalidOperationException: 'The property expression 'e => {from Color color in [e].Colors select [color].Images}' is not valid. The expression should represent a property access: 't => t.MyProperty'. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.'
this pops up on the line:
var model = _context.Towers
.Include(e => e.Colors.Select(color => color.Images))
.FirstOrDefault(e => e.ID == id);
Here's some of the other code:
Tower.cs
public class Tower
{
[Key]
public Nullable<int> ID { get; set; }
public List<Color> Colors { get; set; } = new List<Color>();
}
Color.cs
public class Color
{
[Key]
public Nullable<int> ID { get; set; }
public string ColorHash { get; set; }
public List<Image> Images { get; set; } = new List<Image>();
}
Image.cs
public class Image
{
[Key]
public Nullable<int> ID { get; set; }
public string ImagePath { get; set; }
}
I need to be able to access the Images associated with each Color associated with the Tower I'm showing details for.

I think it should be something like this:
var model = _context.Towers
.Include(e => e.Colors)
.ThenInclude(color => color.Images))
.FirstOrDefault(e => e.ID == id);

Related

ASP.NetCore OData DTO $expand navigation property results in empty array

I am using Entity Framework with OData to get data from my mysql database but I don't want to expose database entites to the user, so I've created some DTO's and map them with Automapper.
My Problem is that everything works fine except loading entities with $expand.
There are 2 Entities with 2 DTO's (in my project the dto's and domain models do not look the same, this is only for better reading):
public partial class Product
{
public string Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<ProductPrice> ProductPrices { get; set; }
}
public class ProductDTO
{
[Key]
public string Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
public virtual ICollection<ProductPriceDTO> ProductPrices { get; set; }
public virtual CategoryDTO Category { get; set; }
}
public partial class Category
{
public int Id { get; set; }
public string Title { get; set; }
}
public class CategoryDTO
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
}
public partial class ProductPrice
{
public string VendorId { get; set; }
public string ProductId { get; set; }
public decimal Price { get; set; }
public virtual Product Product { get; set; }
public virtual Vendor Vendor { get; set; }
}
public class ProductPriceDTO
{
[Key]
[ForeignKey("Vendor")]
public string VendorId { get; set; }
[Key]
[ForeignKey("Product")]
public string ProductId { get; set; }
public decimal Price { get; set; }
public virtual VendorDTO Vendor { get; set; }
public virtual ProductDTO Product { get; set; }
}
The models are created the following way:
public IEdmModel GetEdmModel(IServiceProvider serviceProvider)
{
var builder = new ODataConventionModelBuilder(serviceProvider);
builder.Namespace = "Functions";
//category
builder.EntitySet<CategoryDTO>("Categories").EntityType.Select().Filter().OrderBy().Expand().Count().Page();
//product
builder.EntitySet<ProductDTO>("Products").EntityType.Select().Filter().OrderBy().Expand().Count().Page();
return builder.GetEdmModel();
//productprice
builder.EntitySet<ProductPriceDTO>("ProductPrices").EntityType.Select().Filter().OrderBy().Expand().Count().Page();
}
Automapper profile:
public AutoMapperProfile()
{
CreateMap<Product, ProductDTO>()
.ForMember(dto => dto.Category, conf => conf.AllowNull())
.ForMember(dto => dto.ProductPrices, dest => dest.MapFrom(x => x.ProductPrices))
.ForMember(dto => dto.ProductPrices, dest => dest.ExplicitExpansion())
.ForMember(dto => dto.ProductPrices, conf => conf.AllowNull());
CreateMap<ProductPrice, ProductPriceDTO>()
.ForMember(dto => dto.Product, conf => conf.AllowNull())
.ForMember(dto => dto.Vendor, conf => conf.AllowNull());
}
Controller:
[Authorize]
[ODataRoutePrefix("Products")]
public class ProductsController : BaseODataController
{
private readonly IProductService ProductService;
private readonly IProductPriceService ProductPriceService;
public ProductsController(IMapper mapper, IProductService productService, IProductPriceService productPriceService) : base(mapper)
{
ProductService = productService;
ProductPriceService = productPriceService;
}
[AllowAnonymous]
[EnableQuery]
public IQueryable<ProductDTO> Get(ODataQueryOptions queryOptions)
{
var query = ProductService.QueryProducts();
string[] includes = GetExpandNamesFromODataQuery(queryOptions);
if (includes != null && includes.Length > 0)
{
return query.ProjectTo<ProductDTO>(null, includes);
}
return query.ProjectTo<ProductDTO>();
}
[AllowAnonymous]
[EnableQuery]
[ODataRoute("({key})")]
public IQueryable<ProductDTO> Get([FromODataUri] string key, ODataQueryOptions queryOptions)
{
var query = ProductService.QueryProducts().Where(x => x.Id.Equals(key));
string[] includes = GetExpandNamesFromODataQuery(queryOptions);
if (includes != null && includes.Length > 0)
{
return query.ProjectTo<ProductDTO>(null, includes);
}
return query.ProjectTo<ProductDTO>();
}
}
As I mentioned above every query works fine ($select, $filter, $orderBy, $count).
But when I call the following:
https://localhost:44376/odata/Products('631794')?$expand=Category
I get:
{"#odata.context":"https://localhost:44376/odata/$metadata#Products","value":[
as response.
In the output of Visual Studio there is a message:
No coercion operator is defined between types 'System.Int16' and 'System.Boolean'.
I think there must be something wrong with the Automapper profile. As I read somewhere .ProjectTo() with include parameters creates a Select to get the related data from the navigation property. I thought it is enough to create the relation with [ForeignKey] in the DTO.

Want to join two tables on primary key, display the results in one view

IQueryable<Product> product = objContext.Set<Product>().Include(p =>
p.Categories.Name).Where(p => p.Id == 2);
As per the current view, I'm getting an error. It says add other model with their properties. i.e. to include Category model and corresponding Name property.
#model IEnumerable<>crudOneToMany.Models.Product>
using viewmodel, is it possible to join two tables?
View
Error
A specified Include path is not valid. The EntityType 'crudOneToMany.Models.Category' does not declare a navigation property with the name 'Name'.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CategoryId { get; set; }
public virtual Category Categories { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
public class ProductDBContext : DbContext
{
public ProductDBContext()
: base("ProductDBContext")
{
}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().HasRequired(o => o.Categories).WithMany(o => o.Products).HasForeignKey(o => o.CategoryId);
base.OnModelCreating(modelBuilder);
}
}
Your problem is here:
.Include(p => p.Categories.Name)
Instead you should write .Include(p => p.Categories)
This means that in output there will be loaded Categories navigation collection to product.
Name is simple string property (is not navigation property so it should not be included)
Here is the proposed ViewModel for you.
ProductViewModel.cs
public class ProductViewModel
{
public int Id { get; set; }
[Required(ErrorMessage = "required")]
public string ProductName { get; set; }
public Category Category { get; set; }
public ICollection<Category> Categories { get; set; }
}

Adding dynamic attributes to a model

I can't wrap my mind around this issue and haven't found the correct search keys yet:
I would like to have several categories of items in which all items have specific attributes. Those attributes (text fields, dropdowns, or checkboxes) should be added to a category and I want to edit and save those attributes for each item.
I'm working with MVC 4 and code-first EF5. How can I implement this?
My first approach were several classes like Text, Dropdown that were inherited from an abstract Attribute class and a Category class like this:
public class Category
{
[Key]
public int CategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Item> Items { get; set; }
public virtual ICollection<Attribute> Attributes { get; set; }
}
But then I had no idea to proceed. Am I on the right way or completely wrong? Can someone give me a few hints I can search for?
Edit
Ultimately I'm trying to build a list of hifi devices. Speakers have different attributes than amplifier and those have different attributes to tape recorders. I would like to give a unified look for the details of each device and pre-define specific attributes to that category with an additional free-for-all text area. Speaker XYZ is my item, Speaker my category and dB an attribute.
Ok so this question is basically about the data design.
First, I assume that the rule is:
One item has one category
One category has many attributes
One item has many attributes associated with the category
For rule no.1, it is good enough in your design. (simplified example)
public class Category{
public IEnumerable<Item> Items{get;set;}
}
public class Item{
public Category Category{get;set;}
}
Its clear enough.
For rule no.2, I think you should make a CategoryAttribute class. It holds the relation between one to many Category and Attribute. Basically, CategoryAttribute is a master, whereas the children will be ItemAttribute.
public class Category{
public IEnumerable<CategoryAttribute> CategoryAttributes{get;set;}
}
public class CategoryAttribute{
public Category Category{get;set;}
public string CategoryName{get;set;}
public string DefaultValue{get;set;} // maybe a default value for specific
// attribute, but it's up to you
public IEnumerable<ItemAttribute> ItemAttributes{get;set;}
}
The IEnumerable<ItemAttribute> is the one to many relation between category attribute and item attribute.
For rule no.3, the the ItemAttribute described in rule no.2 will be represented attribute owned by each item.
public class Item{
public IEnumerable<ItemAttribute> ItemAttributes{get;set;}
}
public class ItemAttribute{
public Item Item {get;set;} // it owned by one attribute
public CategoryAttribute{get;set;} // it owned by one category attribute
}
I don't quite sure about how to represent relation or primary and foreign key in code first. Hopefully I can enhance my answer if needed (and if I able). But hopefully my illustration about relations and the class designs for each objects.
I think something like this may work for you...
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Item> Items { get; set; }
public virtual ICollection<Attribute> Attributes { get; set; }
}
public class Item
{
public int ItemId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
public virtual ICollection<ItemAttribute> ItemAttributes { get; set; }
}
public class Attribute
{
public int AttributeId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Category> Categories { get; set; }
public virtual ICollection<ItemAttribute> ItemAttributes { get; set; }
}
public class ItemAttribute
{
public int ItemId { get; set; }
public int AttributeId { get; set; }
public Item Item { get; set; }
public Attribute Attribute { get; set; }
public string Value { get; set; }
public int ValueInt{ get; set; }
// etc.
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ItemAttribute>()
.HasKey(x => new { x.ItemId, x.AttributeId });
modelBuilder.Entity<ItemAttribute>()
.HasRequired(x => x.Item)
.WithMany(x => x.ItemAttributes)
.HasForeignKey(x => x.ItemId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<ItemAttribute>()
.HasRequired(x => x.Attribute)
.WithMany(x => x.ItemAttributes)
.HasForeignKey(x => x.AttributeId)
.WillCascadeOnDelete(false);
// AttributeCategories is created for you - but you can do the same as ^ above to customize
// just change 'ICollection<Category> Categories' to collection of 'ItemAttribute'
}
// use it like e.g.
var item = new Item { Name = "ItemTest", };
var attribute = new Attribute { Name = "attributeTest", };
item.ItemAttributes = new List<ItemAttribute>
{
new ItemAttribute { Item = item, Attribute = attribute, Value = "test", },
};
var category = new Category
{
Name = "cat1",
Items = new[]
{
item,
new Item{ Name = "Item1", },
new Item{ Name = "Item2", },
new Item{ Name = "Item3", },
new Item{ Name = "Item4", },
new Item{ Name = "Item5", },
},
Attributes = new[]
{
attribute,
new Attribute{ Name = "att1", },
new Attribute{ Name = "att2", },
}
};
db.Categories.Add(category);
db.SaveChanges();
var categories = db.Categories.ToList();
ItemAttribute is used to connect and store values.
And you're going to need to further adjust as per your requirements.
I actually never worked with code first approach, but I can give you some idea about how this scenario can be handled...To me, it looks that Item is the major one instead of Category. So you can have this structure...
public class Category
{
[Key]
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public string CategoryDescription { get; set; }
// use attributes here if you want them for Category
//public Dictionary<string, string> ItemnAttributes { get; set; }
}
public class MyItem
{
[Key]
public int ItemId { get; set; }
public string ItemName { get; set; }
public string ItemDescription { get; set; }
public Category ItemnCatagory { get; set; }
public Dictionary<string, string> ItemnAttributes { get; set; }
}
Hope this helps..

Attribute of LINQ result is NULL but relationship is working

I have set up 3 models, code first and the relationships seem to be working but one is causing me a problem.
I have Article, Language and Edition Classes
public class Article
{
public int ID { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
}
public class Language
{
public int ID { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
public class Edition
{
public int ID { get; set; }
public Article Article { get; set; }
public Language Language { get; set; }
public string Title { get; set; }
public string Details { get; set; }
}
In my bootstrap/DBinitialiser, I can create Objects and populate them fine. The DB is created and the foreign keys for Language and Article are both present on the Edition table and correctly entered.
var engLang = new Language() {Code="en", Name="English Language"};
var altLang = new Language() {Code="xx", Name="Alternative Language"};
db.Languages.Add(engLang);
db.Languages.Add(altLang);
db.SaveChanges();
var testArt = new Article() { Name = "test" };
db.Articles.Add(testArt);
db.SaveChanges();
db.Editions.Add(new Edition(){Article = testArt, Language = engLang, Title="English Content"});
db.Editions.Add(new Edition(){Article = testArt, Language = altLang, Title="Alternative Content"});
db.SaveChanges();
I can now query the Editions and return a list of them, but the Language attribute is always NULL. The Article Attribute works fine.
var query = db.Editions.Where(r => r.Article.ID == Article.ID);
foreach (Edition item in query)
{
// item.Language => NULL
// item.Article => {Object Article}
}
I'm new to .net and Entity-Framework and can't work out why I always get this error.
I can even query by r => r.Language.ID == 1 and still get a NULL attribute on the Edition object.
Make sure you are using EF codefirst in right manner. Here you have some ambiguities. You must determine what relationships actually should exist, in your POCOs. Change classes like bellow:
public class Article
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
}
public class Language
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
public class Edition
{
[Key]
public int ID { get; set; }
public virtual Article Article { get; set; }
public virtual Language Language { get; set; }
public string Title { get; set; }
public string Details { get; set; }
}
With thanks to AmirHossein Mehrvarzi for helping me write my models more clearly, I believe this error to be caused by the lazy loading of entities while iterating through the result of the query. ref: Entity Framework: There is already an open DataReader associated with this Command.
Without enabling MultipleActiveResultSets I simply added an Include statement to my linq
var query = db.Editions.Where(r => r.Article.ID == Article.ID).Include(r => r.Language);
foreach (Edition item in query)
{
// item.Language => {Object Language}
// item.Article => {Object Article}
}

Enitiy Framework Query, Filtering child collections of child collections

I'm using eager loading to populate my object. Is it possible to filter child collections of child collections. The following code gives me the child collection of filtered periods. But I would also like to filter the child of periods where TradeStatus == TradeStatus.Open?
public class Route
{
public Guid RouteId { get; set; }
public string Name { get; set; }
public virtual ICollection<Period> Periods { get; set; }
}
public class Period
{
public Guid PeriodId { get; set; }
public virtual ICollection<Trade> Trades { get; set; }
public bool IsActive { get; set; }
}
public class Trade
{
public Guid PeriodId { get; set; }
public string TradeName { get; set; }
public decimal TradePrice { get; set; }
public TradeStatus TradeStatus { get; set; }
}
string routeName = "UK_USA"
Route route = Context.Set<Route>().SingleOrDefault(r => r.Name.Equals(routeName));
if (route != null)
{
Context.Entry(route).Collection(r => r.Tenors).Query()
.Where(t => t.IsActive)
.Include(t => t.Trades).Load();
}
I've tried the following but it returns
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
Context.Entry(route).Collection(r => r.Tenors).Query()
.Where(t => ValidTenorName.Contains(t.Name))
.Include(t =>
t.Trades.Where(tr=> tr.TradeStatus == TradeStatus.Open).Load();
Is there a solution to this problem, or an alternative way using joins perhaps?
Thanks

Resources