Справка Рефакторинг Entity Framework 4 Query

У меня есть две сущности:

  • Местоположение
  • Опубликовать

Это 1..* между Location и Post.

Местоположение является абстрактным, у меня есть много производных объектов, таких как Город. Я использую наследование Table-Per-Type для своей модели.

Я пытаюсь написать следующий запрос: (упрощенный)

  • Получите 20 лучших городов и включите «Пост с самым высоким рейтингом» (самый высокий рейтинг).

Итак, Location имеет навигационное свойство, называемое Posts.

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

Итак, вот запрос, который у меня сейчас есть. Имейте в виду, мне нужно вернуть ICollection<Location> из этого метода:

public ICollection<Location> FindTopTwentyLocations()
{
    var results = new List<Location>();

    var cities = locationRepository
                   .Find()
                   .OfType<City>()
                   .Select(x => new
                    {
                       Location = x,
                       TopPost = x.Posts.OrderByDescending(r => x.Rating).FirstOrDefault()
                    }).Take(20).ToList();

    foreach (var city in cities)
    {
       var aggregatedCity = city.Location;
       aggregatedCity.Posts = new List<Post> { aggregatedCity.TopPost };
       results.Add(city);
    }

    return results;
}

По сути, я беру первые 20 городов, проецирую их в анонимный тип, чтобы я мог захватить верхнюю публикацию, а затем перебираю эту коллекцию анонимных типов, чтобы запихнуть публикацию обратно в объект «City», для добавления к возвращаемому типу List<Location>.

  • Я не могу использовать .Include, так как это вернет все сообщения.
  • Я не хочу выполнять 2 запроса
  • Я должен использовать проекцию анонимного типа, иначе он выдаст ошибку EF (не может перевести запрос)

Имея в виду эти моменты, есть ли лучший способ сделать это? Я вполне доволен запросом var cities, но мне не очень нравится зацикливание/копирование свойств анонимного типа в объект моей модели.

Любые идеи?

ИЗМЕНИТЬ

Я также заметил, что проекция на анонимный тип теряет нетерпеливо загруженные ассоциации Location, которые я получаю.

E.g

var query = locationRepository.Find().OfType<City>().Include("State").ToList();

работает - вернулись все "государственные" ассоциации.

но:

var query = locationRepository.Find().OfType<City>().Include("State").Select(x => new {
   Location = x,
   TopPost = x.Posts.OrderByDescending(r => x.Rating).FirstOrDefault()
}).ToList();

В результате все ассоциации «Состояние» становятся нулевыми.

странно!


person RPM1984    schedule 08.12.2010    source источник


Ответы (2)


Рад оставить это открытым на некоторое время и увидеть другие ответы, но пока я придерживаюсь того, что у меня есть.

Один момент, который я хотел бы упомянуть относительно моего РЕДАКТИРОВАТЬ выше.

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

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

var cities = locationRepository
                   .Find()
                   .OfType<City>()
                   .Select(x => new
                    {
                       Location = x,
                       State = x.State, // include association in anon type
                       TopPost = x.Posts.OrderByDescending(r => x.Rating).FirstOrDefault()
                    }).Take(20).ToList();

    foreach (var city in cities)
    {
       var aggregatedCity = city.Location;
       aggregatedCity.State = city.State; // copy anon type association over
       aggregatedCity.Posts = new List<Post> { aggregatedCity.TopPost };
       results.Add(city);
    }

И это, кажется, работает.

Что касается рефакторинга кода, я не думаю, что запрос var cities можно оптимизировать. Я попытался переместить копирование foreach слева направо в другую проекцию .Select (например, после материализации запроса - после .ToList()), но для этого мне пришлось бы скопировать ВСЕ свойства слева направо. , например:

var cities = locationRepository
                   .Find()
                   .OfType<City>()
                   .Select(x => new
                    {
                       Location = x,
                       State = x.State, // include association in anon type
                       TopPost = x.Posts.OrderByDescending(r => x.Rating).FirstOrDefault()
                    }).Take(20).ToList().Select(x => new City
                    {
                       CityName = Location.Name,
                       State = State,
                       // etc etc
                    });

Учитывая, что у меня есть более 20 свойств для моих сущностей, я не хочу этого делать. Возвращает меня в болезненный мир Linq-To-Sql и POCO.

Как я уже сказал - открыт для других предложений.

ИЗМЕНИТЬ

Я закончил тем, что использовал хранимую процедуру здесь. Мой код работает, но это более 100 строк. Я бы предпочел, чтобы эти 100+ строк были абстрагированы в хранимую процедуру.

Кроме того, этот результат в основном доступен только для чтения (мне не нужны объекты на графике — я просто получаю результаты, отображаю их и готово).

person RPM1984    schedule 08.12.2010

Я мог бы что-то упустить, но вы не могли бы сделать что-то подобное?

var cities = locationRepository 
                   .Find() 
                   .OfType<City>() 
                   .Select(x => 
                    { 
                       x.Posts = x.Posts.OrderByDescending(r => x.Rating).Take(1);
                       return x;
                    }).Take(20).ToList(); 

или если EF жалуется на изменение свойства Posts, создайте новый объект Location и добавьте сообщение в операцию выбора.

person Kirk Broadhurst    schedule 09.12.2010
comment
Интересно, я попробую - не уверен, что это сработает, но я попробую. О, и я не могу создать новое местоположение, так как оно абстрактно — я использую дженерики, поэтому мой код на самом деле .OfType<T> (where T : Location). Я не могу создать новый город (например), потому что мне пришлось бы копировать все свойства слева направо. Я попробую ваш пример и дам вам знать. - person RPM1984; 09.12.2010
comment
Нет, не смог заставить его работать. Говорит Невозможно преобразовать IEnumerable‹Post› в ICollection‹Post›. И если я попытаюсь сделать .ToList(), он говорит, что лямбда-выражение с телом оператора не может быть преобразовано в дерево выражений. Неважно, у меня это работает лучше/быстрее с SPROC. Однако оцените вашу помощь. :) - person RPM1984; 10.12.2010
comment
Достаточно честно, я знаю, что иногда бывает сложно установить эти свойства коллекции ORM. Использование хранимой процедуры всегда является хорошей идеей, когда запрос становится сложным. - person Kirk Broadhurst; 10.12.2010