Сравнение Controllable Query с Dapper

6yrop

В соседнем треде сравнение Controllable Query с ADO.NET вызывает массу не относящихся к делу моментов. Поэтому предлагаю для сравнения использовать уже “рафинированный” ADO.NET, т.е. проект Dapper (ближайший аналог для Java DbUtils). Dapper имеет авторитет, поскольку на нем работает stackoverflow.com

//Dapper
connection.Query<Dog>("select Age = @Age, Id = @Id", new {Age = 1, Id = Guid.NewGuid});

//ControllableQuery
new {Age = 1.Param Id = Guid.NewGuid.Param}.Invoke(
p => "select Age = @Age, Id = @Id".Query<Dog>(new {p.Age, p.Id}.Execute(connection);

Получаем лишние слова Param, Invoke, Execute и каждый параметр упоминается дважды. Замечу, что упоминание параметров дважды является будничным делом, вы такое проделываете при объявлении/вызове любого метода. Вопрос: это сильно большая плата за автоматическую проверку, find usages и простой рефакторинг базы? Где здесь можно запутаться? Оверхед по количеству букв есть, но сложности, генерирующей запутанность, нет.

6yrop

каждый параметр упоминается дважды
для не динамических запросов это не требуется:
 

new {Age = 1.Param Id = Guid.NewGuid.Param}.Invoke(
p => "select Age = @Age, Id = @Id".Query<Dog>(p.Execute(connection);

zorin29

но сложности, генерирующей запутанность, нет
Если ты так говоришь, не могу спорить.
Хотя для меня первая строчка выглядит логично, а вот вторая — извращенно.
Первую строчку я могу прочитать, как если бы это было на естественном языке:
"у connection-а вызвать query, возвращающий Dog, с SQL телом <SQL> и параметрами {Age=1, Id = Guid.NewGuid}"
Вторая строчка:
"Взять объект с полями {Age = 1 как параметр, Id = Guid.NewGuid как параметр} , затем у него вызвать Invoke с делегатом, возвращающим строчку <SQL>, ой нет, не строчку, а ее extension-метод, превращающий ее в Query, возвращающую Dog, с параметром {Age=Age, Id = Id}, получится какая-то исполняемая хреновина, которую затем надо исполнить над connection-ом."
То есть надо читать не слева направо, не справа налево, а как-то шиворот-навыворот и кверху ногами: тело запроса в середине, значения параметров в начале, а connection в конце.
Кстати, не совсем понял, у тебя что, привносится extension-method object.Invoke( ... ) ?

kokoc88

То есть надо читать не слева направо, не справа налево, а как-то шиворот-навыворот и кверху ногами: тело запроса в середине, значения параметров в начале, а connection в конце.
Слушай, круто ты всё расписал. Подскажи котанам, а как ты это интерпретируешь?
oreach (var record in QExecutor.MaterializeReader(
command => new {
BillOfMaterialsHeaderId = command.Param => int.Parse(billOfMaterialsHeaderId
ModelPublicationId = command.Param => int.Parse(modelPublicationId
ServicePartsPageId = command.Param => int.Parse(servicePartsPageId
redCondition,
redOn = Func.Invoke(
=> {
if (redCondition)
{
Console.Write("LanguageCode=");
var languageCode = Console.ReadLine;
Console.Write("ModelId=");
var modelId = Console.ReadLine;
Console.Write("BlueCondition=");
var blueCondition = bool.Parse(Console.ReadLine;
return new {
languageCode = command.Param(languageCode
modelId = command.Param => int.Parse(modelId
blueCondition
}.AsOption;
}
else
return new {
languageCode = default(IParam<string>
modelId = default(IParam<int>
blueCondition = default(bool)
}.Nothing;
})
},
p => {
var q = new Q(@"
FROM BillOfMaterialsLine
WHERE BillOfMaterialsHeaderId = ")._(p.BillOfMaterialsHeaderId)._(@"
AND ( ( BillOfMaterialsLine.MainLinkExcludedFrom = 0
AND EXISTS ( SELECT 1
FROM MainPartLink
WHERE MainPartLink.PartNumber = BillOfMaterialsLine.PartNumber
AND MainPartLink.ModelPublicationId = ")._(p.ModelPublicationId)._(@"
AND MainPartLink.ServicePartsPageId <> ")._(p.ServicePartsPageId)._(@" )
)");
p.redOn.Some(
@on => {
q._(@"
OR ( BillOfMaterialsLine.InnerLinkExcludedFrom = 0
AND EXISTS ( SELECT 1
FROM InnerPartLink
INNER JOIN Model
ON InnerPartLink.ModelCode = Model.ModelCode
AND InnerPartLink.ModelSequence = Model.ModelSequence");
if (on.blueCondition)
q._(@"
INNER JOIN Publication
ON InnerPartLink.PublicationId = Publication.PublicationId");
q._(@"
INNER JOIN LocalizedPublication
ON InnerPartLink.PublicationId = LocalizedPublication.PublicationId
AND LocalizedPublication.LanguageCode = ")._(on.languageCode)._(@"
WHERE InnerPartLink.PartNumber = BillOfMaterialsLine.PartNumber
AND Model.ModelId = ")._(on.modelId);
if (on.blueCondition)
q._(@"
AND Publication.FollowingPublicationId IS NULL");
q._(@" )
)");
});
return q._(@"
)").Select(
_ => new {
BillOfMaterialsLineId = _._<int>
PartNumber = _._<string>
});
}
Console.WriteLine("BillOfMaterialsLineId = {0}, PartNumber = {1}",
record.BillOfMaterialsLineId,
record.PartNumber);

zorin29

боюсь, если начну интерпретировать, то перейду на темную сторону.

kokoc88

боюсь, если начну интерпретировать, то перейду на темную сторону.
Да ладно, разве тёмная сторона - это сильно большая плата за автоматическую проверку, find usages и простой рефакторинг базы?
Рассмотри и другие плюсы, например, ты сможешь делать вот так:

zorin29

Энакин Скайуокер проходит инструктаж по Controllable Query

6yrop

О спасибо, мне кажется это начало конструктивного разговора. :)
По поводу естественного языка дам ссылку на статью Дейкстры On the foolishness of "natural language programming".
Вот так выглядит функция Invoke:

public static TResult Invoke<T1, TResult>(this T1 arg, Func<T1, TResult> func)
{
return func(arg);
}

Алгоритмы Controllable Query будут работать и в таком варианте:

new {connection, Age = 1.Param Id = Guid.NewGuid.Param}.Invoke(
p => p.connection.Query<Dog>("select Age = @Age, Id = @Id", new {p.Age, p.Id};

или с не анонимным методом:

DogQuery(connection, 1.Param Guid.NewGuid.Param;

static IEnumerable<Dog> DogQuery(IDbConnection connection, IParam<int> age, IParam<Guid> id)
{
return connection.Query<Dog>("select Age = @age, Id = @id", new {age, id});
}

Легко видеть, что вариант ControllableQuery отличается от варианта Dapper всего лишь на extract method плюс вызовы Param. Это утверждение является более формализованной формулировкой моего предыдущего утверждения “сложности, генерирующей запутанность, нет”.
Причем, если такой extract method не сделать или не вызвать Param, то query checker найдет запрос и сообщит нам о невозможности проверить запрос.

zorin29

По поводу естественного языка дам ссылку на статью Дейкстры On the foolishness of "natural language programming".
Ну да, Дейкстра пишет, что естественные языки слишком неоднозначны и неформализованы, чтобы на них программировать. Из этого же не следует, что я не должен хотеть читать выражения слева направо, а не "задом наперед, совсем наоборот".
К твоему фреймворку можно привыкнуть, конечно. Вот моя мама, к примеру, пишет на MUMPS и не жалуется:
PF   S %H=$H D YMD S %(9)=X,X=%DT["F"*2-1 I @("%I(1)*100+%I(2)"_$E("> <",X+2)_"$E(%(94,7)") S %I(3)=%I(3)+X
Q
TT D 7 S %I(1)=%M,%I(2)=%D,%I(3)=%Y K %M,%D,%Y Q
NOW S %H=$H,%H=$S($P(%H,",",2):%H,1:%H-1)
D TT S %=$P(%H,",",2) D S S %=X_$S(%:%,1:.24) Q
DMW S %=$S(X?1.N1"D":+X,X?1.N1"W":X*7,X?1.N1"M":X*30,+X=X:X,1:0)

Как его не причесывай, твой код выглядит все равно уродливее твоего же примера на Dapper. Не могу судить, насколько пресловутое удобство рефакторинга уравновешивает уродство.

Dasar

new {Age = 1.Param Id = Guid.NewGuid.Param}.Invoke(
6 p => "select Age = @Age, Id = @Id".Query<Dog>(new {p.Age, p.Id}.Execute(connection);
это же вроде можно свести до:

connection
.Prepare(p => "select Age = @Age, Id = @Id".Query<Dog>(p
.Execute(new {Age = 1.Param Id=Guid.NewGuid.Param

который более наглядный.
почему так не сделать?

6yrop

это же вроде можно свести до:
code:--------------------------------------------------------------------------------
connection
.Prepare(p => "select Age = @Age, Id = @Id".Query<Dog>(p
.Execute(new {Age = 1.Param Id=Guid.NewGuid.Param
--------------------------------------------------------------------------------
который более наглядный.
да, вроде можно. Ты думаешь это не назовут "шиворот-навыворот и кверху ногами"? Существует какой-нибудь формализованный критерий наглядности, который можно было бы аргументировано отстаивать при возникновении возражений?
Обобщение на динамический случай:

connection
.Prepare(p => "select Age = @Age" + flag?"+1":"" + ", Id = @Id".Query<Dog>(new {p.Age, p.Id}
.Execute(new {flag, Age = 1.Param Id=Guid.NewGuid.Param

Это нормально?

Dasar

Query<Dog>(new {p.Age, p.Id})
почему здесь нельзя просто написать p? зачем явное указание полей? о них же и так уже известно из строки запроса

6yrop

ты предлагаешь парсить запрос?

Dasar

Существует какой-нибудь формализованный критерий наглядности, который можно было бы аргументировано отстаивать при возникновении возражений?
код должен читаться слева направо, сверху вниз.
весь процесс должен подчиняться одной метафоре (например, выполняем операции над множеством записей).
после каждой "элементарной" операции должно быть внятное понимание, что есть "на руках" (отфильтрованное множество записей, упорядоченное множество и т.д.)
зы
примеры даны на примере linq

6yrop

почему здесь нельзя просто написать p? зачем явное указание полей? о них же и так уже известно из строки запроса
потом список параметров в общем случае не линеен, а дерево. Например, если передаем не bool, а Option.

6yrop

код должен читаться слева направо, сверху вниз.
Вызов функций этому подчиняется?

var a = a;
var b = b(a);
var c = c(b);

а можно так
 

c(b(a

Было abc стало cba.

Dasar

Вызов функций этому подчиняется?
c(b(a
вложенные вызовы функций этому не соответствуют. Поэтому я не советую использовать длинные последовательности вложенных вызовов функций.

var a = a;
var b = b(a);
var c = c(b);

это читается намного легче

Dasar

ты предлагаешь парсить запрос?
можно парсить и тогда будет доп. проверка, причем это можно делать на этапе разработки.
можно тупо брать из p все поля, которые типа Param (или как они там у тебя называются)

6yrop

вложенные вызовы функций этому не соответствуют. Поэтому я не советую использовать длинные последовательности вложенных вызовов функций.

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

ava3443

взял и переопределил все ключевые слова
во, со стороны кажется что именно этим ты и занимаешься с Controllable Query :)

6yrop

можно тупо брать из p все поля, которые типа Param (или как они там у тебя называются)
тип IParam может быть где-нибудь в середине дерева, например, IOption<IParam<int>>

6yrop

код должен читаться слева направо, сверху вниз.
весь процесс должен подчиняться одной метафоре (например, выполняем операции над множеством записей).
после каждой "элементарной" операции должно быть внятное понимание, что есть "на руках" (отфильтрованное множество записей, упорядоченное множество и т.д.)
Мой взгляд на это такой. В первую очередь должны быть соблюдены правила хорошего стиля кодирования: решение поставленной задачи, DRY, не злоупотребление mutable состоянием и т.п. Множество решений, удовлетворяющих этим условиям, как правило, образуют класс изоморфности. В том смысле, что решения из этого класса переводятся друг в друга формальным рефакторингом. Опытный программист должен понимать эту изоморфность и не делать особых предпочтений какому-либо элементу из класса изоморфности. Например, вот кода изоморфны.
 
примеры даны на примере linq

linq to sql это представление кода в виде data structures (раздел 4.6 C# Language Specification). Если не знать этого, а обманываться "выполняем операции над множеством записей", то далеко не продвинешься.

zorin29

Опытный программист должен понимать эту изоморфность и не делать особых предпочтений какому-либо элементу из класса изоморфности.
Например, код до обфускации эквивалентен коду после обфускации. Значит ли это, что опытный программист не должен особо предпочитать один вид кода другому?

Bibi

Ты думаешь это не назовут "шиворот-навыворот и кверху ногами"?
ты странный.

6yrop

ты странный.
т.е тебе этот вариант нравится? Если, да, то это хорошо.

6yrop

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

zorin29

В первую очередь должны быть соблюдены правила хорошего стиля кодирования (...). Множество решений, удовлетворяющих этим условиям, как правило, образуют класс изоморфности. В том смысле, что решения из этого класса переводятся друг в друга формальным рефакторингом.
Вроде как, из твоих слов получается, что "формальный рефакторинг" не должен нас выводить за пределы "класса изоморфности".
Я тебе привел извращенный пример формального рефакторинга: обфускация. Этот контрпример показывает, что всего лишь переименованием идентификаторов и добавлением/удалением пробельных символов можно вывести код за пределы класса "хороших, годных решений задачи".
А значит, не всякий рефакторинг сохраняет "хорошесть, годность" кода. Множество хороших решений задачи не замкнуто относительно рефакторинга.
Следовательно, ты пока не доказал, что твои кадавры для опытных программистов должны быть приемлемы.
Your move.

kokoc88

Следовательно, ты пока не доказал, что твои кадавры для опытных программистов должны быть приемлемы.
Круто, на форумлокал занимаются формальным доказательством того, что гавнокод на самом деле является гавнокодом.
Интересно, будет ли Шурик в этот раз делать высер писать статью?
Кстати, а что мы будем делать, если он формально докажет, что его гавнокод не гавнокод?
Я предлагаю в этом случае отправить его на костёр, а то потом он примется доказывать, что Земля - это шар!11

zorin29

Круто, на форумлокал занимаются формальным доказательством того, что гавнокод на самом деле является гавнокодом.
[зануда-mode] Я не доказываю ничего, а лишь указываю на ошибку в доказательстве противоположного факта.

6yrop

Мои слова соответствуют следующей картинке:

kokoc88

Мои слова соответствуют следующей картинке:
А слова читателей раздела - этой:

6yrop

Я хочу найти опытных программистов, с которыми можно было бы обсудить, например, следующее. Касательно sql-параметров чистый ADO.NET и Dapper/Dapper.Contrib(*1) находятся в разных областях изоморфности. Хотелось бы обсудить причины этого. И послушать оценки практической значимости этих причин.
(*1) можно рассмотреть еще ряд подобных библиотек

Dasar

Множество решений, удовлетворяющих этим условиям, как правило, образуют класс изоморфности. В том смысле, что решения из этого класса переводятся друг в друга формальным рефакторингом.
Изоморфность всегда определяется, исходя из каких-то критериев. И то, что изоморфно исходя из одних критериев, будет не изоморфным исходя из других критериев.
В частности, человек (в отличии от машины) очень "не любит" операцию переопределения символов, хотя проще операции и быть не может.

kokoc88

"(I) Shurick" Не выдержала душа поэта. Запостите ему картинку, а то жалко, столько её рисовал...

6yrop

Первую строчку я могу прочитать, как если бы это было на естественном языке:
"у connection-а вызвать query, возвращающий Dog, с SQL телом <SQL> и параметрами {Age=1, Id = Guid.NewGuid}"
...
Как его не причесывай, твой код выглядит все равно уродливее твоего же примера на Dapper. Не могу судить, насколько пресловутое удобство рефакторинга уравновешивает уродство.
  

Вот такая форма вроде нормально читается на естественном языке:

//ControllableQuery
connection.Execute(p => "select Age = @Age, Id = @Id".ToQuery<Dog>(p new { Age = 1.Param Id = Guid.NewGuid.Param });

//Dapper
connection.Query<Dog>("select Age = @Age, Id = @Id", new { Age = 1, Id = Guid.NewGuid });

"На connection-е выполнить запрос, получаемый как результат анонимной функции, с параметрами {Age=1, Id = Guid.NewGuid}, запрос создается из sql-строки и указанных параметров, приходящих в аргументе анонимной функции."
Update: переставил аргументы в конец. Спасибо, .

Dasar

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

6yrop

параметры лучше в конец переставить.
сначала хочется видеть что делаем, а потом уже понятно что это за параметры, и чему они равны
по этому вопросу я метаюсь из стороны в сторону :confused: .
С одной стороны, да, твой аргумент имеет смысл. Да и запись будет более похожа на Dapper.
С другой стороны, когда я начал писать интерпретацию на естественном языке, предложение плохо строилось, поскольку "p" используется для построения запроса. Год назад Майк кричал, что параметры вторым аргументов это ужасно. Автокомплишен иногда сбивается, поскольку выражение еще не полностью написано и вывести тип трудно.

zorin29

Вот такая форма вроде нормально читается на естественном языке:
Да, такая лучше.

6yrop

я тут прикинул, вероятно получится сделать так. Для нединамического sql-я, можно убрать “p =>” и “Param”:

connection.Execute("select Age = @Age, Id = @Id".ToQuery<Dog>(new { Age = 1, Id = Guid.NewGuid };

или лучше так

"select Age = @Age, Id = @Id".ToQuery<Dog>(new { Age = 1, Id = Guid.NewGuid }).Execute(connection);

поскольку часто управление connection-ом будет вынесено, и будет просто .Execute или даже просто .ToList
В случае динамического sql-я появление лямбды “p =>” обусловлено самым базовым принципом работы алгоритма “статической типизации” . Этот алгоритм основан на выделение из всей программы фрагментов, которые можно вызвать автоматически, перебирая все возможные комбинации параметров. Лямбда и является таким фрагментом.
Появление Param тоже имеет чёткое объяснение (зачем пишется эта закорючка) в рамках парадигмы статической типизации. Алгоритм попытается для всех параметров перебрать все значения. Для многих типов (string, int) он не сможет этого сделать и сообщит об этом. Эти параметры должны транзитом проскочить исполняемый метод и уйти параметрами в sql сервер. Для таких транзитных параметров была введена специальная обертка IParam. Исполняемый метод не сможет для своей логики использовать значение, спрятанное под этой оберткой. Алгоритм может спокойно создать IParam, который пройдет через исполняемый метод.
То есть всё в точности так же, как при общении человека с компилятором.
Оставить комментарий
Имя или ник:
Комментарий: