Why can't I get this model to map to this viewmodel? - c#

I have this entity model for a recursively structured category tree:
public class ProductCategory
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Title { get; set; }
public int SortOrder { get; set; }
public ProductCategory ParentCategory { get; set; } //nav.prop to parent
public ICollection<ProductCategory> Children { get; set; } = new List<ProductCategory>();
public ICollection<ProductInCategory> ProductInCategory { get; set; }
public ICollection<FrontPageProduct> FrontPageProduct { get; set; } // Nav.prop. to front page product
// Recursive sorting:
public void RecursiveOrder()
{
Children = Children.OrderBy(o => o.SortOrder).ToList();
Children.ToList().ForEach(r => r.RecursiveOrder());
}
}
... and this, supposedly, matching ViewModel:
public class ViewModelProductCategory
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Title { get; set; }
public int SortOrder { get; set; }
public bool Checked { get; set; } // Used for assigning a product to multiple categories in Product/Edit
// Nav.props:
public ViewModelProductCategory ParentCategory { get; set; } // Nav.prop. to parent
public ICollection<ViewModelProductCategory> Children { get; set; } // Nav.prop. to children
public IEnumerable<ViewModelProduct> Products { get; set; } // Products in this category
public IEnumerable<ViewModelFrontPageProduct> FrontPageProducts { get; set; }
public string ProductCountInfo { get { return Products?.Count().ToString() ?? "0"; } }
}
When I try to populate the viewmodel, like this:
List<ProductCategory> DbM =
await _context.ProductCategories
.Include(c => c.Children)
.Where(x => x.ParentId == null)
.OrderBy(o => o.SortOrder)
.ToListAsync();
foreach (var item in DbM)
{
VMSelectCategories.Add(
new ViewModelProductCategory{
Id = item.Id,
Children = item.Children,
Title = item.Title
});
}
VisualStudio screams at me that it can't implicitly convert ProductCategory to ViewModelCategory. This happens at Children = item.Children.
Why isn't it working? Can't I have additional properties in the viewmodel that I use unrelated to the original entity model? Like Checked and ProductCountInfo?

In this line:
Children = item.Children,
Children is the ViewModelProductCategory.Children property, which is type ICollection<ViewModelProductCategory>, while item.Children is the ProductCategory.Children property, which is type ICollection<ProductCategory>. They are different types and neither inherits or implements the other so why would you expect to be able to assign an object of one type to a property of the other type? Would you expect this to work:
var list1 = new List<int> {1, 2, 3};
List<string> list2 = list1;
Of course not (I hope) because assigning a List<int> object to a List<string> variable would be silly. What you're trying to do is exactly the same. You need to provide some way to convert from one type to the other and then implement that in your code. An option for that might be like this:
Children = item.Children.Select(pc => MapToViewModel(pc)).ToList(),
where MapToViewModel is a method that you write to create a ViewModelProductCategory and populate its properties from a ProductCategory parameter.
You might also look at using something like AutoMapper.

Related

LINQ statement issue for dynamic view component menu

I have three level (Category- Subcategory - Nestedcategory) dropdown navigation menu on my website for which data must come dynamically from database. My main problem in generation of InvokeAsync() method to make it work. I can write two levels which work fine as I checked, but confused in defining Nestedcategories - need to get it from subcategories which derived from categories.
Here is my Controller
public class MenuViewComponent: ViewComponent
{
private readonly SamirDbContext _samirDbContext;
public MenuViewComponent(SamirDbContext samirDbContext)
{
_samirDbContext = samirDbContext;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var menu = await _samirDbContext.Categories.Include(x => x.Subcategories).ThenInclude(y => y.NestedCategories).
Select(x => new MenusModel()
{
Category = x,
Id = x.Id,
Subcategories = x.Subcategories,
**NestedCategories = ...**
}).ToListAsync();
return View(menu);
}
}
Here are models:
public class Category
{
public Category()
{
Subcategories = new HashSet<Subcategory>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Subcategory> Subcategories { get; set; }
}
public class Subcategory
{
public Subcategory()
{
Posts = new HashSet<Post>();
NestedCategories = new HashSet<NestedCategory>();
}
public int Id { get; set; }
public string Name { get; set; }
public Category Category { get; set; }
public int CategoryId { get; set; }
public ICollection<Post> Posts { get; set; }
public ICollection<NestedCategory> NestedCategories { get; set; }
}
public class NestedCategory
{
public NestedCategory()
{
Posts = new HashSet<Post>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Post> Posts { get; set; }
public Subcategory Subcategory { get; set; }
public int SubcategoryId { get; set; }
}
Menu ViewModel
public class MenusModel
{
public int Id { get; set; }
public Category Category { get; set; }
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Subcategory> Subcategories { get; set; }
public Subcategory Subcategory { get; set; }
public IEnumerable<NestedCategory> NestedCategories { get; set; }
public NestedCategory NestedCategory { get; set; }
}
Please, help in completion InvokeAsyinc() method in order to get work for 3 level menu.
You can use SelectMany() method, change the linq like below:
var menu = await _samirDbContext.Categories
.Select(x => new MenusModel()
{
Category = x,
Id = x.Id,
Subcategories = x.Subcategories,
NestedCategories = x.Subcategories.SelectMany(s => s.NestedCategories).ToList()
}).ToListAsync();
Looking at your models for Category, Subcategory, and NestedCategory, I'm asking myself why you actually could need to have a separate output property (**NestedCategories = ...**) in your final Select statement.
Let's think this way if the NestedCategory is defined inside the Subcategory collection, then every Subcategory element should have its own list of NestedCategory-ies, which will be available when you will check some Subcategory from the dropdown.
So, my advice here is to leave the result as follows:
var menu = await _samirDbContext.Categories
.Include(x => x.Subcategories)
.ThenInclude(y => y.NestedCategories)
.Select(x => new MenusModel()
{
Category = x,
Id = x.Id,
Subcategories = x.Subcategories
.Select(sb => new SubcategoryDTO
{
sb.Id,
sb.Name,
...
NestedCategories = sb.NestedCategories
.Select(nst => new NestedCategoriesDTO
{
nst.Id,
nst.Name,
...
})
}),
}).ToListAsync();
Then you can use the above model in your UI.
Hope this will help ))

How to access list of a ViewModel

I have the following model;
public class Object
{
public int Id { get; set; }
public string Name { get; set; }
public Nullable<double> Price { get; set; }
public string Description { get; set; }
public string Image { get; set; }
public Nullable<int> Type { get; set; }
}
and i create a view model;
public class PlanBaseTypedObjects
{
public int? Id { get; set; }
public IEnumerable<Object> ObjectDetails { get; set; }
}
in my controller i did grouping as follows;
var model = model1.GroupBy(t => t.Type).Select(g => new PlanBaseTypedObjects
{
Id = g.Key,
ObjectDetails = g
});
How can i get the number of records that belong to a particular "Type"??
for an example for type 1 how many records under ObjectDetails??
and can i access the "objectDetails" directly??
for an example if i want to know the "Name" of Id=3 of Type 2.how can i get it??
You need to iterate on the grouping result to find the count for each one.
foreach(var group in model)
{
int groupCount = group.ObjectDetails.Count();
}
If you want to access the items in ObjectDetails you need another foreach:
foreach(var group in model
{
int groupCount = group.ObjectDetails.Count();
foreach(var item in group.ObjectDetails))
{
//do something with item
}
}

automapper create.map() to a collection property

How do you map to a collection of property but only interested with the last record of the collection.
Say, something like.
public class ItemDTO <-- destination class
{
public string Name { get; set; }
public decimal PricesPrice { get; set; }
}
public class Item <-- Source class
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Price> Prices { get; set; }
}
public class Price <-- Source class
{
public int Id { get; set; }
public int ItemId { get; set; }
public decimal Price { get; set; }
public virtual Item Item { get; set; }
}
Then I have tried something like this, but doesn't seems to be right.
Mapper.CreateMap<Item, ItemDTO>()
.ForMember(dto => dto.PricesPrice, opt => opt.MapFrom(s => s.Prices.LastOrDefault().Price));
EDIT: And then after that I did a Projection because If I use Mapper.Map() It will return the entire result set which is not what I want, I only want the values I needed. So I did something like this:
Project().To<ItemDTO>()
Well, basically, I want something like this:
from item in SomeDbContext.Items
where item.ItemId == 1
select new ItemDTO
{
Name = item.Name,
PricesPrice = item.Prices.LastOrDefault()
}
Can above code be done using AutoMapper?
I think I understand what you are trying to do now.
Try this
from item in SomeDbContext.Items
where item.ItemId == 1
select Mapper.Map<ItemDTO>(item)
or you could use LINQ
SomeDbContext.Items.Where(i=> i.ItemId == 1).Select(Mapper.Map<ItemDTO>)
Both of these will return you a list of ItemDTO object where the ItemId is 1

Filtering IEnumerable child property

I'm attempting to bring back a list of objects. This object has an IEnumerable property of a second class. I'm attempting to filter this child list based on a condition.
There are the classes:
public class Parent
{
public int Id { get; set; }
public string Title { get; set; }
public bool Active { get; set; }
public virtual IEnumerable<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Title { get; set; }
public int ParentId { get; set; }
public int OtherId { get; set; }
public bool Active { get; set; }
public virtual Parent Parent { get; set; }
}
Here is the EF code where I am attempting to get the parents and filter the children:
public IEnumerable<ParentViewModel> GetParents(int otherId)
{
var parents = _databaseContext.Parents
.Include(i => i.Children.Where(child => child.OtherId == otherId));
return parents;
}
When I call this method, I get an ArgumentException, with the message:
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.
Given that the exception mentions using Select, I've tried doing that too:
public IEnumerable<ParentViewModel> GetParents(int otherId)
{
var parents = _databaseContext.Parents
.Where(parent => parent.Active == true)
.Include(parent => parent.Children);
.Select(parent => new
{
Active = parent.Active,
Id = parent.Id,
Children = parent.Children
.Where(child => child.OtherId == propertyId)
.Select(child => new
{
Active = child.Active,
Id = child.Id,
ParentId = child.ParentId,
OtherId = child.OtherId,
Title = child.Title
},
Title = parent.Title
});
return parents;
}
This also blows up, giving me the exception:
The specified type member 'Children' is not supported in LINQ to
Entities. Only initializers, entity members, and entity navigation
properties are supported.
And that's where I'm all out of ideas! I don't know what I'm doing wrong, but this doesn't feel like it should be as hard as it has been, so I'm guessing that I'm missing something pretty fundamental to Entity Framework.
Ah, ok! I am not familiar with Code First (I am using a generated model in my Project) but I am pretty sure that EF doesn't recognize that these Entities are related. The error Message: " ... and entity navigation properties are supported" tells me that you should define a navigation property (a Relation between these Entities).
public class Parent
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public bool Active { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public int ParentId { get; set; }
public int OtherId { get; set; }
public bool Active { get; set; }
public int ParentId [get;set;}
[ForeignKey("ParentId")]
public virtual Parent Parent { get; set; }
}

Fluent NHibernate automappings with self-reference

I have a simple class that looks like this...
public class Item {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual int ParentId { get; set; }
public virtual IList<Item> Children { get; private set; }
public Item() {
Children = new List<Item>();
}
}
... where the Id is the primary key and ParentId is the foreign key. When I run this code I get Invalid object name 'ItemToItem'. exception and I can't figure out what's wrong? I seems like NHibernate tries to select from a table called ItemToItem or something like that?
Correct way to self reference
// Class
public class Item
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Item Parent { get; private set; }
public virtual IList<Item> Children { get; set; }
public Item() {
Children = new List<Item>();
}
}
// Map
References(x => x.Parent).Column("ParentId");
HasMany(x => x.Children).Cascade.All().KeyColumn("ParentId");
// Add Item
session.Save(new Item { Description = "Electronics",
Children = {
new Item { Description = "PS2" },
new Item { Description = "XBox" }
}});
// Get Item
var items =
(from c in session.Linq<Item>()
where c.Parent == null
select c).ToList();
Yes. fluent nhibernate is seeing this as a many-many relationship. I dont know off the top of my head how to create the type of relationship you want. you'll probably at least want to set up a member:
public virtual Item Parent{ get; set; }

Categories

Resources