简体   繁体   中英

How can I move through multiple classes/tables in MVC using foreign keys?

In other words, how can I traverse the "Hierarchical tree" of my classes through foreign keys?

Below is a picture of the tree and associations.

这是树和关联的简要图片。

I want to loop through the tables in my view respectively. It's easy to display the Survey data and Category data because I can just reference the PK and FK SurveyID from the query string. I'm not sure how to get the association from CategoryID to SurveyID though past that point.

Here is my View

@using (Html.BeginForm("Save", "Surveys"))
{

<div class="form-group">
    @Html.LabelFor(m => m.SurveyId)
    @Html.TextBoxFor(m => m.Description, new { @class = "form-control" })
    @Html.ValidationMessageFor(m => m.Description)
</div>
<div class="form-group">
            @for (int i = 0; i < Model.Categories.Count; i++)
            {
                <ul>@Html.TextBoxFor(m => m.Categories[i].Description, new { @class = "form-control" })</ul>
                @Html.HiddenFor(m => m.Categories[i].CategoryId)
                @*Here is where I attempted to loop the questions based on category*@
                @*@for (int j = 0; j < Model.Categories[i].Questions.Count(); j++)*@
                {
                    @Html.TextBoxFor(m => m.Questions[j].QuestionText)
                }
            }
</div>

    @Html.HiddenFor(m => m.User.Id)
    @Html.HiddenFor(m => m.SurveyId)
    @Html.AntiForgeryToken()
    <button type="submit" class="btn btn-primary">Save</button>
}

I tried using for (int j = 0; j < Model.Questions.Count(); j++)

but it came up with nothing and I have a feeling that it wouldn't work regardless.

My guess is the problem lies not within the view, but within the controller on how it grabs the data.

public ActionResult Edit(int id)
    {
        var survey = _context.Surveys.SingleOrDefault(c => c.SurveyId == id);
        var categories = new List<Category>();
        categories = _context.Categories.Where(c => c.SurveyId == id).ToList();
        var questions = new List<Question>();
        //questions = _context.Questions.Include()

        //var questions = new List<Question>();
        //questions = _context.Categories.Include(c => c.SurveyId == id).ToList();
        if (survey == null)
            return HttpNotFound();

        var viewModel = new NewSurveyViewModel(survey)
        {
            Questions = questions,
            Categories = categories
        };
        return View("SurveyForm", viewModel);
    }

Here I'm not sure if I'm supposed to use the include method or what, but I can't think of how to bridge the association between the Category and Survey ID's and then from there go on to use QuestionID.

Am I relatively close?

This looks like it may be a data access problem rather than an MVC problem. If you're using Entity Framework, Entity Framework Loading Related Entities for how to ensure your objects are loaded.

Also, don't load Questions into the viewModel separately as you need to preserve hierarchy. Your TextBoxFor call for the questions should utilize that.

Try @Html.TextBoxFor(m => m.Categories[i].Questions[j].QuestionText) .

JamieSee pointed out that in order to get the data to show up, first eager loading must be used as shown here msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx

from there I then used a nested foreach loop.

<div class="form-group">
    @foreach (var category in Model.Categories.Where(q => q.SurveyId == Model.SurveyId))
    {
        <ul>@Html.TextBoxFor(m => category.Description, new { @class = "form-control" })</ul>
            @Html.HiddenFor(m => category.CategoryId)

        foreach (var question in Model.Questions.Where(q => q.CategoryId == category.CategoryId))
        {
            <ul>@Html.TextBoxFor(m => question.QuestionText, new { @class = "form-control" })</ul>
                @Html.HiddenFor(m => question.QuestionId)

            foreach (var answer in Model.Answers.Where(a => a.QuestionId == question.QuestionId))
            {
                <ul>@Html.TextBoxFor(m => answer.AnswerText, new { @class = "form-control" })</ul>
                    @Html.HiddenFor(m => answer.AnswerId)
            }
        }
    }
</div>
var db = new DBContext() // => this is your database context

var result = from s in db.Surveys
             select new {
               survey = s,
               categories = from c in db.Categories
                            where c.SurveyID = s.ID
                            select new {
                               category = c,
                               questions = from q in db.Questions
                                           where q.CategoryID = c.ID
                                           select new {
                                               question = q,
                                               answers = from a in db.Answers
                                                         where a.QuestionID = q.ID
                                                         select a
                                            } // close the questions
                            } // close the categories
            };// close the surveys

return result;

If you want to use this query inside the VIEW, you can use with out return result; Also you dont need to define any data transfer class.

But if you want to use inside the controller and return from any action or json result; you must use class object for data transfer in every level of select.

For example first level DTO class can be like this;

public class SurveyDTO
{
    public Survey survey{get;set;}
    public List<CategoriesDTO> categories {get;set;}
}

And second level DTO is;

public class CategoriesDTO
{
    public Category category {get;set;}
    public List<QuestionsDTO> questions {get;set;}
}

Same way the third level;

public class QuestionsDTO
{
    public Question question {get;set;}
    public List<Answer> answers{get;set;}
}

Dont forget to add DTO names after select! In the contreller; The query changes like this;

var db = new DBContext() // => this is your database context

var result = (from s in db.Surveys
             select new SurveyDTO(){
               survey = s,
               categories = (from c in db.Categories
                            where c.SurveyID = s.ID
                            select new CategoriesDTO(){
                               category = c,
                               questions = (from q in db.Questions
                                           where q.CategoryID = c.ID
                                           select new QuestionsDTO(){
                                               question = q,
                                               answers = (from a in db.Answers
                                                         where a.QuestionID = q.ID
                                                         select a).ToList()
                                            }).ToList() // close the questions
                            }).ToList() // close the categories
            }).ToList();// close the surveys

return result;

I hope the explanation clear and helps.

IF YOU USE ONE QUERY TO GET DATA IT WILL BE MORE FAST

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM