Подтвердить что ты не робот

Может ли кто-нибудь объяснить, почему эти два запроса linq возвращают разные результаты?

У меня есть два запроса linq (к EF4), которые возвращают разные результаты. Первый запрос содержит правильные результаты, но не отформатирован/спроецирован правильно.

Второй запрос - это то, что я хочу, но в нем отсутствуют некоторые данные.

схема

альтернативный текст http://img220.imageshack.us/img220/9678/schema.png

Запрос 1

var xxxx = (from cp in _connectedClientRepository
            .GetConnectedClients(new[] { "LogEntry", "LogEntry.GameFile" })
            .AsExpandable()
            .Where(predicate)
            select cp)
    .ToList();

альтернативный текст http://img231.imageshack.us/img231/6541/image2ys.png

Обратите внимание на свойство GameFile. Это не нуль. Это здорово :) Обратите внимание на запрос linq? Я хочу загрузить LogEntry и затем загрузить GameFile (для каждого загруженного LogEntry).

Это то, что мне нужно → для каждого LogEntry который загружен с нетерпением, пожалуйста, GameFile загрузите GameFile. Но этот результат проекции неверен...

Хорошо.. дальше...

Запрос 2

var yyy = (from cp in _connectedClientRepository
            .GetConnectedClients(new[] { "LogEntry", "LogEntry.GameFile" })
            .AsExpandable()
            .Where(predicate)
        select cp.LogEntry)
    .ToList();

alt text

ПРИМЕЧАНИЕ: изображение выше содержит опечатку... обратите внимание, что типизированный код включаемых ассоциаций является правильным (т. LogEntry.GameFile), в то время как изображение имеет опечатку.

Правильная проекция сейчас → все результаты LogEntries. Но обратите внимание, что свойство GameFile теперь имеет значение null? Я не уверен, почему :( Я думал, что я правильно загрузил правильную цепочку. Так что это правильная проекция, но с неверными результатами.

Обязательный код репозитория.

public IQueryable<ConnectedClient> GetConnectedClients(
    string[] includeAssociations)
{
    return Context.ConnectedClients
        .IncludeAssociations(includeAssociations)
        .AsQueryable();
}

public static class Extensions
{
    public static IQueryable<T> IncludeAssociation<T>(
        this IQueryable<T> source, string includeAssociation)
    {
        if (!string.IsNullOrEmpty(includeAssociation))
        {
            var objectQuery = source as ObjectQuery<T>;

            if (objectQuery != null)
            {
                return objectQuery.Include(includeAssociation);
            }
        }

        return source;
    }

    public static IQueryable<T> IncludeAssociations<T>(
        this IQueryable<T> source, params string[] includeAssociations)
    {
        if (includeAssociations != null)
        {
            foreach (string association in includeAssociations)
            {
                source = source.IncludeAssociation(association);
            }
        }

        return source;
    }
}

Обновления

  • 1: исправлена ошибка, замеченная в примерах кода.
  • 2: Добавлен код репозитория, чтобы помочь любому, кто запутался.
4b9b3361

Ответ 1

Я подозреваю, что предложение Крейга Штутца может сработать, но если этого не произойдет, обязательно должно работать следующее:

 var xxxx =_connectedClientRepository
        .GetConnectedClients(new[] { "LogEntry", "LogEntry.GameFile" })
        .AsExpandable()
        .Where(predicate)
        .ToList() // execute query
        .Select(cp => cp.LogEntry); // use linq-to-objects to project the result

Ответ 2

Include() работает с результатами запроса, а не с промежуточными запросами. Вы можете прочитать больше о Include() в этом сообщении. Поэтому одним из решений является применение Include() для всего запроса, например:

var q = ((from cp in _connectedClientRepository.GetConnectedClients()
                                               .AsExpandable()
                                               .Where(predicate) 
          select cp.LogEntry) 
         as ObjectQuery).Include("GameFile").ToList();

Это, вероятно, будет работать, но это уродливо. Можем ли мы лучше?

Я могу думать о двух путях вокруг этой проблемы. В основном это зависит от того, действительно ли вам нужны возвращаемые типы объектов. Я не могу сказать, так ли это, не видя остальной части вашего кода. Как правило, вам нужно возвращать типы объектов, когда вы собираетесь обновлять (или иным образом изменять) их. Если вы выбираете для показа или целей расчета, это часто лучшая стратегия возврата POCOs вместо типов сущностей. Вы можете сделать это с помощью , и, конечно, он работает в EF 1. В этом случае вы измените метод репозитория на возврат типов POCO

 public IQueryable<ClientInfo> GetConnectedClients()
 {
      return from cp in _context.Clients
             where // ...
             select new ClientInfo
             {
                 Id = cp.Id,
                 ClientName = cp.ClientName,
                 LogEntry = new LogEntryInfo
                            {
                                LogEntryId = cp.LogEntry.LogEntryId,
                                GameFile = new GameFileInfo
                                           {
                                               GameFileId = cp.LogEntry.GameFile.GameFileId,
                                               // etc.
                                           },
                                // etc.
                            },
                  // etc.
             };
 }

Обратите внимание, что при использовании проецирования нет никакой загруженной загрузки, без ленивой загрузки и без явной загрузки. Существует только ваше намерение, выраженное в запросе. Поставщик LINQ выяснит, что вам нужно, даже если вы создадите запрос за пределами репозитория!

С другой стороны, вам может потребоваться вернуть типы сущностей вместо POCOs, потому что вы намерены их обновить. В этом случае я бы написал отдельный метод репозитория для LogEntries, как предложил Томас. Но я бы сделал это только в том случае, если бы планировал их обновить, и я мог бы написать его как метод обновления, а не метод Get.

Ответ 3

Кажется, вам нужен еще один метод репозитория, который сделает это за вас; _connectedClientsRepository.GetLogEntriesOfConnectedClients().