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 : ""
};
И это все!
Спасибо, все!