How can i select all levels of a self-referencing table as a view model. if max level was 2 or 3 then i can do that by calling Select multiple times but i have 4-5 level menus and i think there should be a better solution for doing that and select all levels.
this is my viewmodel:
public class MenuViewModel
{
public MenuViewModel()
{
Childs = new HashSet<MenuViewModel>();
}
public int Id{ get; set; }
public string Title { get; set; }
public string Url { get; set; }
public ICollection<MenuViewModel> Childs { get; set; }
}
and this is my Menu class:
public class Menu
{
public Menu()
{
Childs = new HashSet<Menu>();
}
public int Id{ get; set; }
public string Title { get; set; }
public string Url { get; set; }
public string Description { get; se; }
public byte[] Icon { get; set; }
public int Order { get; set; }
public ICollection<Menu> Childs { get; set; }
}
var viewModel = _dataContext.Menus
.Select(x => new MenuViewModel
{
Id = x.Id,
Title = x.Title,
Child = ???
}
.ToList();
When you are using EF , you can do like following way:
public class BlogComment
{
public int Id { set; get; }
[MaxLength]
public string Body { set; get; }
public virtual BlogComment Reply { set; get; }
public int? ReplyId { get; set; }
public ICollection<BlogComment> Children { get; set; }
}
using (var ctx = new MyContext())
{
var list = ctx.BlogComments
//.where ...
.ToList() // fills the childs list too
.Where(x => x.Reply == null) // for TreeViewHelper
.ToList();
}
with this way you don't need to use recursive queries but As far as I know,when use view model for fetch data , the dynamic proxy of EF Is destroyed.
about above example:
just select one list of comments and with
.Where(x=>x.Reply==null).Tolist()
EF fill children property of Comments.
Reference
Assuming that Id property is unique you can do it in two passes:
Create viewmodel items without children, but with associated children ids. From that data create the Dictionary that will allow you to get any viewmodel by its id. Values in this dictionary will be the created viewmodels alongside their children ids.
For each viewmodel item get the associated view model items using the children ids.
Something like:
var tempModels = _dataContext
.Menus
.Select(menu => new
{
childrenIds = menu.Childs.Select(item => item.Id).ToArray(),
viewModel =
new MenuViewModel
{
Id = menu.Id,
Title = menu.Title
}
})
.ToDictionary(
keySelector: item => item.viewModel.Id);
var viewModels = tempModels
.Select(kv =>
{
var viewModel = kv.Value.viewModel;
viewModel.Childs = kv
.Value
.childrenIds
.Select(childId =>
tempModels[childId].viewModel)
.ToList();
return viewModel;
})
.ToList();
for depth problem you can use one int property like Depth in your Model then you can fetch data like this :
public class BlogComment
{
public int Id { set; get; }
[MaxLength]
public string Body { set; get; }
public int Depth{get;set}
public virtual BlogComment Reply { set; get; }
public int? ReplyId { get; set; }
public ICollection<BlogComment> Children { get; set; }
}
using (var ctx = new MyContext())
{
var list = ctx.BlogComments
.Where(a=>a.Depth<2)
.ToList() // fills the childs list too
.Where(x => x.Reply == null) // for TreeViewHelper
.ToList();
}
for using viewModel in this senario , I Test with AutoMapper,but when select data with viewModel , the dyamic proxy that EF generate is Destroyed .
Please Note this Issue
Related
I got 3 models: Human, Skill and HumanSkill. There is a many to many relationship between Human and Skill, the HumanSkill is the intermediary table between them.
My query to the database loads the collection of the intermediary table HumanSkill correctly, but does not load the reference navigation property Skill through which I want to load the Skill name (Human -> HumanSkill -> Skill -> Skill.name) using a query projection with select.
public IActionResult Preview(int humanId)
{
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
PrimaryData = r.PrimaryData,
// How should I write this line?
Skills = r.Skills.ToList(),
}).SingleOrDefault();
return View(currentResume);
}
Human model:
public class Human
{
public Human()
{
this.Skills = new HashSet<HumanSkill>();
}
public int Id { get; set; }
public virtual PrimaryData PrimaryData { get; set; }
public virtual ICollection<HumanSkill> Skills { get; set; }
}
HumanSkill model:
public class HumanSkill
{
public int Id { get; set; }
public int HumanId { get; set; }
public Human Human { get; set; }
public int SkillId { get; set; }
public Skill Skill { get; set; }
}
Skill model:
public class Skill
{
public Skill()
{
this.Humans = new HashSet<HumanSkill>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HumanSkill> Humans { get; set; }
}
HumanPreviewViewModel:
public class HumanPreviewViewModel
{
public HumanPreviewViewModel()
{
}
public PrimaryData PrimaryData { get; set; }
public List<HumanSkill> Skills { get; set; }
}
}
How can I achieve this without using include?
If you use some data from Skills table in the Select, EF will perform the necessary joins to retrieve the data
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
PrimaryData = r.PrimaryData,
SkillNames = r.Skills.Select(hs => hs.Skill.Name).ToList(),
}).SingleOrDefault();
When projecting from entity to a view model, avoid mixing them. For example, do not have a view model contain a reference or set of entities. While it might not seem necessary, if you want a list of the skills with their ID and name in the HumanPreviewViewModel then create a serialize-able view model for the skill as well as the PrimaryData if that is another related entity. Where PrimaryData might be a one-to-one or a many-to-one the desired properties from this relation can be "flattened" into the view model.
[Serializable]
public class HumanPreviewViewModel
{
public Id { get; set; }
public string DataPoint1 { get; set; }
public string DataPoint2 { get; set; }
public List<SkillViewModel> Skills { get; set; }
}
[Serializable]
public class SkillViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
Then when you go to extract your Humans:
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
Id = r.Id,
DataPoint1 = r.PrimaryData.DataPoint1,
DataPoint2 = r.PrimaryData.DataPoint2,
Skills = r.Skills.Select(s => new SkillViewModel
{
Id = s.Skill.Id,
Name = s.Skill.Name
}).ToList()
}).SingleOrDefault();
The reason you don't mix view models and entities even if they share all of the desired fields is that your entities will typically contain references to more entities. When something like your view model gets sent to a Serializer such as to send to a client from an API or due to a page calling something an innocent looking as:
var model = #Html.Raw(Json.Encode(Model));
then the serializer can, and will touch navigation properties in your referenced entities which will trigger numerous lazy load calls.
I have 3 entities for main menus. and sub menus. I created menu view model.
My Entities:
MainMenu.cs:
public UstMenuler()
{
AltMenuler = new HashSet<AltMenuler>();
}
public int ID { get; set; }
public string Name { get; set; }
public bool AktifMi { get; set; }
public int YetkiID { get; set; }
public string ControllerName { get; set; }
public string ActionName { get; set; }
public virtual Yetkilendirme Yetkilendirme { get; set; }
public virtual ICollection<AltMenuler> AltMenuler { get; set; }
SubMenu.cs:
public partial class AltMenuler
{
public AltMenuler()
{
UstMenuler = new HashSet<UstMenuler>();
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ID { get; set; }
public int? YetkiID { get; set; }
public string Name { get; set; }
public string ActionName { get; set; }
public string ControllerName { get; set; }
public virtual Yetkilendirme Yetkilendirme { get; set; }
}
MyViewModel:
public class MenuViewModel
{
public UstMenuler UstMenuler { get; set; }
public AltMenuler AltMenuler { get; set; }
}
My Controller:
dbContext = new MarkettingDbContext();
int sessionYetkiID = (int)Session["yetkiID"];
MenuViewModel menuler = new MenuViewModel();
var query = (from menu in dbContext.UstMenuler
where (menu.YetkiID == sessionYetkiID)
select new MenuViewModel
{
menuler.UstMenuler.ID = menu.ID,
menuler.UstMenuler.Name = menu.Name,
menuler.UstMenuler.YetkiID = menu.YetkiID,
menuler.UstMenuler.ControllerName = menu.ControllerName,
menuler.UstMenuler.ActionName = menu.ActionName
}).ToList();
I am getting error when I execute my query. For example my sessionID is 1, I want to take my main and submenus.
The problem is your object initializer:
select new MenuViewModel
{
menuler.UstMenuler.ID = menu.ID,
menuler.UstMenuler.Name = menu.Name,
menuler.UstMenuler.YetkiID = menu.YetkiID,
menuler.UstMenuler.ControllerName = menu.ControllerName,
menuler.UstMenuler.ActionName = menu.ActionName
}
Firstly you're using menuler in the object initializer, and I'm not sure why. Next, you're trying to assign to subproperties, which even if it did compile, would result in a NullReferenceException. Collectively, that's basically not what you want to do :)
Instead, I'd use one of the two options below.
If you're happy to just copy the UstMenuler reference from the menu parameter into the new view model instance, you can do that really easily:
select new MenuViewModel { UseMenuler = menu }
Or if you want to create a new UstMenuler and copy properties from menu:
select new MenuViewModel
{
UstMenuler = new UstMenuler
{
ID = menu.ID,
Name = menu.Name,
YetkiID = menu.YetkiID,
ControllerName = menu.ControllerName,
ActionName = menu.ActionName
}
}
Either way, you don't need your menuler variable which you appear not to be using anywhere else.
As it sounds like you're using LINQ to Entities, it sounds like you may want to separate the database query part from the projection part. The simplest way to do that in your case is to avoid the query expression syntax, so you can call AsEnumerable easily. So for example:
var query = dbContext.UstMenuler
.Where(menu => menu.YetkiID == sessionYetkiID)
.AsEnumerable() // Do the projection in-process
.Select(menu => new MenuViewModel
{
UstMenuler = new UstMenuler
{
ID = menu.ID,
Name = menu.Name,
YetkiID = menu.YetkiID,
ControllerName = menu.ControllerName,
ActionName = menu.ActionName
}
})
.ToList();
I have this model:
public class ProductCategory
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Title { get; set; }
public int SortOrder { get; set; }
public ICollection<ProductCategory> Children { get; set; } = new List<ProductCategory>();
}
This is the query I'm using to load the data into a recursive <ul>:
List<ProductCategory> DbCategories = _context.ProductCategories
.ToList().OrderBy(o => o.SortOrder)
.Where(e => e.ParentId == null).ToList();
That query only apply sorting to the root categories, so I'm adding this to at least sort one generation of children:
DbCategories.ForEach(cat => cat.Children = cat.Children.OrderBy(c => c.SortOrder).ToList());
Now, the solution to this question suggests adding a method to the class, like this:
public class ProductCategory
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Title { get; set; }
public int SortOrder { get; set; }
public ICollection<ProductCategory> Children { get; set; } = new List<ProductCategory>();
public void RecursiveOrder()
{
Children = Children.OrderBy(x => x.SortOrder).ToList();
Children.ToList().ForEach(c => c.RecursiveOrder());
}
}
But I'm a bit lost - how would I call such a method?
Edit And one more thing; Should the RecursiveOrder()-method be in the entity model or the viewmodel?
You can simply replace the content of your DbCategories.ForEach() call like this:
DbCategories.ForEach(cat => cat.RecursiveOrder());
You call RecursiveOrder() on first-level children, and it will handle recursively the nested children.
Besides, since it seems that the purpose of this ordering is for UI display, it should be more convenient to put it in the viewmodel (it needs probably to be refactored a little bit in this case)
I have this scenario:
I want to make a ViewModel with the property that I only want but my issue is with the Collections. So here's an example:
Main classes:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Order> Orders { get; set; }
// remove some code for brevity
}
public class Order
{
public int Id { get; set; }
public string ItemName { get; set; }
public int CustomerId { get; set; }
public virtual Customer Customer { get; set; }
// remove some code for brevity.
}
View Models:
public class OrderVM
{
public string ItemName { get; set; }
}
public class CustomerVM
{
public string Name { get; set; }
public ICollection<OrderVM> Orders { get; set; }
}
Then I tried something like this:
_customerService.Include(c => c.Orders)
.Select(x => new CustomerVM
{
Name = x.Name,
Orders = x.Orders.Select(order => new OrderVM { ItemName = order.ItemName }) // but it says cannot convert implicitly from IEnumerable to ICollection
}
)
In a nutshell, how can I populate CustomerVM's properties? I only want to select that I want. Any thoughts? Really stuck here.
Linq .Select() generates IEnumerable<T> but your property is ICollection<T>. You can append .ToList() to the query to generate List<T> which is ICollection<T>.
customerService.Include(c => c.Orders)
.Select(x => new CustomerVM
{
Name = x.Name,
Orders = x.Orders
.Select(order => new OrderVM { ItemName = order.ItemName }).ToList()
}
Alternatively, if you do not specifically need Orders to be ICollection, then you could change your property definition to
public IEnumerable<OrderVM> Orders { get; set; }
just use ToList() to convert IEnumerable to ICollection. Not tested but it should work
customerService.Include(c => c.Orders)
.Select(x => new CustomerVM
{
Name = x.Name,
Orders = x.Orders.Select(order => new OrderVM { ItemName = order.ItemName }).ToList()
}
I am developing a MVC Project with Entity framework and i have a category table like this :
public partial class Categories
{
public Categories()
{
this.Categories1 = new HashSet<Categories>();
}
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public Nullable<int> RelatedCategoryId { get; set; }
public virtual ICollection<Categories> Categories1 { get; set; } //Children
public virtual Categories Categories2 { get; set; } //Parent
}
When i get table data with EF, it gives me the object i want. Parents with children.
class Program
{
static Entities db = new Entities();
static void Main(string[] args)
{
List<Categories> categories = db.Categories.Where(item => item.RelatedId == null).ToList();
}
}
With relatedId == null part, i get the main categories which has no parent.
There is no problem this far. But i want to cast categories object which ef returned to another class which is :
public class NewCategories
{
public int Id { get; set; }
public string Name { get; set; }
private List<NewCategories> _subCategories;
public NewCategories()
{
_subCategories= new List<NewCategories>();
}
public List<NewCategories> SubCategories { get { return _subCategories; } }
}
And i want new List<NewCategories> newCategories object.
How can i accomplish that?
Thanks.
I think you have to create a recursive method to convert Categories to NewCategories, something like this (I'm not sure if it works, but it's worth trying):
public NewCategories ConvertToNewCategories(Categories cat){
NewCategories nc = new NewCategories {Id = cat.CategoryId, Name = cat.CategoryName};
nc.SubCategories.AddRange(cat.Categories1.Select(c=>ConvertToNewCategories(c)));
return nc;
}
//Then
List<NewCategories> categories = db.Categories.Where(item => item.RelatedId == null)
.Select(item=>ConvertToNewCategories(item))
.ToList();