Loading parent and child nodes from remote data - asp.net-mvc

I've got a method in my controller that returns a List<TreeViewItemModel>() that I'm populating with the correct hierarchy. This seems to serialize correctly, but when I load the Treeview, I don't have any of the hierarchy, just the first level of nodes.
Example:
Each of the above Curricula has 2/3 scenarios underneath that I've verified are getting added as items to the base object when going from curriculum => TreeViewItemModel
Controller:
public JsonResult GetAvailableCurricula(string LocationId)
{
LocationId = "1";
if(LocationId != string.Empty)
{
var results = Logic.GetFilteredCurriculum().Select(c => CurriculumToTreeView(c));
return Json(results, JsonRequestBehavior.AllowGet);
}
else
{
return Json(new List<TreeViewItemModel>(),JsonRequestBehavior.AllowGet);
}
}
private TreeViewItemModel CurriculumToTreeView(CurriculumModel c)
{
var tree = new TreeViewItemModel()
{
Id = c.CurriculumId.ToString(),
Text = c.CurriculumName,
HasChildren = c.Scenarios.Any()
};
if (tree.HasChildren)
{
tree.Items = c.Scenarios.Select(scenario =>
new TreeViewItemModel()
{
Text = scenario.Name,
}
).ToList();
}
return tree;
}
View:
#(Html.Kendo().TreeView()
.Name("AvailableCurricula")
.DataTextField("Text")
.DataSource(source => source
.Read(read => read
.Action("GetAvailableCurricula", "TraineeAssignments")
.Data("filterAvailableCurricula")
)
)
Is there some extra step I need to take to bind the child objects AND the parent, instead of just one level at a time? I have a fairly small set of data that I don't need to reload all that often, so I would was hoping to avoid loading each level individually/on-demand.
In case it's helpful, here's the raw JSON I'm sending from the controller for one of my curricula:
{"Enabled":true,"Expanded":false,"Encoded":true,"Selected":false,"Text":"Operator B","SpriteCssClass":null,"Id":"1","Url":null,"ImageUrl":null,"HasChildren":true,"Checked":false,"Items":[{"Enabled":true,"Expanded":false,"Encoded":true,"Selected":false,"Text":"test 2","SpriteCssClass":null,"Id":null,"Url":null,"ImageUrl":null,"HasChildren":false,"Checked":false,"Items":[],"HtmlAttributes":{},"ImageHtmlAttributes":{},"LinkHtmlAttributes":{}},{"Enabled":true,"Expanded":false,"Encoded":true,"Selected":false,"Text":"Scenario II","SpriteCssClass":null,"Id":null,"Url":null,"ImageUrl":null,"HasChildren":false,"Checked":false,"Items":[],"HtmlAttributes":{},"ImageHtmlAttributes":{},"LinkHtmlAttributes":{}}],"HtmlAttributes":{},"ImageHtmlAttributes":{},"LinkHtmlAttributes":{}}

I believe that the remote data option for the Treeview uses ajax to load the child data, either on demand or at initial load - controlled by the LoadOnDemand option.
The remote data examples in the kendo docs behave that way. That is how I implemented it on a previous project. The full code example includes a treeview as well as a grid.

Related

Relational data not loading even LazyLoading set false

I am trying to get a List of Main category and Sub Category. But in debug i can see its loading only first level Main category but not loading sub category which is relational database. I have attached .edmx picture to make you sure that relational database is configured properly. LazyLoading false also not works! Any idea?
[ChildActionOnly]
public PartialViewResult _GuestNav()
{
using (var db = new TestWebDbEntities())
{
db.Configuration.LazyLoadingEnabled = false;
var Cat = db.Categories.ToList();
return PartialView("_GuestNav", Cat);
}
}
Lazy loading prevents child entities from being loaded, you need to explicitly tell Entity Framework what you want it to do. For this you use the Include method:
var Cat = db.Categories
.Include(c => c.SubCategories)
.Include(c => c.Products)
.ToList();

Creating pager for collection of anonymous type using PagedList.Mvc in ASP.NET MVC

I am developing an ASP.NET MVC project. In my project I need to implement pagination feature. So I use PagedList.Mvc for it. I installed it running this command,"install-package PagedList.Mvc" in package manager console. I know how to implement pagination using it. But I am having a problem with implementing for collection of anonymous type view.
This is my action method implementing pagination logic
public ActionResult IpList(int page = 1)
{
List<IpVisitInfo> ipVisitList = new List<IpVisitInfo>();
var rawVisits = trackTool.Visitors.GroupBy(x => x.IpAddress).Select(x => new { Visitors = x.Count(), IpAddress = x.Key, LastVisitedTime = x.OrderByDescending(c => c.Id).FirstOrDefault().VisitedAt }).ToPagedList(page,PageSize);
if(rawVisits!=null && rawVisits.Count()>0)
{
foreach(var visit in rawVisits)
{
IpVisitInfo info = new IpVisitInfo
{
IpAddress = visit.IpAddress,
LastVisitedAt = visit.LastVisitedTime,
TotalVisitors = visit.Visitors
};
ipVisitList.Add(info);
}
}
IpVisitListVM model = new IpVisitListVM
{
IpList = ipVisitList
};
return View(model);
}
As you can see I retrieved anonymous type collection and then loop through it and assign to strongly typed collection. I called to ToPagedList(PageNumber,PageSize) on anonymous type collection. But the problem is all the data required to implement pagination in view is in anonymous type collection. In view pagination has to be rendered something like this below.
#Html.PagedListPager(Model.Items, page => Url.Action("List", new { page }))
This is my view model
public class IpVisitListVM
{
public IEnumerable<IpVisitInfo> IpList { get; set; }
}
So according to what I am doing, IpList of my view model does not have required data to render pagination. If I retrieve all anonymous collection fist without using ToPagedList() on it, then loop through each record, performance will be very slow, because it is retrieving all records first from database. I will have so many records. How can I use PagedList on anonymous typed view and pass to strongly typed view to implement pagination?
In view model I am using this property
IEnumerable<IpVisitInfo> IpList
Instead of this
IPagedList<IpVisitInfo> IpList
because it does not have enough data to create pagination. Because I filtered in action.
But if I can pass following as list
trackTool.Visitors.GroupBy(x => x.IpAddress).Select(x => new { Visitors = x.Count(), IpAddress = x.Key, LastVisitedTime = x.OrderByDescending(c => c.Id).FirstOrDefault().VisitedAt }).ToPagedList(page,PageSize);
pagination is perfectly created and performance will be better. Is there a way to pass that list?

Select from a list passed to view in .NET MVC

Before I had this:
public ActionResult ShowSong(int id)
{
return view(db.PublishedSongs.Where(x => x.Id == id).FirstOrDefault());
}
and in my view:
#model MusicSite.Models.PublishedSong
<h2>#Model.SongName</h2>
#Html.ImageFor(x => x.CoverImageBytes, #Model.SongName + " Image", new { #class = "big-cover-image" })
This worked fine for just retrieving one item from DB and showing on view.
However now, I need to also pass a list to my view, because I want to show all of the items in model and just the select item that was passed through action method, how do I do this?
If I pass return View(db.PublishedSongs); to view then I no longer know which id to get to show just that one item.
As I am not that much good in .NET MVC but I will try to answer according to spring MVC knowledge.
The rough idea is to pass list to view as you are mentioned and along with it pass the id that you want to select using ViewBag. Now in view iterate over the list, while iteration check whether id come from iteration is equal to id that you passed in ViewBag.
So in controller :
public ActionResult ShowSong(int id)
{
ViewBag.SelectId = id;
return view(db.PublishedSongs);
}
In View :
(Loop over the db.PublishedSongs)
{
if(PublishedSong.id=#ViewBag.SelectId)
{
//select current PublishedSong
}
else
{
//otherwise
}
}

How do I gracefully reattach an Entity Framework 5 POCO structure and save it?

I am making an MVC4 web application using Entity Framework 5 (Database-first with generated POCOs) for data access.
In the app, the user goes through several screens, creating or editing a document (called a 'case study'). When they arrive at the final screen, their document exists as a CaseStudy POCO in memory, and everything is great until it is time to save this structure to the database.
To store the document, I have defined several database tables, which in turn map to EF POCOs used by the business layer, which is then consumed by the MVC controllers. As such, short-lived DbContexts are used to retrieve POCOs and store them in session between requests.
As a result, the save screen must save the contents of this POCO that has navigational properties to existing table data (Category, Layout, and Sections tables), and also added or updated data (CaseStudySections and the CaseStudy itself). So all of the POCOs are either new, or the context used to retrieve them has long been disposed. In other words, they are all 'detached'.
What is unusual about this post is that I already have a working solution in hand. The problem is that it is bulky, brittle, and inelegant. I am posting the code below. Note the iteration through sub-collections, the explicit adds and attaches, having to get an entry object and mark individual properties as modified just so they will be updated, and the awful song and dance at the end to get the AdditionalMaterials collection synced up. If this is what is required to deal with detached POCOs in EF5 I will be disappointed.
Am I missing something here? Is this consistent with best practices? Is there a more graceful and/or concise way to attach a structure of POCOs and insert/update?
The code to save a case study:
public void SaveCaseStudy(CaseStudy caseStudy)
{
foreach (var s in caseStudy.CaseStudySections)
{
this.Entities.Sections.Attach(s.Section);
if (s.CreatedByRefId == default(Guid))
{
s.CreatedByRefId = this.UserRefId;
s.CreatedTime = DateTime.Now;
this.Entities.CaseStudySections.Add(s);
}
else
{
this.Entities.CaseStudySections.Attach(s);
var entry = this.Entities.Entry(s);
entry.Property(e => e.TextData).IsModified = true;
entry.Property(e => e.BinaryData).IsModified = true;
}
s.LastModifiedByRefId = this.UserRefId;
s.LastModifiedTime = DateTime.Now;
}
foreach (var m in caseStudy.AdditionalMaterials)
{
if (m.CreatedByRefId == default(Guid))
{
m.CreatedByRefId = this.UserRefId;
m.CreatedTime = DateTime.Now;
this.Entities.AdditionalMaterials.Add(m);
}
else
{
this.Entities.AdditionalMaterials.Attach(m);
}
m.LastModifiedByRefId = this.UserRefId;
m.LastModifiedByTime = DateTime.Now;
}
this.Entities.Layouts.Attach(caseStudy.Layout);
this.Entities.Categories.Attach(caseStudy.Category);
if (caseStudy.CreatedByRefId != default(Guid))
{
this.Entities.CaseStudies.Attach(caseStudy);
var entry = this.Entities.Entry(caseStudy);
entry.Property(e => e.CaseStudyName).IsModified = true;
entry.Property(e => e.CaseStudyTitle).IsModified = true;
}
else
{
this.Entities.CaseStudies.Add(caseStudy);
caseStudy.CreatedByRefId = this.UserRefId;
caseStudy.CreatedTime = DateTime.Now;
}
caseStudy.LastModifiedByRefId = this.UserRefId;
caseStudy.LastModifiedTime = DateTime.Now;
if (caseStudy.CaseStudyStatus != (int)CaseStudyStatus.Personalized)
{
caseStudy.CaseStudyStatus = (int)CaseStudyStatus.PendingApproval;
}
caseStudy.ApprovedByRefId = null;
caseStudy.ApprovedTime = null;
this.Entities.SaveChanges();
var existingAdditionalMaterialRefIds = caseStudy.AdditionalMaterials
.Select(m => m.AdditionalMaterialRefId)
.ToArray();
var additionalMaterialsToRemove = this.Entities.AdditionalMaterials
.Where(m =>
m.CaseStudyRefId == caseStudy.CaseStudyRefId &&
!existingAdditionalMaterialRefIds.Contains(m.AdditionalMaterialRefId))
.ToArray();
foreach (var additionalMaterialToRemove in additionalMaterialsToRemove)
{
this.Entities.AdditionalMaterials.Remove(additionalMaterialToRemove);
}
this.Entities.SaveChanges();
}
In general it is what you have to do. You must tell EF about each change you want to perform when attaching detached object graph. I don't say that your code cannot be simplified but you will still have to deal with every entity and setting its state if you want it to be added or modified.
Here is little bit older but still valid answer about the topic - in short nothing has changes since I wrote it, only new DbContext API was created which still sits on top of the old API. The best description of this topic I have seen so far is in book Programming Entity Framework: DbContext.
How about just doing:
db.CaseStudies.Attach(caseStudy);
db.Entry(caseStudy).State = EntityState.Modified;
db.SaveChange();
That will save all changes in your model to the db.

Entity Framework Code First lazy loading non navigation properties

I'm using the entity framework code first CTP4.
Is it possible to lazy load non navigation properties like you can in NH 3.
A common example would be having a table containing a binary column. I only want to retrieve this column's data when I explicitly ask for that property in my code e.g. image.ImageData
Thanks
Ben
Vote here
Vote here
Read this
Ugly workaround:
public static void Main()
{
IEnumerable<MyTable> table;
using (Entities context = new Entities())
{
var buffer =
context.MyTable
.Select(myTable => new
{
Id = myTable.Id,
OtherColumn = myTable.OtherColumn
})
.ToArray();
table = buffer
.Select(t => new MyTable
{
Id = t.Id,
OtherColumn = t.OtherColumn
});
}
}
This will not select the rest of the fields.

Resources