Запрос анонимного объекта EF возвращает нулевые коллекции вместо пустых

Я использую этот трюк для выполнения условного включения с EF. http://blogs.msdn.com/b/alexj/archive/2009/10/13/tip-37-how-to-do-a-conditional-include.aspx

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

Например, коллекция StudentModules будет нулевой. Как я могу превратить его в пустой список в моем запросе? т.е. без необходимости перебирать все это и проверять.

Я могу поместить конструктор в poco для инициализации списка, который это исправит, но эта коллекция является виртуальным членом в poco (на основе видео EF!) - конечно, это не путь?

var query = from module in db.Modules
            where module.Id == id
            select new 
            {
              module,
              QualificationModules = from qualificationModule in module.QualificationModules
                                     where qualificationModule.IsDeleted == false
                                     select new
                                     {
                                       qualificationModule,
                                       qualificationModule.Qualification,
                                       StudentModules = from studentModule in qualificationModule.StudentModules
                                                        where studentModule.IsDeleted == false 
                                                        select new
                                                        {
                                                          studentModule,
                                                          studentModule.Student
                                                        }
                                     },

              Assessments = (from assessment in module.Assessments
                             where assessment.IsDeleted == false
                             select new
                             {
                               assessment,
                               assessment.AssessmentType
                             }
                            )
            };

var modules = query.AsEnumerable().Select(x => x.module);

return modules.ToList().First();

person user2301328    schedule 15.05.2013    source источник


Ответы (1)


Исправление отношений запускается, когда объект присоединяется к контексту — либо вручную, вызывая Attach, либо когда объект материализуется в результате запроса (ваш случай).

Он основан на внешних ключах объекта и работает в обоих направлениях:

  • Если контекст уже содержит объект A с внешним ключом f к объекту B, а объект B присоединяется к контексту, который имеет первичный ключ с тем же значением f, что и внешний ключ в A (т. е. два объекта связаны отношения FK), то Entity Framework сделает следующее:

    • If A has a navigation reference property to B it will assign the attached entity B to this property.
    • Если B имеет свойство навигационной ссылки, равное A (отношение "один к одному"), этому свойству будет присвоено значение A.
    • Если B имеет свойство коллекции навигации A (отношение "один ко многим"), оно добавит A к этой коллекции в прикрепленном объекте B. Если коллекция null, она создаст экземпляр коллекции перед добавлением.
  • Если объект B присоединяется к контексту, который имеет внешний ключ f к объекту A, который уже содержится в контексте и имеет f в качестве первичного ключа, EF установит свойства навигации на основе тех же правил, что и выше.

В качестве примечания: тот факт, что исправление отношений основано на внешних ключах (они всегда загружаются, когда вы запрашиваете объект, независимо от того, отображается ли FK как свойство в классе модели или нет), также является причиной того, что исправление отношений не работает. не применяется и не работает для отношений «многие ко многим», поскольку два объекта отношения «многие ко многим» не имеют внешнего ключа.

Теперь, если в вашем случае нет связанных StudentModules, нет объекта StudentModule, который загружается в контекст, и нет ничего, на что EF мог бы ориентироваться для исправления. Имейте в виду, что алгоритм исправления не связан с конкретным запросом и не только фиксирует отношения между сущностями, которые этот запрос материализует, но и рассматривает все сущности для исправления, которые уже содержатся в контексте, независимо от того, как они вошли в контекст. Если вы хотите, чтобы коллекции создавались как пустые коллекции, EF пробежал все прикрепленные родительские сущности StudentModules и просто создал пустую коллекцию. Нет смысла делать это во время исправления вместо того, чтобы заранее создавать пустые коллекции, прежде чем сущности будут привязаны к контексту.

Я могу поместить конструктор в poco для инициализации списка, который это исправит, но эта коллекция является виртуальным членом в poco (на основе видео EF!) - конечно, это не путь?

На мой взгляд, это это лучшее решение, если вы не хотите иметь null коллекций в экземплярах класса вашей модели. Неважно, объявлена ​​коллекция как virtual (чтобы включить ленивую загрузку) или нет. Тип коллекции не имеет производного типа прокси, только экземпляры, которые добавляются в коллекцию, являются производными прокси. В обоих случаях вы можете просто использовать StudentModules = new HashSet<StudentModule>(); (или List, если хотите).

person Slauma    schedule 15.05.2013
comment
Ааааа, вижу, отличное объяснение. Очень тщательно. Спасибо любезно. - person user2301328; 15.05.2013
comment
Самая большая проблема с этим алгоритмом заключается в том, что он делает неясным, есть ли у объекта дочерние элементы или они не были загружены. Один из них, вероятно, является ошибкой программирования (забыл загрузить), а другой - действительное состояние системы. Во многих веб-сценариях отложенная загрузка вполне может быть архитектурным анти-шаблоном и намеренно отключена на уровне контекста, поскольку вы хотите убедиться, что вы совершаете только один круговой путь между сервером базы данных и веб-сервером. Есть ли другой способ сделать вывод, был ли загружен связанный объект (коллекция или отдельная ссылка) или нет? - person Marchy; 05.01.2014
comment
@Marchy: я считаю, что единственный надежный шаблон: if (!context.Entry(parent).Reference(p => p.Child).IsLoaded) context.Entry(parent).Reference(p => p.Child).Load() (или .Collection(p => p.Children) для коллекций). Однако он может генерировать ненужные запросы, поскольку IsLoaded может вернуть false, хотя все связанные объекты уже присоединены из-за некоторых более ранних запросов. EF обнаружит это, но только после запроса, который запускает .Load(), вернет результат. - person Slauma; 05.01.2014