Трансляции SQL

49

»» В ДАННОЙ СТАТЬЕ ИСПОЛЬЗУЕТСЯ ИСХОДНЫЙ КОД ДЛЯ ПРИМЕРОВ

При написании запросов LINQ to SQL вы, вероятно, заметили, что при указании выражений, таких как в конструкции where, используется "родной" язык программирования, а не SQL. В конце концов, языковая интеграция является частью предназначения LINQ.

Вот обычный пример запроса LINQ to SQL:

Customer cust = (from с in db.Customers
                  where c.CustomerID == "LONEP" 
                  select c).Single<Customer>();

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

// Пример неправильного запроса LINQ to SQL
Customer cust = (from с in db.Customers
                  where c.CustomerID = 'LONEP' 
                  select c).Single<Customer>();

Вместо операции эквивалентности C# (==) в SQL применяется операция эквивалентности (=). Строковый литерал SQL заключается не в двойные (""), а в одинарные (') кавычки. Одной из целей LINQ является разрешение разработчикам программировать на своем родном языке программирования. Помните, что LINQ — это язык интегрированных запросов. Однако, поскольку база данных не выполняет выражений C#, эти выражения должны быть транслированы в корректный SQL. Поэтому ваши запросы должны транслироваться в SQL.

И это сразу означает, что то, что можно делать, имеет свои ограничения. Хотя в основном трансляция работает достаточно хорошо. Вместо того чтобы дублировать руководство типа справочной системы MSDN для описания этого процесса трансляции с указанием того, что можно и что нельзя транслировать, рассмотрим, чего можно ожидать в тех случаях, когда запрос LINQ to SQL не может быть транслирован.

Первым делом, имейте в виду, что нетранслируемый код может компилироваться. Некорректная трансляция может не проявить себя до тех пор, пока запрос в действительности не будет выполнен. Из-за отложенного выполнения запроса это также означает, что строка кода, определяющая запрос, может выполниться успешно. И только когда дойдет дело до реального извлечения результата, неправильная трансляция заявит о себе в виде исключения, подобного следующему:

Unhandled Exception: System.NotSupportedException: Method 'TrimEnd' has no supported translation to SQL.

Необработанное исключение: System.NotSupportedException: Метод 'TrimEnd' не имеет поддерживаемой трансляции в SQL.

...

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

Northwind db = new Northwind(@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;
                                           AttachDbFilename=C:\Northwind.mdf;
                                           Integrated Security=True");
IQueryable<Customer> custs = from c in db.Customers
                                   where c.CustomerID.TrimEnd('K') == "LAZY"
                                   select c;

      foreach (Customer c in custs)
      {
        Console.WriteLine("{0}", c.CompanyName);
      }

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

Northwind db = new Northwind(@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;
                                           AttachDbFilename=C:\Northwind.mdf;
                                           Integrated Security=True");
                  
IQueryable<Customer> custs = from c in db.Customers
                                   where c.CustomerID == "LAZY".TrimEnd('K')
                                   select c;

      foreach (Customer c in custs)
      {
        Console.WriteLine("{0}", c.CompanyName);
      }

У данного запроса вообще нет вывода. Но так и должно быть, поскольку это правильный вывод запроса, и никакого исключения трансляции SQL здесь нет.

Таким образом, вызов неподдерживаемого метода на столбце базы данных приводит к исключению, в то время как при вызове того же метода на переданном параметре все в порядке.

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

Следует иметь в виду одну вещь. Если нужно вызвать неподдерживаемый метод на столбце базы данных, возможно, вместо этого можно вызвать метод, имеющий противоположный эффект, но вызвать его на параметре.

Скажем, к примеру, что требуется вызвать метод ToUpper на столбце базы данных, а он не поддерживается; возможно, вместо этого удастся вызвать метод ToLower на параметре. Однако в этом случае метод ToUpper все-таки поддерживается, так что пример не очень удачный.

Также следует удостовериться, что вызываемый метод дает прямо противоположный эффект. В рассматриваемом случае столбец базы данных может содержать строку в смешанном регистре, так что вызов ToLower не будет иметь в точности противоположного эффекта. Если столбец базы данных содержит значение "Smith", а параметр — "SMITH", и производится проверка эквивалентности, то вызов метода ToUpper на столбце базы данных будет работать и обеспечит соответствие. Однако если бы метод ToUpper не поддерживался, попытка вывернуть эту логику наизнанку вызовом метода ToLower на параметре не дала бы соответствия.

Может возникнуть вопрос: как узнать, что метод TrimEnd не поддерживается в трансляции SQL? Поскольку природа того, какие примитивные типы и методы поддерживаются, динамична и подвержена изменениям, попытка документировать их все выходит за рамки контекста этой статьи. Относительно трансляции существует масса ограничений и исключений. Совершенствование трансляции SQL является областью приложения усилий со стороны Microsoft. Чтобы узнать, что поддерживается, а что нет, загляните в раздел документации MSDN, озаглавленный ".NET Framework Function Translation" для LINQ to SQL. Однако, как видно из предыдущих примеров, узнать, что метод не поддерживается, очень легко.

Пройди тесты
Лучший чат для C# программистов