Поддержка сложного LINQ Entity Framework

#.net #linq #entity-framework #entity-framework-4

#.net #linq #entity-framework #entity-framework-4

Вопрос:

Я знаю, что у Entity Framework есть некоторые проблемы с поддержкой LINQ (по крайней мере, по сравнению с его предшественником LINQ to SQL) … и обычно я могу найти творческий способ реструктуризации моего запроса LINQ, чтобы он поддерживался EF и не выдавал

 "Unable to create a constant value of type ..."
  

Но на этот раз у меня возникли проблемы. Как обычно, проблема связана со сложными объединениями. На этот раз это требуется для моделирования некоторых устаревших данных, с которыми я работаю.

У меня есть запрос для Office (простой POCO)

 public IQueryable<Office> Offices
{
    get
    {
        IQueryable<Office> query = 
            from pn in _context.Locations
            where pn.Type == "Y"
            select new Office
            {
                Id = pn.Id    1000,
                Name = pn.Name,
            };

        query = query.Union(
            from r in _context.Resources
            where r.ResourceType == "L"
            select new Office
            {
                Id = r.ResourceId,
                Name = r.ResourceName,
            });

        return query;
    }
}
  

Тогда у меня есть кое-что еще, в чем есть свойство Office.

 public IQueryable<ScheduleEntry> ScheduleEntries
{
    get
    {
        return 
            from pc in _context.CalendarEntries
            join o in this.Offices on pc.LocationId 
                equals o.Id into offices
            from office in offices.DefaultIfEmpty()
            let mainOffice = this.Offices.First()
            select new ScheduleEntry
            {
                Id = pc.CalendarId,
                StartDateTime = pc.StartDateTime ?? DateTime.MinValue,
                EndDateTime = pc.EndDateTime ?? DateTime.MinValue,
                Office = pc.LocationId == 0 ? mainOffice : office,
            };
    }
}
  

Пожалуйста, обратите внимание, что указание мне сделать его перечислимым сводит на нет purpose…so пожалуйста, не советуйте этого.

И так …. выполнение CalendarEntries.ToArray() бросает "Unable to create a constant value of type Office"...

В частности, проблема заключается в let mainOffice = Offices.First() . Если я удалю эту логику, запрос будет работать нормально. Есть идеи, как заставить это работать с Entity Framework?

Спасибо.

Комментарии:

1. Вау. «Плохая поддержка LINQ?» Я думаю, вам следует исключить эмоции или рискнуть закрыть этот вопрос как субъективный и аргументированный. Придерживайтесь сути.

2. EF не имеет плохой поддержки linq. Это не зависит от хранилища. Linq для sql — это, по сути, генерация кода SQL Server, тогда как EF — это настоящий ORM. Это означает, что вычисляемые свойства будут работать, только если есть способ выполнить их в ЛЮБОМ хранилище данных (не только SQL Server). Это не плохая поддержка, а просто общая поддержка хранилища данных.

3. Это звучит скорее как угроза, чем предложение. Хотя, честно говоря, я ищу помощи, а не угроз.

4. @Gats: Я не думаю, что использование функций продукта является адекватной защитой от его сбоев. Сделать один шаг вперед и два шага назад — это не определение успеха.

5. @Gats: Я прекрасно осведомлен о том, как работает IQueryProvider. Сказать, что это из-за способа работы хранилища данных, неточно / некорректно. Вполне возможно выполнить запрос вышеуказанного типа САМЫМИ разнообразными способами, независимо от хранилища данных. ФРЕЙМВОРК — это то, что не может этого сделать. Если это компромисс, то компромисс заключается в том, что команда EF решила, что они не могут понять, как или у них недостаточно времени для этого. Учитывая, что предшественник EF мог сделать это без проблем, я действительно считаю это ошибкой.

Ответ №1:

Да, хорошо, похоже, что двойной запрос отключает его. Единственным вариантом является либо левое присоединение к Office, либо выполнение следующего:

        Office mainOffice = Offices.First();
       return from pc in _context.CalendarEntries
               join o in Offices on pc.LocationId equals o.Id into offices
               from office in offices.DefaultIfEmpty()
               select new ScheduleEntry
                          {
                              Id = pc.CalendarId,
                              StartDateTime = pc.StartDateTime ?? DateTime.MinValue,
                              EndDateTime = pc.EndDateTime ?? DateTime.MinValue,
                              Office = pc.LocationId == 0 ? mainOffice : office,
                          };
  

Я предполагаю, что вы не хотели этого делать из-за жалоб на то, чтобы сделать его IEnumerable, поэтому вам нужно будет присоединиться к запрашиваемому Office, чтобы указать EF, как объединять запросы вместе. Пусть x = y или value = значение ищет константу, которая, я думаю, имеет смысл.

Комментарии:

1. Я получаю то же самое исключение «Не удается создать постоянное значение типа …».

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

3. Спасибо. При выполнении этого я получаю ту же ошибку… НО если я заранее получу идентификатор главного офиса: var mainOfficeId = Офисы. Первый(). Идентификатор, затем используйте этот идентификатор во втором соединении в запросе, это работает…. Это не идеально, но это работает.

4. Спасибо. Просто к вашей точке зрения ранее, хотя … не будучи в состоянии сделать именно то, что у вас есть в вашем примере выше: pc.LocationID == 0? MainOffice: office, где office является частью запроса, а MainOffice — локальной переменной, не имеет ничего общего с хранилищем данных или SQL server или чем-либо еще, за исключением того, как EF реализовала компонент проектирования своего IQueryProvider. Нет причин, по которым ваш пример не должен работать … но это не так.

5. Согласен. Понятно, что они не могут сконструировать весь объект для передачи в результаты выбора, но я думаю, что они могли бы что-то сделать при заполнении указателями или что-то в этомроде. Если оставить в стороне сравнения Linq с sql, я разделяю ваше разочарование по поводу ошибок во время выполнения.

Ответ №2:

First выполняет запрос при построении дерева выражений, поэтому он пытается передать Office в качестве параметра вашему запросу, что невозможно выполнить.

Одним из способов замены First является вызов this.Offices.Take(1) .

Комментарии:

1. Это также приводит к той же ошибке «Не удается создать постоянное значение типа». Также — First не выполняет запрос при построении дерева выражений, если оно является частью большего дерева выражений. То есть в исходном примере, который я предоставил, он создается как MethodCallExpression для метода First…it не выполняется непосредственно во время создания.

2. @Jeff: Я только что протестировал First другой запрос, и как только я использую его в подзапросе, я получаю исключение: метод ‘First’ может использоваться только в качестве заключительной операции запроса. Вместо этого рассмотрите возможность использования метода ‘FirstOrDefault’ в этом экземпляре. Поэтому я все еще думаю, что это First нельзя использовать внутри запроса, но FirstOrDefault можно.

3. Хороший момент!… но это все еще функция поставщика EF, а не способ создания деревьев выражений. Я только что попытался заменить First () в моем примере на FirstOrDefault (), и я все еще получаю то же исключение «Не удается создать постоянное значение …»…

4. Я все еще удивлен, что Take(1) это не работает — я успешно использовал это раньше вместо First . Похоже, поставщику EF не нравится, как разные наборы результатов объединяются в запросе, потому что обычно ваш подзапрос должен выполняться для каждой записи с pc.LocationId == 0 .