Как загрузить связанные данные в MVC со сложной структурой таблицы

У меня есть несколько классов, которые я буду сокращать для краткости. Ниже они перечислены с соответствующими свойствами/полями, связанными с этим вопросом.

public class AcademicYear
{
    [Key]
    public int AcademicYearId { get; set; }
}

public class Division
{
    public Division()
    {
        this.CareerFields = new HashSet<CareerField>();
    }

    [Key]
    public int    DivisionId     { get; set; }

    // Foreign Keys
    public int    AcademicYearId { get; set; }

    // Navigation Properites
    public virtual AcademicYear AcademicYear { get; set; }

    public virtual ICollection<CareerField> CareerFields { get; set; }
}

public class CareerField
{
    public CareerField()
    {
        this.Clusters    = new HashSet<Cluster>();
    }

    [Key]
    public int    CareerFieldId   { get; set; }

    // Foreign Keys
    public int    AcademicYearId  { get; set; }
    public int    DivisionId      { get; set; }


    // Navigation Properties
    public virtual AcademicYear AcademicYear { get; set; }
    public virtual Division     Division     { get; set; }

    public virtual ICollection<Cluster>    Clusters    { get; set; }
}

public class Cluster
{
    public Cluster()
    {
        this.CareerFields = new HashSet<CareerField>();
    }

    [Key]
    public int ClusterId { get; set; }
    {
        // Foreign Keys
        public int    AcademicYearId { get; set; }

        // Navigation Properties
        public virtual AcademicYear AcademicYear { get; set; }

        public virtual ICollection<CareerField> CareerFields { get; set; }
    }
}

public class Pathway
{
    public Pathway()
    {
        this.CareerMajors = new HashSet<CareerMajor>();
    }

    [Key]
    public int    PathwayId      { get; set; }

    // Foreign Keys
    public int    AcademicYearId { get; set; }
    public int    ClusterId      { get; set; }

    // Navigation Properties
    public virtual AcademicYear AcademicYear { get; set; }
    public virtual Cluster      Cluster      { get; set; }

    public virtual ICollection<CareerMajor> CareerMajors { get; set; }
}

public class CareerMajor
{
    public CareerMajor()
    {
        this.Courses        = new HashSet<Course>();
    }

    [Key]
    public int    CareerMajorId     { get; set; }
    public string FirstYearOffered  { get; set; }

    // Foreign Keys
    public int    AcademicYearId    { get; set; }
    public int    PathwayId         { get; set; }

    // Navigation Properties

    [HiddenInput(DisplayValue = false)]
    public virtual AcademicYear      AcademicYear      { get; set; }
    public virtual Pathway           Pathway           { get; set; }

    public virtual ICollection<Course>         Courses        { get; set; }

}

public class Course
{
    public Course()
    {
        this.CareerMajors  = new HashSet<CareerMajor>();
    }

    [Key]
    public int    CourseId       { get; set; }

    // Foreign Keys
    public int    AcademicYearId { get; set; }
    public int?   InstructorId   { get; set; }

    // Navigation Properties
    public virtual ICollection<CareerMajor>  CareerMajors  { get; set; }

    public virtual AcademicYear AcademicYear { get; set; }
}

У меня также есть класс ViewModel для загрузки всего этого для моего контроллера.

public class CMSIndex
{
    public IEnumerable<Division> Divisions { get; set; }
    public IEnumerable<CareerField> CareerFields { get; set; }
    public IEnumerable<Cluster> Clusters { get; set; }
    public IEnumerable<Pathway> Pathways { get; set; }
    public IEnumerable<CareerMajor> CareerMajors { get; set; }
    public IEnumerable<Course> Courses { get; set; }
}

У меня есть страница razor cshtml (страница индекса, определяемая автоматически CRUD), где я начинаю с отдела и могу загрузить соответствующие CareerFields. Однако, когда я пытаюсь загрузить кластеры, я получаю сообщение об ошибке

Ошибка сервера в приложении '/' Значение не может быть нулевым. Имя параметра: источник Описание: Необработанное исключение возникло во время выполнения текущего веб-запроса. Пожалуйста, просмотрите трассировку стека для получения дополнительной информации об ошибке и о том, где она возникла в коде.

Сведения об исключении: System.ArgumentNullException: значение не может быть нулевым. Имя параметра: источник

Вот мой метод index из контроллера (обратите внимание, что закомментированные строки - это то, что я пробовал)

public ViewResult Index(Int32? divisionID, Int32? careerFieldID, Int32? clusterID, Int32? pathwayID, Int32? careerMajorID, Int32? courseID)
    {
        var viewModel = new CMSIndex();

        viewModel.Divisions = db.Divisions
            .Include(d => d.AcademicYear)
            .Include(d => d.CareerFields)//
            .Include(d => d.CareerFields.Select(cf => cf.Clusters))
            //.Select(c => c.Clusters.Select(cl => cl.Pathways.Select(p => p.CareerMajors.Select(cm => cm.Courses)))))
            .OrderBy(d => d.DivisionName);

        if (divisionID != null)
        {
            ViewBag.DivisionID = divisionID.Value;
            viewModel.CareerFields = viewModel.Divisions.Where(d => d.DivisionId == divisionID.Value).Single().CareerFields;
        }

        if (careerFieldID != null)
        {
            ViewBag.careerFieldID = careerFieldID.Value;
            //viewModel.Clusters = viewModel.CareerFields.Where(cf => cf.CareerFieldId == careerFieldID.Value).Single().Clusters;
            //viewModel.Clusters = viewModel.CareerFields.SelectMany(cf => cf.Clusters);
            viewModel.Clusters = viewModel.CareerFields.Where(cf => cf.CareerFieldId == careerFieldID.Value).Single().Clusters;
        }

        return View(viewModel);
    }

Я следую руководству Contoso по загрузке связанных данных. Кажется, я могу загрузить отношения 1: m, но я не уверен, как это сделать с m: m (например, CareerFields и Clusters).

И у меня есть инициализатор, который загрузил данные для извлечения, и я передаю идентификатор (например,careFieldID).

Где выбрасывается исключение, находится в строке:

viewModel.Clusters = viewModel.CareerFields.Where(cf => cf.CareerFieldId == careerFieldID.Value).Single().Clusters;

И каждая прокомментированная вариация.

Любая помощь будет очень признательна.


person Erik    schedule 27.07.2012    source источник


Ответы (1)


Не говорю, что это проблема, но проверяли ли вы, что значения не равны нулю? В соответствии

viewModel.Clusters = viewModel.CareerFields
    .Where(cf => cf.CareerFieldId == careerFieldID.Value).Single().Clusters;

возможно ли, что viewModel.CareerFields равно нулю?

Как некоторые примечания:

  • С вашими нулевыми проверками, когда у вас есть нулевые значения, лучше использовать их метод HasValue, например.

    if (divisionID.HasValue)
    {
        ...
    }
    
  • Использование Single() может быть немного темпераментным и может генерировать исключения, если элементов не существует или существует более одного. First() может использоваться для обработки более чем одного результата (но если ни один из них не соответствует), SingleOrDefault() будет обрабатывать ноль или один результат, а FirstOrDefault() будет обрабатывать большинство вещей.

  • Вы можете сохранить выражение, поместив свою лямбду в сингл, например.

    viewModel.CareerFields = viewModel.Divisions
        .Single(d => d.DivisionId == divisionID.Value).CareerFields;
    

Изменить: я думаю, что проблема, с которой вы столкнулись, такая же, как и в Этот ответ, не могли бы вы попробовать изменить свои коллекции классов, чтобы они инициализировались при получении при нулевом значении. Например, для отдела

public class Division
{
    [Key]
    public int    DivisionId     { get; set; }

    // Foreign Keys
    public int    AcademicYearId { get; set; }

    // Navigation Properites
    public virtual AcademicYear AcademicYear { get; set; }

    private ICollection<CareerField> careerFields;
    public virtual ICollection<CareerField> CareerFields { 
        get { return careerFields ?? (careerFields = new HashSet<CareerField>()); }
        set { careerFields = value; } 
    }
}

На самом деле это мой предпочтительный метод в любом случае, это создает собственную проблему с вашим кодом, поскольку тот факт, что CareerFields был нулевым, означает, что для данного подразделения нет связанных CareerFields. Это означает, что при попадании в ваш вызов Single() будет выдано исключение The source contains no elements, поскольку CareerFields будет содержать пустой хэш-набор. Это можно исправить, изменив SingleOrDefault().

person Manatherin    schedule 28.07.2012
comment
Спасибо за ответ. Да, я почти уверен, что он равен нулю (кластеры, а не карьеруField.ID... Я проверил, что он не равен нулю). Я знаю, что в моей БД есть значения для того, что я ищу, я просто не уверен, правильно ли я настраиваю свой запрос для запроса отношений m: m. Является ли viewModel.Clusters = viewModel.CareerFields .Where(cf => cf.CareerFieldId == careerFieldID.Value).Single().Clusters; правильный? Кроме того, правильно ли я загружаю соответствующие данные? - person Erik; 28.07.2012
comment
@Erik Ваша загрузка выглядит нормально для меня. Я считаю, что это не кластеры, а поле карьеры подразделения. Я отредактировал свой вопрос, но затем заметил, что вы сказали: «Я знаю, что в моей БД есть значения для того, что я ищу». Структура Entity, похоже, не знает о CareerFields, связанных с Searched Division. Являются ли они определенно в базе данных, есть ли между ними связь «многие ко многим» (в таблице ссылок? Знает ли об этом инфраструктура сущностей?). - person Manatherin; 28.07.2012
comment
Подразделения загружаются. Когда я выбираю поле карьеры для загрузки кластеров, происходит сбой. Исключение выдается в строке viewModel.Clusters = viewModel.CareerFields.Where(cf =› cf.CareerFieldId == careerFieldID.Value).Single().Clusters; - Я пробовал SingleOrDefault, но все равно получаю нулевую ошибку. Я могу сказать, где проблемы. CareerField.Clusters ViewModel не загружается. Я просто не знаю, как их загрузить. Мне нужно загрузить его в модель представления или в контроллер? - person Erik; 31.07.2012
comment
Я заметил, что не могу получить доступ к свойствам непосредственно в моей модели представления (например, я не могу получить доступ к viewModel.CareerFields.Clusters). Это потому, что он определен как IEnumerable? Может ли это быть причиной того, что связанные данные не загружаются? - person Erik; 31.07.2012
comment
ты был прав. Подразделения не загружаются, когда я выбираю кластер. DivisionID сбрасывается и имеет значение null при попытке выбрать кластер, который вызывает ошибку, поскольку viewModel.CareerFields имеет значение null. Я обнаружил, что разница между тем, что я делаю, и примером Contoso заключается в том, что строки запроса отличаются. В contoso это localhost/controller/action/id, но мой URL-адрес отображается как localhost/controller/action?divisionid=1. У меня есть маршрут по умолчанию (и другие маршруты не установлены) как контроллер/действие/идентификатор, но он работает неправильно. - person Erik; 01.08.2012
comment
Я понял. Проблема была не в запросе, а в маршрутизации. Мне нужно было создать пользовательские маршруты для обработки дополнительных необязательных параметров. Я попробовал {tags}/{*tags} для полного охвата, но это не соответствовало тому, что я делал, поэтому я просто создал собственные маршруты для каждого дополнительного тега до курсов. Я ценю помощь, которую вы предложили. Это действительно помогло мне обдумать проблему. +1 за помощь. - person Erik; 01.08.2012
comment
@ Эрик, извини, не было вчера, рад слышать, что ты решил проблему: D - person Manatherin; 02.08.2012