Хочется работать с SQL-ем напрямую, и в тоже время хочется статической проверки sql statement-ов, параметров и результатов запросов. Intellisense тоже подтянется, если будет алгоритм статической проверки.
Собственно, в чем сложность? Если параметризованный sql statement известен в дизайн тайме, то упомянутая статическая проверка это лишь техническая проблема. Например, при использовании LINQtoSQL или Entity Framework можно запрос оформить в виде хранимой процедуры и сгенерировать строго типизированную обертку на C#-е. Правда, тут есть небольшое неудобство -- в хранимой процедуре надо вручную указывать типы параметров. Но это неудобство можно устранить, см. Приложение 1. Таким образом, со статическим SQL-ем проблем нет -- наше желание реализуемо.
Но потребности реальных проектов не укладываются в статический SQL. Причины бывают самые разнообразные.
Есть большое подозрение, что всю или почти всю динамичность, требуемую в реальных проектах, можно описывать следующим образом. Динамичность sql statement-а задается некоторым набором параметров, заданных на “конечных” множествах: Boolean, Enum, интерфейс с известным числом реализаций (вложенность конструкторов разрешается произвольная). Некоторая утилита (в post-build event-е) перебирает все комбинации таких параметров и генерирует sql statement-ы. Сгенерированные sql statement-ы проходят проверку. Еще динамичность может зависеть от массива неизвестной длинны, в этом случае в дизайн тайме будет генерироваться sql statement-ы для массива с Length=1,2,3,5, надо же где-то остановиться . Последнее выглядит немного странным и похожим на кривизну, но при детальном рассмотрении выясняется, что кривизны нет.
Продемонстрирую описанную схему на вот этом примере
Дописываем partial класс к PartLinkQueryProvider и пишем вот такой код:
public partial class PartLinkQueryProvider : IQueryProvider<PartLinkInfo>
{
public readonly IParamManager<PartLinkParams> PartLinkParams;
private readonly IInnerPartLinkSwitcher innerPartLinkSwitcher;
public PartLinkQueryProvider(
IParamManager<PartLinkParams> partLinkParams, IInnerPartLinkSwitcher innerPartLinkSwitcher)
{
PartLinkParams = partLinkParams;
this.innerPartLinkSwitcher = innerPartLinkSwitcher;
}
}
public partial class PartLinkInfo
{
}
public partial class PartLinkParams
{
}
public interface IInnerPartLinkSwitcher
{
void AddSubQuery(Action<bool> action);
}
public class WithoutInnerPartLinkSwitcher : IInnerPartLinkSwitcher
{
public void AddSubQuery(Action<bool> action)
{
//no-op
}
}
public class WithInnerPartLinkSwitcher : IInnerPartLinkSwitcher
{
public readonly IParamManager<InnerPartLinkParams> InnerPartLinkParams;
private readonly bool latestPublicationOnly;
public WithInnerPartLinkSwitcher(
IParamManager<InnerPartLinkParams> innerPartLinkParams, bool latestPublicationOnly)
{
InnerPartLinkParams = innerPartLinkParams;
this.latestPublicationOnly = latestPublicationOnly;
}
public void AddSubQuery(Action<bool> action)
{
action(latestPublicationOnly);
}
}
public partial class InnerPartLinkParams
{
}
Запускаем build. В post build event-е запускается некоторая утилита. Утилита перебирает все классы, реализующие интерфейс IQueryProvider<T>. Утилита указывает на ошибки, если такие имеются. Если все хорошо, генерируются следующий код:
public partial class PartLinkParams
{
public readonly Guid GenericServicePartsPageID;
public readonly Guid ModelPublicationID;
public readonly Guid ServicePartsPageID;
public PartLinkParams(Guid genericServicePartsPageID, Guid modelPublicationID, Guid servicePartsPageID)
{
GenericServicePartsPageID = genericServicePartsPageID;
ModelPublicationID = modelPublicationID;
ServicePartsPageID = servicePartsPageID;
}
}
public partial class InnerPartLinkParams
{
public readonly Guid ModelID;
public readonly string LanguageCode;
public InnerPartLinkParams(Guid modelID, string languageCode)
{
ModelID = modelID;
LanguageCode = languageCode;
}
}
public partial class PartLinkInfo
{
public string PartNumber { get; set; }
}
Теперь мы можем выполнять в строго типизированной манере три варианта запроса, упомянутые в условии задачи:
public static void Main()
{
ExecutePartLinkQuery(
dbCommand =>
new PartLinkQueryProvider(
new PartLinkParams(
new Guid("..."),
new Guid("..."),
new Guid("...")
).AsQueryParams(dbCommand),
new WithoutInnerPartLinkSwitcher()
)
);
ExecutePartLinkQuery(
dbCommand =>
new PartLinkQueryProvider(
new PartLinkParams(
new Guid("..."),
new Guid("..."),
new Guid("...")
).AsQueryParams(dbCommand),
new WithInnerPartLinkSwitcher(
new InnerPartLinkParams(
new Guid("..."),
"en"
).AsQueryParams(dbCommand),
false
)
)
);
ExecutePartLinkQuery(
dbCommand =>
new PartLinkQueryProvider(
new PartLinkParams(
new Guid("..."),
new Guid("..."),
new Guid("...")
).AsQueryParams(dbCommand),
new WithInnerPartLinkSwitcher(
new InnerPartLinkParams(
new Guid("..."),
"en"
).AsQueryParams(dbCommand),
true
)
)
);
}
private static void ExecutePartLinkQuery(
Func<DbCommand, IQueryProvider<PartLinkInfo>> queryProviderFunc)
{
var dbCommand = ...
var queryProvider = queryProviderFunc(dbCommand);
queryProvider.FillDbCommand(dbCommand);
using (var reader = dbCommand.ExecuteReader())
{
while (reader.Read())
{
PartLinkInfo partLinkInfo = queryProvider.Materialize(reader);
Console.WriteLine(partLinkInfo.PartNumber);
}
}
}
Материализацию может осуществлять как самописная инфраструктура, так и метод ObjectContext.Translate из Entity Framework.
Приложение 1.
Sql statement кладем в некоторое место (xml, cs, и т.д.), откуда его можно достать в дизайн тайме. Специальная утилита в дизайн тайме достает sql statement, объявляет все параметры типа binary и смотрит план запроса. В плане запроса будет соответствующий оператор неявного преобразования из binary в нужный тип .
Re: Строго типизированная работа с SQL-ем напрямую без LINQ
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Создаем Preprocessed Text Template с названием PartLinkQueryProvider (IntelliSense и Syntax Coloring обеспечивает T4 Editor):
Почему таким же способом не сгенерировать различные выражения Linq для различных вариантов?
Если нам не помогут, то мы тоже никого не пощадим.
Re[2]: Строго типизированная работа с SQL-ем напрямую без LI
Здравствуйте, IT, Вы писали:
AP>>Создаем Preprocessed Text Template с названием PartLinkQueryProvider (IntelliSense и Syntax Coloring обеспечивает T4 Editor): IT>Почему таким же способом не сгенерировать различные выражения Linq для различных вариантов?
Да, пожалуйста, можно и Linq генерировать. Но факт в том, что главный козырь Linq (static checking) побит. Соответственно, выбор между LINQ и SQL следует делать с учетом этого факта. Расклад поменялся, и притом существенно .
Re[3]: Строго типизированная работа с SQL-ем напрямую без LI
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Здравствуйте, IT, Вы писали:
AP>>>Создаем Preprocessed Text Template с названием PartLinkQueryProvider (IntelliSense и Syntax Coloring обеспечивает T4 Editor): IT>>Почему таким же способом не сгенерировать различные выражения Linq для различных вариантов? AP>Да, пожалуйста, можно и Linq генерировать. Но факт в том, что главный козырь Linq (static checking) побит. Соответственно, выбор между LINQ и SQL следует делать с учетом этого факта. Расклад поменялся, и притом существенно .
Вот сколько тебя не читаю у тебя всегда сильно свое мнение, которые ты навязываешь окружающим.
Рассказываю тайну. Главный козырь Linq не столько в статической проверке как таковой сколько в динамической сборке запроса (композиция-декомпозиция) с проверкой корректности при компиляции.
Этот паттерн называется QueryObject и фаулером описан был давно. Linq реализует этот паттерн и добавляет к нему статическую проверку.
Статическую проверку в post-build можно и для обычных строк сделать, если они не клеятся динамически. Такое вполне работает в некоторых IDE для java.
Re[4]: Строго типизированная работа с SQL-ем напрямую без LI
Здравствуйте, gandjustas, Вы писали:
G>Статическую проверку в post-build можно и для обычных строк сделать, если они не клеятся динамически.
Ты не уловил сути первого поста. Там ровно про это! В первом посте как раз про динамическое склеивание (можно просто StringBuilder-ом), и при этом работает static checking.
Re[3]: Строго типизированная работа с SQL-ем напрямую без LI
Здравствуйте, Alexander Polyakov, Вы писали:
AP>>>Создаем Preprocessed Text Template с названием PartLinkQueryProvider (IntelliSense и Syntax Coloring обеспечивает T4 Editor): IT>>Почему таким же способом не сгенерировать различные выражения Linq для различных вариантов? AP>Да, пожалуйста, можно и Linq генерировать. Но факт в том, что главный козырь Linq (static checking) побит. Соответственно, выбор между LINQ и SQL следует делать с учетом этого факта. Расклад поменялся, и притом существенно .
Почему это побит. Ты же потом это компилировать будешь.
Если нам не помогут, то мы тоже никого не пощадим.
Re[5]: Строго типизированная работа с SQL-ем напрямую без LI
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Здравствуйте, gandjustas, Вы писали:
G>>Статическую проверку в post-build можно и для обычных строк сделать, если они не клеятся динамически. AP>Ты не уловил сути первого поста. Там ровно про это! В первом посте как раз про динамическое склеивание (можно просто StringBuilder-ом), и при этом работает static checking.
Нет, ты заранее определяешь некоторый набор строк, это далеко не тоже самое что динамическое создание запросов с проверкой при компиляции.
Результат примерно одинаковый а степень удобства очень разная.
Re[4]: Строго типизированная работа с SQL-ем напрямую без LI
Здравствуйте, IT, Вы писали:
AP>>... Но факт в том, что главный козырь Linq (static checking) побит. Соответственно, выбор между LINQ и SQL следует делать с учетом этого факта. Расклад поменялся, и притом существенно . IT>Почему это побит. Ты же потом это компилировать будешь.
Не совсем понимаю о чем ты, что плохого в компиляции?
Если придираться к словам, то фразу “главный козырь побит” можно заменить на “у другого решения такая фича (static checking) теперь тоже имеется”.
Re[5]: Строго типизированная работа с SQL-ем напрямую без LI
Здравствуйте, Alexander Polyakov, Вы писали:
AP>>>... Но факт в том, что главный козырь Linq (static checking) побит. Соответственно, выбор между LINQ и SQL следует делать с учетом этого факта. Расклад поменялся, и притом существенно . IT>>Почему это побит. Ты же потом это компилировать будешь. AP>Не совсем понимаю о чем ты, что плохого в компиляции? AP>Если придираться к словам, то фразу “главный козырь побит” можно заменить на “у другого решения такая фича (static checking) теперь тоже имеется”.
А... в этом смысле. Ты его через SQL прогоняешь?
Если нам не помогут, то мы тоже никого не пощадим.
Re[3]: Строго типизированная работа с SQL-ем напрямую без LI
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Да, пожалуйста, можно и Linq генерировать. Но факт в том, что главный козырь Linq (static checking) побит. Соответственно, выбор между LINQ и SQL следует делать с учетом этого факта. Расклад поменялся, и притом существенно .
И что из этого следует? Какие есть плюсы и минусы у описанного подхода по сравнению с linq?
Re[6]: Строго типизированная работа с SQL-ем напрямую без LI
Здравствуйте, gandjustas, Вы писали:
G>... ты заранее определяешь некоторый набор строк ...
Нет, не верно. Программист не задает набор строк, он пишет код, который динамически формирует строки.
G>... динамическое создание запросов с проверкой при компиляции.
На эту тему тоже есть варианты, например, классический QueryObject и LINQ имеют сильные различие в этом плане. QueryObject предоставляет полную динамичность в отличие от LINQ. Ты, похоже, этого не понимаешь. Доказывать тебе что-либо выходит сильно дорого
. У меня нет столько времени. Подсказка: expression tree в LINQ формирует компилятор, а в случае QueryObject компилятор не вмешивается в процесс формирования ET, все делается программистом (обычным кодом).
G>Результат примерно одинаковый а степень удобства очень разная.
Да, верно. Одно из различий, влияющее на степень удобства: с SQL СУБД часто бывает удобнее работать на ее родном языке, без посредников.
Re[7]: Строго типизированная работа с SQL-ем напрямую без LI
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Здравствуйте, gandjustas, Вы писали:
G>>... динамическое создание запросов с проверкой при компиляции. AP>На эту тему тоже есть варианты, например, классический QueryObject и LINQ имеют сильные различие в этом плане. QueryObject предоставляет полную динамичность в отличие от LINQ. Ты, похоже, этого не понимаешь.
Это ты не понимаешь.
AP>Подсказка: expression tree в LINQ формирует компилятор, а в случае QueryObject компилятор не вмешивается в процесс формирования ET, все делается программистом (обычным кодом).
А что мне мешает руками создавать ET? Компилятор только во многом упрощает жизнь, но отнюдь не лишает возможностей.
G>>Результат примерно одинаковый а степень удобства очень разная. AP>Да, верно. Одно из различий, влияющее на степень удобства: с SQL СУБД часто бывает удобнее работать на ее родном языке, без посредников.
Тут ты сильно неправ. В SQL нету средств декомпозиции. Я писал Linq запросы, которые в SQL давали в 5-6 раз больший выхлоп после оптимизации текста. В linq я разбил запрос на кусочки, потом из них собрал нужный мне результат.
Re[8]: Строго типизированная работа с SQL-ем напрямую без LI
Здравствуйте, gandjustas, Вы писали:
G>Это ты не понимаешь.
Не конструктивно. С какой целью пишешь такие неконструктивные вещи?
G>А что мне мешает руками создавать ET? Компилятор только во многом упрощает жизнь, но отнюдь не лишает возможностей.
Так речь и идет об “упрощении жизни”. Любую программу (в том числе формирование ET) можно написать в виде последовательности нулей и единиц. Возможность есть всегда.
G>В SQL нету средств декомпозиции.
Средства декомпозиции есть в T4 и в StringBuilder-е (можно и Razor попробовать).
G>В SQL нету средств декомпозиции.
В SQL изначально есть замкнутость: результат запроса может участвовать в качестве источника для другого запроса (подзапроса).
G>... В linq я разбил запрос на кусочки, потом из них собрал нужный мне результат.
Строку можно собирать хоть посимвольно.
Re[9]: Строго типизированная работа с SQL-ем напрямую без LI
Здравствуйте, Alexander Polyakov, Вы писали:
G>>В SQL нету средств декомпозиции. AP>Средства декомпозиции есть в T4 и в StringBuilder-е (можно и Razor попробовать).
Тем не менее это текстовая генерация запросов, со всеми вытекающими.
Статическая проверка при компиляции для определенного заранее набора запросов.
G>>В SQL нету средств декомпозиции. AP>В SQL изначально есть замкнутость: результат запроса может участвовать в качестве источника для другого запроса (подзапроса).
Это тут причем?
G>>... В linq я разбил запрос на кусочки, потом из них собрал нужный мне результат. AP>Строку можно собирать хоть посимвольно.
Ага, только гарантировать что-либо при этом невозможно.
Re[10]: Строго типизированная работа с SQL-ем напрямую без L
Здравствуйте, gandjustas, Вы писали:
G>>>В SQL нету средств декомпозиции. AP>>Средства декомпозиции есть в T4 и в StringBuilder-е (можно и Razor попробовать). G>Тем не менее это текстовая генерация запросов, со всеми вытекающими.
Это текстовая генерация запросов со статической проверкой при компиляции, со всеми вытекающими удобствами.
G>Статическая проверка при компиляции для определенного заранее набора запросов.
И что?
G>>>В SQL нету средств декомпозиции. AP>>В SQL изначально есть замкнутость: результат запроса может участвовать в качестве источника для другого запроса (подзапроса). G>Это тут причем?
С учетом возможности декомпозиции строк это обеспечивает соответствующую декомпозицию запросов.
G>Ага, только гарантировать что-либо при этом невозможно.
Как раз возможна статическая проверка, которая гарантирует валидность запросов.
Re[11]: Строго типизированная работа с SQL-ем напрямую без L
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Здравствуйте, gandjustas, Вы писали:
G>>>>В SQL нету средств декомпозиции. AP>>>Средства декомпозиции есть в T4 и в StringBuilder-е (можно и Razor попробовать). G>>Тем не менее это текстовая генерация запросов, со всеми вытекающими. AP>Это текстовая генерация запросов со статической проверкой при компиляции, со всеми вытекающими удобствами.
"Текстовая" и "удобства" — взаимоисключающие параграфы.
G>>Статическая проверка при компиляции для определенного заранее набора запросов. AP>И что?
И что что это по сути не дает возможностей декомпозиции, за счет которой Linq рулит.
G>>>>В SQL нету средств декомпозиции. AP>>>В SQL изначально есть замкнутость: результат запроса может участвовать в качестве источника для другого запроса (подзапроса). G>>Это тут причем? AP>С учетом возможности декомпозиции строк это обеспечивает соответствующую декомпозицию запросов.
"Декомпозиция строк" надо записать.
Как ты сделаешь проекции на строках?
G>>Ага, только гарантировать что-либо при этом невозможно. AP>Как раз возможна статическая проверка, которая гарантирует валидность запросов.
Для динамически склеиваемых в рантайме — нет.
То что ты предлагаешь — автоматизировать процесс генерации набора запросов и проверять их. Но это далеко не дотягивает до Linq.
Re[12]: Строго типизированная работа с SQL-ем напрямую без L
Здравствуйте, gandjustas, Вы писали:
G>"Текстовая" и "удобства" — взаимоисключающие параграфы.
Глупость, в реальности не так однозначно. Обратный пример: Web Forms -- попытка несколько удалиться от текста -- слила MVC, которая ближе к тексту.
G>>>Статическая проверка при компиляции для определенного заранее набора запросов. AP>>И что? G>И что что это по сути не дает возможностей декомпозиции, за счет которой Linq рулит.
Выше я уже писал, что декомпозиция присутствует.
G> "Декомпозиция строк" надо записать. G>Как ты сделаешь проекции на строках?
SELECT
A
FROM
(<#= Table1 #>) Table1
<#+ string Table1 { get { return ToString(() => { #>
SELECT
A,
B
FROM
Table1
<#+ });}} #>
G>Для динамически склеиваемых в рантайме — нет.
Строки склеиваются динамически в рантайме, да.
G>... Но это далеко не дотягивает до Linq.
Давай посмотрим, что до чего не дотягивает, приведи хотя бы схематично решение примера из первого поста.
Re[13]: Строго типизированная работа с SQL-ем напрямую без L
Здравствуйте, Alexander Polyakov, Вы писали:
AP>Здравствуйте, gandjustas, Вы писали:
G>>"Текстовая" и "удобства" — взаимоисключающие параграфы. AP>Глупость, в реальности не так однозначно. Обратный пример: Web Forms -- попытка несколько удалиться от текста -- слила MVC, которая ближе к тексту.
Уходишь от темы.
G>>>>Статическая проверка при компиляции для определенного заранее набора запросов. AP>>>И что? G>>И что что это по сути не дает возможностей декомпозиции, за счет которой Linq рулит. AP>Выше я уже писал, что декомпозиция присутствует.
Надо бы доказать.
G>> "Декомпозиция строк" надо записать. G>>Как ты сделаешь проекции на строках? AP>
AP>SELECT
AP> A
AP>FROM
AP> (<#= Table1 #>) Table1
AP><#+ string Table1 { get { return ToString(() => { #>
AP>SELECT
AP> A,
AP> B
AP>FROM
AP> Table1
AP><#+ });}} #>
AP>
И что? Возможностей композиции я тут не вижу. Напрмиер я беру запрос X и накладываю на него свою проекцию, а в базу уходит только выбор необходимых полей, с учетом derived table, подзапросов и тому подобной фигни.
G>>Для динамически склеиваемых в рантайме — нет. AP>Строки склеиваются динамически в рантайме, да.
Ну давай сначала. Покажи код, который в post-compile time сможет проверить корректоность запроса, если он клеится из кусков строк в Runtime.
G>>... Но это далеко не дотягивает до Linq. AP>Давай посмотрим, что до чего не дотягивает, приведи хотя бы схематично решение примера из первого поста.
Честно говоря лениво реверсить SQL. Уверен что в Linq можно сделать тоже самое, если не упрется в ограничения провайдера.
Если использовать Linq то в конце процедуры у нас гарантированно корректный запрос.
Если клеим строки то гарантировать что-либо практически невозможно.
Re[14]: Строго типизированная работа с SQL-ем напрямую без L
Здравствуйте, gandjustas, Вы писали:
G>И что? Возможностей композиции я тут не вижу. Напрмиер я беру запрос X и накладываю на него свою проекцию, а в базу уходит только выбор необходимых полей, с учетом derived table, подзапросов и тому подобной фигни.
Напиши пример на linq, я его перепишу на T4.