Question

I am using ASP.NET MVC4. I have absolutely no idea where to even start with this - I need to display a hierarchical grid complete with CRUD operations at each level (edit row, create row, delete row). I am using a database-first approach with the Entity Framework (.edmx). The relationships between entities are maintained by a "ParentId" value in the same row in my database. I want to have the "Delete" functionality cascade down to all children, just like in the relationships setting in SQL Server.

enter image description here

QUESTION: Where would I start to begin looking if I want to have a hierarchical grid complete with CRUD operations using the idea of a ParentId to maintain hierarchical relationships? I've looked into ListViews and I don't think that's what I need. I don't want to over complicate this, unless absolutely necessary, and I prefer to not go with a vended solution.

Était-ce utile?

La solution

I ended up using MvcTreeView (http://mvctreeview.codeplex.com/). This is also available in Nuget. This Html Helper is designed to work specifically with an adjacency list (a hierarchical table that maintains its hierarchy by a ParentId column, or similar). This helper generates a hierarchical tree view of each record in your table. Additionally, you can further customize is by adding individual CRUD operations on each item, and the CRUD will work just like it normally would in a normal ASP.NET MVC w/Entity Framework project.

To cascade delete all the way down the tree of "n" depth, here's how to do it:

I ended up creating a custom module that works specifically with adjacency lists. An adjacency list in SQL is where you have a single table maintaining an "n" depth hierarchy by a column ParentId that is a foreign key back to the Id column (the primary key) in the same table.

I created a helper class called "Children". The purpose of this class is to return a list of all traversed child Id's for a given parent. So if you pass in the Id for a parent that's say 6 levels up, the below code will traverse down all 6 levels and return the complete list of Id's for all children, grandchildren, great-grandchildren, and so on.

The reason why I am getting a list of all Id's of children instead of a list of objects to delete is because if I get a list of objects to delete I would have to get these objects under a different DbContext, so when I try to actually delete them, I'll get an error saying that the object is detached (or something like that) because it was instantiated under a different context. Getting a list of Id's prevents this from happening and we can use the same context to perform the delete.

So, call this method GetChildrenIds(List<int> immediateChildrenIds) from your code and pass in a list of ints that correspond to the immediate children of a selected node. For example, to get the list of immediate children to pass into this method, use something like the following (this is based on an ASP.NET MVC pattern using WebAPI):

// keep in mind that `id` is the `id` of the clicked on node.

// DELETE api/region/5
public HttpResponseMessage Delete(int id)
{
     Region region = db.Regions.Find(id);

     List<int> tempRegionIds = new List<int>();
     List<int> immediateChildrenIds = (from i in db.Regions where i.ParentId == id select i.Id).ToList();
     List<int> regionsToDeleteIds = new List<int>();

     // the below is needed because we need to add the Id of the clicked on node to make sure it gets deleted as well
     regionsToDeleteIds.Add(region.Id);

     // we can't make this a static method because that would require static member
     // variables, and static member variables persist throughout each recursion
     HelperClasses.HandleChildren.Children GetChildren = new HelperClasses.HandleChildren.Children();

     // see below this code block for the GetChildrenIds(...) method
     tempRegionIds = GetChildren.GetChildrenIds(immediateChildrenIds);

     // here, we're just adding to regionsToDeleteIds the children of the traversed parent
     foreach (int tempRegionId in tempRegionIds)
     {
         regionsToDeleteIds.Add(tempRegionId);
     }

     // reverse the order so it goes youngest to oldest (child to grandparent)
     // is it necessary?  I don't know honestly.  I just wanted to make sure that
     // the lowest level child got deleted first (the one that didn't have any children)
     regionsToDeleteIds.Reverse(0, regionsToDeleteIds.Count);

     if (regionsToDeleteIds == null)
     {
         return Request.CreateResponse(HttpStatusCode.NotFound);
     }

     foreach (int regionId in regionsToDeleteIds)
     {
         // here we're finding the object based on the passed in Id and deleting it
         Region deleteRegion = db.Regions.Find(regionId);
         db.Regions.Remove(deleteRegion);
     }

     ...

The below code is the class that returns a complete list of children Id's. I put this code in a separate helper class file. The DbContext _db is what I was talking about when I said you don't want to retrieve a list of objects under this context otherwise the Delete wouldn't work when it was actually called in your controller. So instead, as you can see, I get a list of Id's instead and make the actual DbContext call to get the object inside my controller, not this helper class.

public class Children
    {
        private Entities _db = new Entities(HelperClasses.DBHelper.GetConnectionString());
        private List<int> _childrenIds = new List<int>();
        private List<int> _childRegionIds = new List<int>();

        public List<int> GetChildrenIds(List<int> immediateChildrenIds)
        {
            // traverse the immediate children
            foreach (var i in immediateChildrenIds)
            {
                _childRegionIds.Add(i);
                _childrenIds = (from child in _db.Regions where child.ParentId == i select child.Id).ToList();
                if (_childrenIds.Any())
                    GetChildrenIds(_childrenIds);
                else
                    continue;
            }

            return _childRegionIds;
        }
    }

Autres conseils

https://datatables.net/ might be a good start. Check for Editor plugin.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top