This question already has answers here:
Projecting self referencing multi level Entities In Entity Framework 6
(2 answers)
Closed 5 years ago.
I got some help with my recursive product category tree view here on Stack Overflow before, and this is working:
Entity model:
public class ProductCategory
{
public int Id { get; set; }
public int SortOrder { get; set; }
public string Title { get; set; }
[ForeignKey(nameof(ParentCategory))]
public int? ParentId { get; set; }
public ProductCategory ParentCategory { get; set; } //nav.prop to parent
public ICollection<ProductCategory> Children { get; set; } //nav. prop to children
public List<ProductInCategory> ProductInCategory { get; set; }
}
Controller:
var categories = _context.ProductCategories.Include(e => e.Children).ToList();
var topLevelCategories = categories.Where(e => e.ParentId == null);
return View(topLevelCategories);
View:
#if (Model != null)
{
foreach (var item in Model)
{
<li>
#item.Title
<ul>
#Html.Partial("_CategoryRecursive.cshtml", item.Children)
</ul>
</li>
}
}
But when I tried to translate this setup to my viewmodel (and adding a property for counting products in each category, as well as a list of products without any category connection) ...:
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 string ProductCountInfo
{
get
{
return Products != null && Products.Any() ? Products.Count().ToString() : "0";
}
}
public ViewModelProductCategory ParentCategory { get; set; } // Nav.prop. to parent
public IEnumerable<ViewModelProductCategory> Children { get; set; } // Nav.prop. to children
public List<ViewModelProduct> Products { get; set; } // Products in this category
public List<ViewModelProduct> OrphanProducts { get; set; } // Products with no reference in ProductInCategory
}
Controller:
var VMCategories = _context.ProductCategories
.Include(e => e.Children)
.OrderBy(s => s.SortOrder)
.Where(r => r.ParentId == null) // only need root level categories in the View
.Select(v => new ViewModelProductCategory
{
Id = v.Id,
ParentId = v.ParentId,
Title = v.Title,
SortOrder = v.SortOrder,
// get products without a category:
OrphanProducts = v.ProductInCategory
.Where(o => !_context.ProductsInCategories.Any(pc => o.Id == pc.ProductId))
.Select(orph => new ViewModelProduct
{
Id = orph.Product.Id,
Title = orph.Product.Title,
Price = orph.Product.Price,
Info = orph.Product.Info,
SortOrder = orph.SortOrder
})
.OrderBy(s => s.SortOrder)
.ToList()
})
.ToList();
return View(VMCategories);
View:
#if (Model != null)
{
foreach (var item in Model)
{
<li>
#item.Title (#item.ProductCountInfo)
<ul>
#Html.Partial("_CategoryRecursive.cshtml", item.Children)
</ul>
</li>
}
}
... it won't work any more. The view does render, but it is just showing the root categories. It seems that my modified query won't get any of the children categories. When I inspect the query result, the Children property is null.
EDIT
I'm going with #Rainman's solution, and have changed my query .Select to include Children = v.Children,, and changing my viewmodel navigational properties thusly:
public ProductCategory ParentCategory { get; set; } //nav.prop to parent
public ICollection<ProductCategory> Children { get; set; } //nav. prop to children
I have also created the new viewmodel CategoryRecursiveModel and changed my view to this:
#model IEnumerable<MyStore.Models.ViewModels.ViewModelProductCategory>
<ul>
#if (Model != null)
{
foreach (var item in Model)
{
<li>
#item.Title (#item.ProductCountInfo)
<ul>
#Html.Partial("_CategoryRecursive.cshtml", new CategoryRecursiveModel
{
Children = item.Children.ToList();
})
</ul>
</li>
}
}
</ul>
Now I'm faced with InvalidOperationException, as the view is expecting an IEnumerable of ViewModelProductCategory, but receives CategoryRecursiveModel.
Because you are not selecting the Children for second query;
var VMCategories = _context.ProductCategories
.Include(e => e.Children)
.OrderBy(s => s.SortOrder)
.Where(r => r.ParentId == null) // only need root level categories in the View
.Select(v => new ViewModelProductCategory
{
Id = v.Id,
Children = v.Children, // Select it
ParentId = v.ParentId,
Title = v.Title,
SortOrder = v.SortOrder,
// get products without a category:
OrphanProducts = v.ProductInCategory
.Where(o => !_context.ProductsInCategories.Any(pc => o.Id == pc.ProductId))
.Select(orph => new ViewModelProduct
{
Id = orph.Product.Id,
Title = orph.Product.Title,
Price = orph.Product.Price,
Info = orph.Product.Info,
SortOrder = orph.SortOrder
})
.OrderBy(s => s.SortOrder)
.ToList()
})
.ToList();
public ProductCategory ParentCategory { get; set; } //nav.prop to parent
public ICollection<ProductCategory> Children { get; set; } //nav. prop to children
Also, navigation properties exist only for EF entities not ViewModelProductCategory model class or other classes.
EDIT
Create a model class for _CategoryRecursive view;
public class CategoryRecursiveModel
{
public List<ProductCategory> Children { get; set; }
}
And the change the main view;
#if (Model != null)
{
foreach (var item in Model)
{
<li>
#item.Title (#item.ProductCountInfo)
<ul>
#Html.Partial("_CategoryRecursive.cshtml", new CategoryRecursiveModel
{
Children = item.Children.ToList();
})
</ul>
</li>
}
}
Related
I have Albums and Images tables with a 1-many relationship. Models are automatically generated using EF database-first approach.
I want to get all albums with their images, and display only first image from each album on albums listing page. But I got confused, ICollection does not have indexes and I'm not able to convert it to a list inside view.
Sometimes it says it is Hashset
Album.cs
public partial class Album
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Album()
{
this.Images = new HashSet<Image>();
}
public int id { get; set; }
public string title { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Image> Images { get; set; }
}
Image.cs
public partial class Image
{
public int id { get; set; }
public int album_id { get; set; }
public string img { get; set; }
public string title { get; set; }
public virtual Album Album { get; set; }
}
HomeController.cs
ViewBag.Albums = db.Albums.Where(a => a.status == 1)
.Select(c => new
{
Album = c,
Image = c.Images.OrderBy(i => i.sort)
})
.AsEnumerable() // not execute yet
.Select(a => a.Album)
.OrderBy(a => a.sort)
.Take(6)
.ToList();
View.html
#if (ViewBag.Albums != null) {
foreach (var item in ViewBag.Albums)
{
<div class="album">
#{
string default_image = "no-img.png";
if (item.Images.Count > 0)
{
//var firstImg = item.Images.First();//this showing error that item.image dont have first()
//List<myproject.Models.Image> images = item.Images.ToList();//this also say hashset has no list()
//inside 1 albums many images can be returned, I want to display first one only.
default_image = item.id + "/" + item.Images[0].img;
}
int indx = 1;
}
</div>
}
}
firstly:
You must create a viewmodel
public class ImageAlbumView
{
public Image Image {get;set;}
public Album Album {get;set;}
}
HomeController.cs:
ViewBag.Albums = db.Albums.Where(a => a.status == 1)
.Select(c => new ImageAlbumView()
{
Album = c,
Image = c.Images.OrderBy(i => i.id)
})
.AsEnumerable() // not execute yet
.Select(a => a.Album)
.OrderBy(a => a.Image.id)
.Take(6)
.ToList();
View.html:
#if ((List<ImageAlbumView>)ViewBag.Albums != null) {
foreach (ImageAlbumViewitem in (List<ImageAlbumView>)ViewBag.Albums)
{
<div class="album">
#{
string default_image = "no-img.png";
if (item.Images.Count > 0)
{
default_image = item.id + "/" + item.Images[0].img;
}
int indx = 1;
}
</div>
}
}
I'm having trouble understanding how to retrieve and edit the DevId values from my CustomerDevice table in my database to the CheckBoxList based on the CustId value.
My Index Action Method for the CustomerDeviceController displays a list of Customers from my Customers table. I have an ActionLink labeled "Edit" that passes the CustId value to the CustomerDeviceController [HttpGet] Edit(int? id) Action Method which currently displays all CheckBoxListItem values from the Devices table. However, the CheckBoxList does not display the checked DevId values from the CustomerDevice table in the database to the CheckBoxList that pertain to the CustId, instead it displays a check for each of the CheckBoxList values.
The part that I'm having trouble understanding and figuring out, is how can I display the selected DevId values from the CustomerDevice table in my database to the CheckBoxList based on the CustId and then Edit/Update the modified CheckBoxListItems on the [HttpPost] Edit Action Method back to my CustomerDevice table in my database if need be.
Please see the following code below that I have so far.
Models
public class CheckBoxListItem
{
public int ID { get; set; }
public string Display { get; set; }
public bool IsChecked { get; set; }
}
public class Customer
{
public int CustId { get; set; }
public string CustDisplayName { get; set; }
public string CustFirstName { get; set; }
....
}
public class Device
{
public int DevId { get; set; }
public string DevType { get; set; }
}
public class CustomerDevice
{
public int CustId { get; set; }
public int DevId { get; set; }
public Customer Customer { get; set; }
public Device Device { get; set; }
}
ViewModels
public class CustomerDeviceFormViewModel
{
public int CustId { get; set; }
public string CustDisplayName { get; set; }
public List<CheckBoxListItem> Devices { get; set; }
}
CustomerDeviceController
public ActionResult Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var customervm = new CustomerDeviceFormViewModel();
Customer customer = db.Customers.SingleOrDefault(c => c.CustId == id);
if (customer == null)
{
return NotFound();
}
customervm.CustId = customer.CustId;
customervm.CustDisplayName = customer.CustDisplayName;
// Retrieves list of Devices for CheckBoxList
var deviceList = db.Devices.ToList();
var checkBoxListItems = new List<CheckBoxListItem>();
foreach (var device in deviceList)
{
checkBoxListItems.Add(new CheckBoxListItem()
{
ID = device.DevId,
Display = device.DevType,
IsChecked = deviceList.Where(x => x.DevId == device.DevId).Any()
});
}
customervm.Devices = checkBoxListItems;
return View(customervm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(CustomerDeviceFormViewModel vmEdit)
{
if (ModelState.IsValid)
{
Customer customer = db.Customers.SingleOrDefault(c => c.CustId == vmEdit.CustId);
if (customer == null)
{
return NotFound();
}
foreach (var deviceId in vmEdit.Devices.Where(x => x.IsChecked).Select(x => x.ID))
{
var customerDevices = new CustomerDevice
{
CustId = vmEdit.CustId,
DevId = deviceId
};
db.Entry(customerDevices).State = EntityState.Modified;
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vmEdit);
}
Edit.chtml
<div class="form-group">
Please select the Devices to assign to <b>#Html.DisplayFor(c => c.CustDisplayName)</b>
</div>
<div class="form-group">
#Html.EditorFor(x => x.Devices)
</div>
#Html.HiddenFor(c => c.CustId)
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
Shared/EditorTemplate/CheckBoxListItem.chtml
<div class="checkbox">
<label>
#Html.HiddenFor(x => x.ID)
#Html.CheckBoxFor(x => x.IsChecked)
#Html.LabelFor(x => x.IsChecked, Model.Display)
</label>
<br />
Your code for setting the IsChecked value will always return true (your loop is basically say if the collection contains me (which of course it does) then set it to true).
You need to get the selected values for each Customer by reading the values from your CustomerDevice table
Customer customer = db.Customers.SingleOrDefault(c => c.CustId == id);
if (customer == null)
{
return NotFound();
}
// Get all devices
var deviceList = db.Devices.ToList();
// Get the selected device ID's for the customer
IEnumerable<int> selectedDevices = db.CustomerDevices
.Where(x => x.CustId == id).Select(x => x.DevId);
// Build view model
var model = new CustomerDeviceFormViewModel()
{
CustId = customer.CustId,
CustDisplayName = customer.CustDisplayName,
Devices = deviceList.Select(x => new CheckBoxListItem()
{
ID = x.DevId,
Display = x.DevType,
IsChecked = selectedDevices.Contains(x.DevId)
}).ToList()
};
return View(model);
Here's a snippet of Razor code that I've used:
foreach (SelectListItem p in Model.PositionList)
{
#Html.Raw(p.Text + "<input type=checkbox name=\"PositionIDs\" id=\"PositionIDs\" value=" + #p.Value + (Model.Positions != null && Model.Positions.Any(pos => pos.ScoreCardId == Convert.ToInt32(p.Value)) ? " checked />" : " />"));
}
You might want to have a look at the MvcCheckBoxList NuGet package:
https://www.nuget.org/packages/MvcCheckBoxList/
This makes doing some powerful stuff with a CheckBoxList much easier in MVC - and may be a better approach to fixing your CheckBox issues.
I have categories table which contains :
public partial class C_Categories
{
public int CatId { get; set; }
public Nullable<int> ParentId { get; set; }
public string ImageUrl { get; set; }
public virtual ICollection<C_Node> C_Node { get; set; }
}
And i have node table which contains :
public partial class C_Node
{
public int NodeId{ get; set; }
public Nullable<int> CatId { get; set; }
public System.DateTime PostDate { get; set; }
public virtual C_Categories C_Categories { get; set; }
}
And my controller :
public ActionResult Index(int? catId)
{
IQueryable<C_Node> moduleItems = db.C_Node;
if (catId != null)
{
//here i want to check if category is parent , get all node related to his child categories
moduleItems = moduleItems.Where(x => x.CatId == catId);
}
return View(moduleItems.ToList());
}
At my controller i want to check if category is parent , get all node table related to his child categories,
I tried to use any , but it failed .
to explain my question : i have category : electronics and electronics have childs computers, mobiles . i have products on node table under computers and mobiles , if catId is electronics i want all products under its childs computers, mobiles
You first need to find all the categories under the parent; if there are only 2 levels this is simple:
...
if (catId != null)
{
// Find the child categories for which this is the parent
var childCatIds = db.C_Categories
.Where(cat => cat.ParentId == catId)
.Select(cat => cat.CatId)
.ToList();
if (childCatIds.Count == 0)
// Not a parent category: Just find the items for the category as before
moduleItems = moduleItems.Where(x => x.CatId == catId);
else
// Parent category: Find the items for the child categories
moduleItems = moduleItems.Where(x => childCatIds.Contains(x.CatId));
}
If there are more than 2 levels, you will need to find the child ids recursively.
private List<int> GetChildCatIds(List<int> parentCatIds)
{
var childCatIds = db.C_Categories
.Where(cat => cat.ParentId.HasValue && parentCatIds.Contains(cat.ParentId.Value))
.Select(cat => cat.CatId)
.ToList();
if (childCatIds.Count == 0)
// Reached the end of the tree: no more children
return parentCatIds;
else
// Recursive call to find the next child level:
return GetChildCatIds(childCatIds);
}
...
if (catId != null)
{
var childCatIds = GetChildCatIds(new List<int>{catId.Value});
moduleItems = moduleItems.Where(x => childCatIds.Contains(x.CatId));
}
How about this:
moduleItems = dbcontext.C_Nodes.Where(n => n.CatId == catId);
I have two tables in Database:
PostCalculationLine
PostCaluclationLineProduct
PostCalculationLineProduct(table2) contains Foriegn key of PostCalucationLineId(table1)
In C# code I have two different Models for these two tables as follows:
public class PostCalculationLine : BaseModel
{
public long Id{ get; set; }
public string Position { get; set; }
public virtual Order Order { get; set; }
public virtual Task Task { get; set; }
//some other properties go here
public virtual IList<PostCalculationLineProduct> PostCalculationLineProducts { get; set; }
}
and
public class PostCalculationLineProduct : BaseModel
{
public long Id {get;set;}
public string Description { get; set; }
//some other properties go here
}
Now in Entityframework code, I fetch data from PostCalculationLineProduct as follows:
PostCalculationLineRepository pclr = new PostCalculationLineRepository();
DataSourceResult dsrResult = pclr.Get()
.SelectMany(p => p.PostCalculationLineProducts)
.Where(c => c.Product.ProductType.Id == 1 && c.DeletedOn == null)
.Select(c => new HourGridViewModel()
{
Id = c.Id,
Date = c.From,
EmployeeName = c.Employee != null ?c.Employee.Name:string.Empty,
Description= c.Description,
ProductName = c.Product != null?c.Product.Name :string.Empty,
From = c.From,
To = c.Till,
Quantity = c.Amount,
LinkedTo = "OrderName",
Customer ="Customer"
PostCalculationLineId = ____________
})
.ToDataSourceResult(request);
In the above query I want to get PostCalculationLineId(from Table1) marked with underLine. How can I achieve this?
Thanks
You can use this overload of SelectMany to achieve this:-
DataSourceResult dsrResult = pclr.Get()
.SelectMany(p => p.PostCalculationLineProducts,
(PostCalculationLineProductObj,PostCalculationLineObj) =>
new { PostCalculationLineProductObj,PostCalculationLineObj })
.Where(c => c.PostCalculationLineProductObj.Product.ProductType.Id == 1
&& c.PostCalculationLineProductObj.DeletedOn == null)
.Select(c => new HourGridViewModel()
{
Id = c.PostCalculationLineProductObj.Id,
Date = c.PostCalculationLineProductObj.From,
//Other Columns here
PostCalculationLineId = c.PostCalculationLineObj.Id
};
This will flatten the PostCalculationLineProducts list and returns the flattened list combined with each PostCalculationLine element.
I'm trying to pass few ViewModels to the same View via ViewData. Unfortunately I'm new to MVC and I do not have idea what's wrong with that.
Here is first DataViewModel:
public class TagsViewModel
{
public string TagName { get; set; }
public int TagId { get; set; }
}
And another one:
public class ShortPostViewModel
{
public int PostId { get; set; }
public string PostSubject { get; set; }
public DateTime? PostCreated { get; set; }
public string PostImage { get; set; }
public string PostAuthor { get; set; }
public byte? PostRating { get; set; }
public List<PostTagsViewModel> PostedTags { get; set; }
}
Here is repository:
public IEnumerable<BlogPostViewModel.ShortPostViewModel> GetLast20()
{
var last = from a in _db.blog_post
orderby a.Posted descending
select new BlogPostViewModel.ShortPostViewModel
{
PostId = a.ID,
PostAuthor = (from u in _db.users where u.ID == a.Author
select u.Login).FirstOrDefault(),
PostCreated = a.Posted,
PostImage = a.PostAvatar,
PostRating = a.Rating,
PostSubject = a.Subject,
PostedTags = (from b in _db.tags
join c in _db.posted_tags on b.ID equals c.TagID
where c.PostID == a.ID
select new PostTagsViewModel
{
TagId = b.ID,
TagName = b.TagName
}).ToList()
};
return last.Take(20);
}
And one more:
public IEnumerable<TagsViewModel> GetAll()
{
var t = from a in _db.tags
select new TagsViewModel
{
TagId = a.ID,
TagName = a.TagName
};
return t;
}
So here is Controller:
public ActionResult Index()
{
ViewData["ShortPost"] = _postRepository.GetLast20().AsEnumerable();
ViewData["Tags"] = _tagsRepository.GetAll().AsEnumerable();
return View();
}
So on the View:
<ul class="list-group">
#foreach (var item in (IEnumerable<ShortPostViewModel>)ViewData["ShortPost"])
{
<li class="list-group-item">
<img src="#item.PostImage" alt=""/>
<h3>#Html.ActionLink(#item.PostSubject, "Details", "BlogPost", new { id = item.PostId }, null)</h3>
Создано: #item.PostCreated. Автор: #item.PostAuthor. Оценка: #item.PostRating.
<p>
Темы статьи:
#foreach (var tag in #item.PostedTags)
{
<i class="glyphicon glyphicon-tag"></i> #Html.ActionLink(#tag.TagName, "Tag", "Search", new { id = tag.TagId }, null)
}
</p>
</li>
}
</ul>
</div>
<div class="col-md-4">
#foreach (var tag in (IEnumerable<TagsViewModel>)ViewData["Tags"])
{
<span class="label label-info"><i class="glyphicon glyphicon-tag"></i> #Html.ActionLink(#tag.TagName, "Tag", "Search", new { id = tag.TagId }, null)</span>
}
</div>
This all look just fine for me. Could you advise how should I fix that?
Instead of using several ViewData, I would recommend using a new ViewModel class that have a List<TagsViewModel> property and a List<ShortPostViewModel> property so you don't have to do the conversions in the view. Let's say the ViewModel is named CustomViewModel
public class CustomViewModel
{
public CustomViewModel()
{
this.ShortPosts = new List<ShortPostViewModel>();
this.Tags = new List<TagsViewModel>();
}
public List<ShortPostViewModel> ShortPosts { get; set; }
public List<TagsViewModel> Tags { get; set; }
}
then in your controller
public ActionResult Index()
{
CustomViewModel model = new CustomViewModel();
model.ShortPosts = _postRepository.GetLast20().ToList();
model.Tags = _tagsRepository.GetAll().ToList();
return View(model);
}
Make sure you have this at the top of your view code
#model CustomViewModel
You can enumerate the items of ShortPosts in your view as below
#foreach (var item in Model.ShortPosts)
and enumerate the items of Tags as below
#foreach (var tag in Model.Tags)