Linq "Не удалось перевести выражение... в SQL и не смог обработать его как локальное выражение".

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

var q = from ent in LinqUtils.GetTable<Entity>()
        from tel in ent.Telephones.DefaultIfEmpty()
        select new {
          Name = ent.FormattedName,
          Tel = tel != null ? tel.FormattedNumber : "" // this is what causes the error
        };

tel.FormattedNumber - это свойство, которое объединяет поля Number и Extension в аккуратно отформатированную строку. И вот ошибка, которая возникает:

System.InvalidOperationException: Could not translate expression 'Table(Entity).SelectMany(ent => ent.Telephones.DefaultIfEmpty(), (ent, tel) => new <>f__AnonymousType0`2(Name = ent.FormattedName, Tel = IIF((tel != null), tel.FormattedNumber, "")))' into SQL and could not treat it as a local expression.

Если я изменил ссылку выше от FormattedNumber до простого Number, все будет хорошо.

Но я хочу, чтобы отформатированный номер хорошо отображался в моем списке. Что вы рекомендуете как самый чистый, самый чистый способ сделать это?

Ответ 1

Вы можете использовать AsEnumerable для объекта, но это заставит его вернуть все столбцы (даже если они не используются); возможно, что-то вроде:

var q1 = from ent in LinqUtils.GetTable<Entity>()
         from tel in ent.Telephones.DefaultIfEmpty()
         select new {
           Name = ent.FormattedName,
           Number = (tel == null ? null : ent.Number),
           Extension = (tel == null ? null : ent.Extension)
         };

var q2 = from row in q1.AsEnumerable()
         select new {
             row.Name,
             FormattedNumber = FormatNumber(row.Number, row.Extension)
         };

где FormatNumber - это некоторый метод, который берет два и объединяет их, предположительно, повторно используется из вашего другого (свойства) кода.

С LINQ-to-SQL другой вариант заключается в том, чтобы открыть UDF в контексте данных, который выполняет форматирование внутри базы данных; немного другой пример:

var qry = from cust in ctx.Customers // and tel
          select new {
              cust.Name,
              FormattedNumber = ctx.FormatNumber(tel.Number, tel.Extension)
          };

(который будет выполнять работу в базе данных, является ли это хорошей идеей; -p)

Ответ 2

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

Я не уверен, понимаете ли вы, что класс, представляющий таблицу SQL для LINQ, является классом DTO - он определяет грамматику, используемую переводчиком LINQ-SQL. Внедрение свойства в DTO, которое не сопоставляется с таблицей SQL, даже не поддерживается - это означает, что переводчик может запускать по желанию. Атрибуты определяют грамматику, и все, что не определено ими, не существует для транслятора выражений.

Объекты, названные в предложении from, не являются объектами - это просто символы, используемые для определения правильных полей таблицы, которые будут извлечены. Поле, явно не указанное в select, - это поле, которое не выбрано - по крайней мере, для цели переводчика, возможно, придется пропустить несколько пропусков. Например, если имя ent.FormattedName не объявлено, это проскальзывает и может взорваться последним.

Итак, свойство FormattedNumber, введенное в класс DTO, даже не существует в грамматике. Это не "вычисленное поле" - этот термин строго предназначен для определений таблиц SQL, и если бы у вас его было, это было бы в грамматике DTO. Обратите внимание, что ошибка говорит очень точно "локальное выражение" - очень ограниченная область.

Вы можете попытаться обмануть его вложенным выражением лямбда, вызывающим статическую функцию в целом "tel", которая могла бы извлечь триггер всей записи или выбросить другое исключение.

Другие LINQ-s, которые не являются переводчиками, могут иметь непринужденные правила. LINQ-SQL должен быть либо очень строгим, либо очень медленным, и он уже достаточно медленный: -)

Ответ 3

@Marc Gravell избил меня до ответа, откликнулся также на разных ответчиков на этот вопрос, который поставил меня на правильный путь.

Я сделал это очень похоже на первое предложение Марка, например:

var q1 = from ent in LinqUtils.GetTable<Entity>()
         from tel in ent.Telephones.DefaultIfEmpty()
         select new { ent, tel };
var q2 = from q in q1.AsEnumerable()
         select new {
           Name = q.ent.FormattedName,
           Tel = q.tel != null ? q.tel.FormattedNumber : ""
         };

И это все! Спасибо, все!