[C#] Querying

6yrop

теперь можно писать вот так :D

static void Main
{
var modelCode = Console.ReadLine;
var modelSequence = Console.ReadLine;

foreach (var record in MaterializeReader(
_ =>
{
var builder = new StringBuilder(
@"SELECT
ModelPublication.PublicationId,
ModelPublication.ModelCode,
ModelPublication.ModelSequence
FROM
ModelPublication
WHERE
1 = 1");
if (_.ModelCode.HasValue) builder.Append(@"
AND ModelPublication.ModelCode = " + _.ModelCode.ValueOrEx);
if (_.ModelSequence.HasValue) builder.Append(@"
AND ModelPublication.ModelSequence = " + _.ModelSequence.ValueOrEx);
return builder.ToString.ToQuery(new
{
PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>
ModelSequence = default(NVarChar<L4000>)
});
},
context => new
{
ModelCode = modelCode.ToOptionIfNullOrEmpty.Select(_ => context.Param(_
ModelSequence = modelSequence.ToOptionIfNullOrEmpty.Select(_ => context.Param(_
}
{
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
record.PublicationId,
record.ModelCode,
record.ModelSequence);
}
}

все запросы автоматически находятся и проверяются. Можно прикрутить Find Usages и т.п. :D

kokoc88

теперь можно писать вот так
А можно не писать. Полная свобода действий.

6yrop

Я, конечно, понимаю, что ты просто тролишь. Но всё же прокомментирую. Как только ты начинаешь использовать метод MaterializeReader и при этом не используешь всякие дурацкие приемы типа рефлекшена , каста и т.п., то полной свободы уже нет, ты уже под контролем машины. Либо проверка запроса проходит успешно, и тогда запрос выполняется правильно и в основном рантайме, либо проверка падает, и тогда правим код.

kokoc88

ты уже под контролем машины
Я - ещё нет, но кто-то в разделе Development совершенно точно под контролем.

6yrop

очень остроумно

Dasar

AND ModelPublication.ModelCode = " + _.ModelCode.ValueOrEx);
здесь подставляется переменная или эскейптнутое значение?
все запросы автоматически находятся и проверяются
какие свойства проверяется?
Можно прикрутить Find Usages
к студии? в ней легко к существующему поиску добавить свой поиск символов? или ты теоретически?

kokoc88

здесь подставляется переменная или эскейптнутое значение?
Ты не читал сообщения в прошлой рекламной ветке этой великолепной, прозрачной и понятной каждому программисту по тексту кода, технологии? Разве тебе не очевидно, что получение свойства на самом деле подставляет его в виде параметра, например "@" + paramIndex++; а само значение сохраняется во что-то типа List<T> для использования во время вызова в ADO.NET? Ты что, ещё не под контролем машины?

Dasar

Ты не читал сообщения в прошлой рекламной ветке этой великолепной, прозрачной и понятной каждому программисту по тексту кода, технологии?
приоритеты у ТС были в порядке убывания:
весь код в "одном" исходнике
выразительность
проверяемость на уровне компиляции
отсутствие дублирования
все остальные: прозрачность, красивость и т.д.
и при таком порядке приоритетов понятно почему не удается достичь максимально понятного кода.
осуждать за выбор порядка приоритетов мне представляется неправильным, потому что:
во-первых: нет объективных критериев для определения, а как лучше,
во-вторых: на его задачах и при его подходе к решению задач - может именно такая расстановка приоритетов окупается больше всего.
> Разве тебе не очевидно, что получение свойства
по коду это не очевидно, и не обязано быть очевидным - если при этом есть утилита, которая это требование проверяет

kokoc88

весь код в "одном" исходнике
Конечно, весь код должен быть написан в одном файле MainClass.cs; а что, есть несогласные?
выразительность
Безусловно, это требование было выполнено на 100%.
проверяемость на уровне компиляции
Проверяемость строк "SELECT" на уровне компиляции C#. И я о том же.
отсутствие дублирования
Когда весь код в "одном" исходнике, какие тут могут быть дублирования?
и при таком порядке приоритетов понятно почему не удается достичь максимально понятного кода.

Точно, на 100% выразительный код - это код, не понятный никому.

kokoc88

по коду это не очевидно, и не обязано быть очевидным - если при этом есть утилита, которая это требование проверяет
Чёрт, я и не подумал, что утилита на этапе компиляции проверяет, что Console.ReadLine возвращает только строки без injection. Пойду заменю машину, которая меня контролирует.

Dasar

фактически, ты понял все приоритеты не тем образом, что они обозначают.

6yrop

здесь подставляется переменная или эскейптнутое значение?

при первом обращении к ToString значение параметра добавляется в коллекцию sql параметров, ToString возвращает название параметра. При повторных вызовах ToString просто возвращается название параметра. По-умолчанию параметрам даются имена @p1, @p2 и т.д. Есть возможность задавать имена.
какие свойства проверяется?
Во-первых, проверка запроса осуществляется SQL Server-ом — строка запроса отдается на выполнение в режиме FMTONLY ON (SchemaOnly). Во-вторых, SQL Server возвращает имена и типы колонок, что позволяет сделать проверку на полное совпадение с типом ожидаемого resultset-а. Реализация проверки типов sql параметров была отложена по причине не сильной востребованности и усложнения реализации.
к студии? в ней легко к существующему поиску добавить свой поиск символов? или ты теоретически?

Пока можно реализовать просто что-нибудь консольное. Задаешь имя таблицы и столбца, запускается консольное приложение и на выходе получаем список методов, где этот столбец/таблица используется. Это уже сейчас можно сделать за 1-2 дня. В цикле обхода запросов создаем хранимую процедуру и спрашиваем у SQL Server-а от каких объектов базы эта хранимая процедура зависит (SQL Serve 2008 предоставляет такую возможность).
Если помечтать, и представить, что этим заинтересуется JetBrains, то будет и проверка типов sql параметров и полная интеграция со студией: и find usages, и intellisense, и refactoring. :D

kokoc88

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

Dasar

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

kokoc88

так можно перейти к тому, что сначала необходимо каждое слово определить(вот только каким образом?) и только потом использовать.
Не знаю, может быть и можно; особенно если ты будешь хлебом называть сметану, а сметану морковкой.

Dasar

Во-первых, [..] Во-вторых, [..]
таким образом не получится поймать ошибку/опечатку вида
if (_.ModelSequence.HasValue) builder.Append(@"
AND ModelPublication.ModelSequence = " + _.ModelSequence);
при этом оно может обычно даже работать, если _ModelSequence обычно вводится как число

6yrop

таким образом не получится поймать ошибку/опечатку вида
if (_.ModelSequence.HasValue) builder.Append(@"
    AND ModelPublication.ModelSequence = " + _.ModelSequence);
сделал такую опечатку, запустил, проверка не прошла, выдав ошибку:
 

Message = 'Incorrect syntax near '`'.
Unclosed quotation mark after the character string 'PropertyExpression.ControllableQuery.IParam`1[System.String]'.'
QueryType = 'PropertyExpression.ControllableQuery.QueryExtensions+QueryImpl`1[<>f__AnonymousType1`3[System.Guid,PropertyExpression.ControllableQuery.Test.NVarChar`1[PropertyExpression.ControllableQuery.Test.L4000],PropertyExpression.ControllableQuery.Test.NVarChar`1[PropertyExpression.ControllableQuery.Test.L4000]]]'
LineNumber = '10'
SqlCommandTextWithLineNumbers =
1:SELECT
2: ModelPublication.PublicationId,
3: ModelPublication.ModelCode,
4: ModelPublication.ModelSequence
5:FROM
6: ModelPublication
7:WHERE
8: 1 = 1
9: AND ModelPublication.ModelCode = @p0
*10: AND ModelPublication.ModelSequence = PropertyExpression.Common.OptionalValue+ExistOptionalValue`1[PropertyExpression.ControllableQuery.IParam`1[System.String]]

Dasar

вот так проверь
if (_.ModelSequence.HasValue) builder.Append(@"
AND ModelPublication.ModelSequence = " + modelSequence);

6yrop

 

PropertyExpression.ControllableQuery.Toolkit.QueryCheckException was unhandled
Message=Method is not static Method = 'PropertyExpression.ControllableQuery.IQuery`1[<>f__AnonymousType1`3[System.Guid,PropertyExpression.ControllableQuery.Test.NVarChar`1[PropertyExpression.ControllableQuery.Test.L4000],PropertyExpression.ControllableQuery.Test.NVarChar`1[PropertyExpression.ControllableQuery.Test.L4000]]] <Main>b__0(<>f__AnonymousType0`2[PropertyExpression.Common.IOptionalValue`1[PropertyExpression.ControllableQuery.IParam`1[System.String]],PropertyExpression.Common.IOptionalValue`1[PropertyExpression.ControllableQuery.IParam`1[System.String]]])', DeclaringType = 'ConsoleApplication2.Program+<>c__DisplayClass4'.
Source=PropertyExpression.ControllableQuery.Toolkit
StackTrace:

kokoc88

PropertyExpression.ControllableQuery.Toolkit.QueryCheckException was unhandled
Message=Method is not static Method = 'PropertyExpression.ControllableQuery.IQuery`1[<>f__AnonymousType1`3[System.Guid,PropertyExpression.ControllableQuery.Test.NVarChar`1[PropertyExpression.ControllableQuery.Test.L4000],PropertyExpression.ControllableQuery.Test.NVarChar`1[PropertyExpression.ControllableQuery.Test.L4000]]] <Main>b__0(<>f__AnonymousType0`2[PropertyExpression.Common.IOptionalValue`1[PropertyExpression.ControllableQuery.IParam`1[System.String]],PropertyExpression.Common.IOptionalValue`1[PropertyExpression.ControllableQuery.IParam`1[System.String]]])', DeclaringType = 'ConsoleApplication2.Program+<>c__DisplayClass4'.
Source=PropertyExpression.ControllableQuery.Toolkit
StackTrace:
at PropertyExpression.ControllableQuery.Toolkit.QueryChecker.CheckMethod(MethodInfo methodInfo, ChoiceInvocationGetter choiceInvocationGetter, Action`1 action) in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\ControllableQuery.Toolkit\QueryChecker.cs:line 303
at PropertyExpression.ControllableQuery.Toolkit.QueryChecker.<>c__DisplayClass2a.<>c__DisplayClass2e.<CheckAllQueries>b__1f(MethodInfo resolveMethodInfo) in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\ControllableQuery.Toolkit\QueryChecker.cs:line 270
at PropertyExpression.ControllableQuery.Toolkit.QueryChecker.<>c__DisplayClass6a.<FindMethod>b__61 in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\ControllableQuery.Toolkit\QueryChecker.cs:line 588
at PropertyExpression.ControllableQuery.Toolkit.QueryChecker.<FindMethod>b__63(Action value) in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\ControllableQuery.Toolkit\QueryChecker.cs:line 645
at PropertyExpression.Common.OptionalValue.PerformanceFix`1.<>c__DisplayClass8.<Process>b__6(TValue value) in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\PropertyExpression.Common\OptionalValue.cs:line 129
at PropertyExpression.Common.OptionalValue.ExistOptionalValue`1.Process[TResult](Func`2 existFunc, Func`1 notExistFunc) in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\PropertyExpression.Common\OptionalValue.cs:line 204
at PropertyExpression.Common.OptionalValue.PerformanceFix`1.Process(IOptionalValue`1 optionalValue, Action`1 existAction, Action notExistAction) in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\PropertyExpression.Common\OptionalValue.cs:line 126
at PropertyExpression.Common.OptionalValue.PerformanceFix`1.ProcessValue(IOptionalValue`1 optionalValue, Action`1 existAction) in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\PropertyExpression.Common\OptionalValue.cs:line 144
at PropertyExpression.Common.OptionalValue.ProcessValue[TValue](IOptionalValue`1 optionalValue, Action`1 existAction) in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\PropertyExpression.Common\OptionalValue.cs:line 44
at PropertyExpression.ControllableQuery.Toolkit.QueryChecker.FindMethod(MethodBase methodInfo, Func`1 genericMethodArgumentsFunc, Action`1 foundToQueryMethodAction, Action`1 fountToNonQueryMethodAction, Action`1 foundToQueryRecordExampleMethodAction) in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\ControllableQuery.Toolkit\QueryChecker.cs:line 645
at PropertyExpression.ControllableQuery.Toolkit.QueryChecker.<>c__DisplayClass2a.<CheckAllQueries>b__1e(MethodInfo methodInfo) in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\ControllableQuery.Toolkit\QueryChecker.cs:line 275
at PropertyExpression.Common.EnumerableExtensions.ForEach[T](IEnumerable`1 enumerable, Action`1 action) in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\PropertyExpression.Common\EnumerableExtensions.cs:line 13
at PropertyExpression.ControllableQuery.Toolkit.QueryChecker.CheckAllQueries(String connectionString, QueryResultPropertyTypeChecker queryResultPropertyTypeChecker, ParamCreator paramCreator, Func`2 queryTypePredicate, Func`2 methodInfoPredicate, ChoiceInvocationGetter choiceInvocationGetter, IEnumerable`1 types) in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\ControllableQuery.Toolkit\QueryChecker.cs:line 263
at ConsoleApplication2.Program.CheckAllQueries in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\ConsoleApplication2\Program.cs:line 68
at ConsoleApplication2.Program.Main in E:\Projects\PropertyExpression\Tfs\ControllableQuery\DevBranches\Dev.Perf.E2.FalseCond.Anonym.ByName\Source\ConsoleApplication2\Program.cs:line 16
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart
InnerException:

Мастер Соблазнения Выразительности! Сразу понятно, в каком из десятка затронутых рефакторингом файлов и в какой строке произошла ошибка!1

6yrop

Мастер Соблазнения Выразительности! Сразу понятно, в каком из десятка затронутых рефакторингом файлов и в какой строке произошла ошибка!1
Да, у меня тоже была первая реакция немного поправить сообщение для лучшей читаемости. Но немного спокойствия и можно увидеть:
 
... <Main>b__0 ... DeclaringType = 'ConsoleApplication2.Program+<>c__DisplayClass4'  

kokoc88

Да, у меня тоже была первая реакция немного поправить сообщение. Но немного спокойствия и можно увидеть
Конечно, как же я сразу не понял, когда весь код написан в "одном" файле, есть только один единственный класс и один единственный метод Main. Но Великий, в методе Main может быть десять запросов, как же мне интерпретировать b__0? Ведь пока что я умею интерпретировать только O_o и o_O.

6yrop

Но Великий, в методе Main может быть десять запросов, как же мне интерпретировать b__0?
десять запросов в одном методе это редкий случай, и сейчас предлагается воспользоваться Reflector-ом для таких ситуаций. В будущем, возможно, будет добавлена интеграция с pdb файлами, и тогда будет выводиться номер строки.

kokoc88

сейчас предлагается воспользоваться Reflector-ом для таких ситуаций.
Воистину, нам сегодня открылась Великая Мудрость! Ибо кто мог бы предположить, что после ошибки запуска юнит тестов нужно декомпилировать код?!

6yrop

весь код написан в "одном" файле
кстати, смех смехом, а последние веянья моды как раз Micro ORM-ы в виде одного файла :D .

zorin29

теперь можно писать вот так
о боги... Я ведь тоже пишу на этом языке...

6yrop

о боги... Я ведь тоже пишу на этом языке...
покажи как ты пишешь

kokoc88

вот так проверь
if (_.ModelSequence.HasValue) builder.Append(@"
    AND ModelPublication.ModelSequence = " + modelSequence);
Ну, и где же твой следующий вопрос? Типа такого: нужно ввести в консоли третий параметр типа Да/Нет и в зависимости от значения выбирать или не выбирать столбец ModelSequence. Или что ты там придумываешь?

kokoc88

покажи как ты пишешь
Забей, он пишет как обычно.

Dasar

Message=Method is not static Method
и что ему не понравилось?

6yrop

и что ему не понравилось?
замыкание. Замыкания не разрешены.

Dasar

а параметры на что проверяется?
вот так написать можно:
context => new
{
ModelCode = modelCode.ToOptionIfNullOrEmpty.Select(_ => context.Param(_
ModelSequence = modelSequence.ToOptionIfNullOrEmpty.Select(_ => context.Param(_
zz = modelSequence
})

6yrop

а параметры на что проверяется?
вот так написать можно:
  context => new
     {
     ModelCode = modelCode.ToOptionIfNullOrEmpty.Select(_ => context.Param(_
     ModelSequence = modelSequence.ToOptionIfNullOrEmpty.Select(_ => context.Param(_
     zz = modelSequence
     })
такая запись не пройдет проверку. Точнее проверка сваливается в кастомный колбек, и в колбеке решаем для параметра данного типа либо кидаем исключение, либо предоставляем множество значений для параметра. В твоем примере тип String, поэтому кидаем исключение.

6yrop

Забей, он пишет как обычно.
От чего же, пусть напишет свой вариант, и посмотрим, чей код лучше принимает изменения, а также другие характеристики кода.

kokoc88

От чего же, пусть напишет свой вариант, и посмотрим, чей код лучше принимает изменения, а также другие характеристики кода.
Так ты для этого сделай изменения, о которых тебя попросили:
Типа такого: нужно ввести в консоли третий параметр типа Да/Нет и в зависимости от значения выбирать или не выбирать столбец ModelSequence.
Кроме этого, надо расширить список фильтрации; вполне реальная задача КЛАДР может потребовать 8 параметров.
И, конечно же, нужно ответить на вопрос про такую опечатку, и как ты её проверяешь:
if (_.ModelCode.HasValue) builder.Append("AND ModelPublication.ModelCode = " + _.ModelCode.ValueOrEx);
if (_.ModelSequence.HasValue) builder.Append("AND ModelPublication.ModelCode = " + _.ModelSequence.ValueOrEx);

Dasar

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

kokoc88

NVarChar<L4000>
Как задаётся и проверяется произвольная длина строки? Допустим, поле длиной 32 символа.
Что делать, если у меня две разных таблицы с разной длиной полей, но используются одинаковые запросы в обе эти таблицы?
Есть ли кроссплатформенность? Если у меня на сервере Oracle, а на клиенте встраивается SQLite, как будет работать эта библиотека?
Где задавать настройки базы? Чтобы на каждый отдельный запрос были отдельные: уровень изоляции транзакций, таймауты, и т.п.
Почему параметры и столбцы названы одинаково? Что будет с названиями столбцов при переименовании полей анонимных классов?
Что будет, если решается задача интеграции со сторонней системой, и в запросе необходимо сделать CAST? А если в запросе необходимо сделать GETUTCDATE или (синтаксис забыл) DATEDIFF(Model.CreationDate, hh, 4)?

6yrop

Типа такого: нужно ввести в консоли третий параметр типа Да/Нет и в зависимости от значения выбирать или не выбирать столбец ModelSequence.

static void Main
{
var modelCode = Console.ReadLine;
var modelSequence = Console.ReadLine;
var yesNo = Console.ReadLine;

foreach (var record in MaterializeReader(
_ =>
{
var builder = new StringBuilder(
@"SELECT
ModelPublication.PublicationId,
ModelPublication.ModelCode,
ModelPublication.ModelSequence
FROM
ModelPublication
WHERE
1 = 1");
if (_.ModelCode.HasValue) builder.Append(@"
AND ModelPublication.ModelCode = " + _.ModelCode.ValueOrEx);
if (_.IsYes && _.ModelSequence.HasValue) builder.Append(@"
AND ModelPublication.ModelSequence = " + _.ModelSequence.ValueOrEx);
return builder.ToString.ToQuery(new
{
PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>
ModelSequence = default(NVarChar<L4000>)
});
},
context => new
{
ModelCode = modelCode.IfNullOrEmptyToNothing.Select(_ => context.Param(_
ModelSequence = modelSequence.IfNullOrEmptyToNothing.Select(_ => context.Param(_
IsYes = yesNo == "Yes"
}
{
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
record.PublicationId,
record.ModelCode,
record.ModelSequence);
}
}

Кроме этого, надо расширить список фильтрации; вполне реальная задача КЛАДР может потребовать 8 параметров.

2^8 еще в рамках тупого перебора всех комбинаций. Граница после, которой перебор невозможен, где-то около 12 параметров. В таких случаях поступаем согласно идеологии обычных unit-тестов — разбиваем параметры на группы. Могу подготовить код примера для этого случая. Еще тут можно привести такие рассуждения. Запросов с >12 вариационных параметров в приложении не так много (у нас был один запрос). Как совсем крайний случай (реально такого не было) есть возможность исключить этот запрос из цикла перебора запросов и написать код вызова метода с запросом вручную, при этом, как и раньше, не потребуется подстановка реальных значений параметров.
И, конечно же, нужно ответить на вопрос про такую опечатку, и как ты её проверяешь:

Опечатка вот здесь “ModelPublication.ModelCode”? Тогда это не ловится, но оно также не ловится и в LINQ.

Dasar

значения выбирать или не выбирать столбец ModelSequence.
условие должно было добавиться в select, а не в where

kokoc88

if (_.IsYes && _.ModelSequence.HasValue) builder.Append(@"AND ModelPublication.ModelSequence = " + _.ModelSequence.ValueOrEx);
Ты нипонил!1 Не надо считывать из базы и гонять по сети поле ModelSequence; оно должно отсутствовать в SELECT.
Правильно ли я понимаю, что для модификации твоего кода в общем случае в функцию добавляется параметр, затем мы идём куда-то в конец функции прописать параметр в анонимный класс, а затем идём обратно вверх дописывать место, где его надо использовать?
2^8 еще в рамках тупого перебора всех комбинаций.
Не надо нам перебора, хочется видеть размер и структуру кода, в котором используется 8 параметров для фильтрации.
Опечатка вот здесь “ModelPublication.ModelCode”? Тогда это не ловится, но оно также не ловится и в LINQ.

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

Dasar

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

kokoc88

если клеить запросы без такой штуки, то потребуется тестить все виды инъекций на каждый параметр.
Если клеить их именно так, как тут описано. Сделай простую обёртку над тем же StringBuilder и протестируй её. Мы же сравниваем поделие автора с каким-то разумным и простым подходом, а не с лобовой копипастой.

Dasar

Мы же сравниваем поделие автора с каким-то разумным и простым подходом, а не с лобовой копипастой.
а что еще есть? linq, orm-ы и т.д.?
у них есть траблы с выразительностью. в частности, не всякий sql запрос можно через них выразить.

kokoc88

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

6yrop

условие должно было добавиться в select, а не в where
Ну например так:

static void Main
{
var modelCode = Console.ReadLine;
var modelSequence = Console.ReadLine;
var yesNo = Console.ReadLine;

var queryParamFunc = Func.New(
(QueryContext context) => new
{
ModelCode = modelCode.IfNullOrEmptyToNothing.Select(_ => context.Param(_
ModelSequence = modelSequence.IfNullOrEmptyToNothing.Select(_ => context.Param(_
});

if (yesNo == "Yes")
foreach (var record in MaterializeReader(
_ => (@"SELECT
ModelPublication.PublicationId,
ModelPublication.ModelCode,
ModelPublication.ModelSequence
" + BaseQueryString(_.ModelSequence, _.ModelCode.ToQuery(
new
{
PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>
ModelSequence = default(NVarChar<L4000>)
}
queryParamFunc)
)
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
record.PublicationId,
record.ModelCode,
record.ModelSequence);
else
foreach (var record in MaterializeReader(
_ => (@"SELECT
ModelPublication.PublicationId,
ModelPublication.ModelCode
" + BaseQueryString(_.ModelSequence, _.ModelCode.ToQuery(
new
{
PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>)
}
queryParamFunc)
)
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}'",
record.PublicationId,
record.ModelCode);
}

private static StringBuilder BaseQueryString(IOption<IParam<string>> modelCode, IOption<IParam<string>> modelSequence)
{
var builder = new StringBuilder(
@"FROM
ModelPublication
WHERE
1 = 1");
if (modelCode.HasValue) builder.Append(@"
AND ModelPublication.ModelCode = " + modelCode.ValueOrEx);
if (modelSequence.HasValue) builder.Append(@"
AND ModelPublication.ModelSequence = " + modelSequence.ValueOrEx);
return builder;
}

6yrop

еще можно поддержать композицию в LINQ стиле
 

static void Main
{
var modelCode = Console.ReadLine;
var modelSequence = Console.ReadLine;
var yesNo = Console.ReadLine;

var baseQueryFunc = Func.NewQueryContext context) => Func.Invoke(
_ =>
{
var builder = new StringBuilder(
@"SELECT
*
FROM
ModelPublication
WHERE
1 = 1");
if (_.ModelCode.HasValue) builder.Append(@"
AND ModelPublication.ModelCode = " + _.ModelCode.ValueOrEx);
if (_.ModelSequence.HasValue) builder.Append(@"
AND ModelPublication.ModelSequence = " + _.ModelSequence.ValueOrEx);
return builder.ToString.ToQuery<ModelPublication>
},
new
{
ModelCode = modelCode.IfNullOrEmptyToNothing.Select(_ => context.Param(_
ModelSequence = modelSequence.IfNullOrEmptyToNothing.Select(_ => context.Param(_
};

if (yesNo == "Yes")
foreach (var record in MaterializeReader(
_ => (@"SELECT
PublicationId,
ModelCode,
ModelSequence
FROM
(" + _.baseQuery + ") _").ToQuery(
new
{
PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>
ModelSequence = default(NVarChar<L4000>)
}
context => new { baseQuery = baseQueryFunc(context) })
)
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
record.PublicationId,
record.ModelCode,
record.ModelSequence);
else
foreach (var record in MaterializeReader(
_ => (@"SELECT
PublicationId,
ModelCode
FROM
(" + _.baseQuery + ") _").ToQuery(
new
{
PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>)
}
context => new { baseQuery = baseQueryFunc(context) })
)
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}'",
record.PublicationId,
record.ModelCode);
}

Это сильно надо?

6yrop

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

6yrop

в целом, конечно, страшновато, но неплохо с точки зрения проверяемости.
а что страшно то? как раз с каким-нибудь велосипедом, закрывающим функциональность SQL Server-а (ORM, LINQ, тем более самописные билдеры вот это да страшно. Я думаю, что этот подход выиграет даже у HaskellDB.

6yrop

Как задаётся и проверяется произвольная длина строки? Допустим, поле длиной 32 символа.
мепинг таких типов настраивается в каждом проекте как хочешь, можешь просто на String замепить. Но вот такая вещь как NVarChar<L4000> круто и вот почему. Если это поле скажем байндится к текстовому полю на UI-е, то получаем возможность валидировать длину без дополнительных усилий. Еще пример, данные выгружаются/загружаются в/из XML, опять получаем возможность проверять длину поля, а также генерировать XSD схему, в которой отображается длинна поля. Это вкусно, поскольку данные можно переливать через промежуточные объекты (скажем DTO и при этом информация о длине поля сохраняется и проверяется компилятором.
Что делать, если у меня две разных таблицы с разной длиной полей, но используются одинаковые запросы в обе эти таблицы?

Это как-то криво, но, повторюсь, можно промепить на просто String.
Есть ли кроссплатформенность? Если у меня на сервере Oracle, а на клиенте встраивается SQLite, как будет работать эта библиотека?

кросСУБДшностью не занимался, поскольку в реальности крайне редкий сценарий (этот критерий используется чисто в рекламных целях ORM). Но принципиальных сложностей поддержать другие СУБД я пока не вижу, должно сработать и там.
Почему параметры и столбцы названы одинаково?

Как удобно было так и назвал. Совпадение случайное, скрытого смысла в этом нет.
Что будет с названиями столбцов при переименовании полей анонимных классов?

Проверка запросов упадет с ошибкой. Можно в SQL строке столбцу дать алиас через AS.
Что будет, если решается задача интеграции со сторонней системой, и в запросе необходимо сделать CAST? А если в запросе необходимо сделать GETUTCDATE или (синтаксис забыл) DATEDIFF(Model.CreationDate, hh, 4)?

поддерживаются любые выражения на SQL, поэтому проблем не вижу.

6yrop

Здесь мы до сих пор не увидели: запросы с агрегацией, вызовы хранимых процедур, кроссплатформенность, уровни изоляции транзакций, разные виды JOIN, полностью динамические запросы, несколько запросов в одной транзакции... можно долго перечислять.
в Controllable Query довольно мало кода, и он практически не закрывает ADO.NET, поэтому твои вопросы странные.... Да, документации пока нет....

6yrop

Ты нипонил!1 Не надо считывать из базы и гонять по сети поле ModelSequence; оно должно отсутствовать в SELECT.
ответил выше -ю
 
Правильно ли я понимаю, что для модификации твоего кода в общем случае в функцию добавляется параметр, затем мы идём куда-то в конец функции прописать параметр в анонимный класс, а затем идём обратно вверх дописывать место, где его надо использовать?

Да, правильно. Анонимный класс можно сделать не внизу а вверху, но коллеги выбрали "внизу", они сказали, что так похоже на sp_sqlexec (первый вариант был "вверху"). Это не входит в Conrollable Query это всё кастомный код, для каждого проекта может быть свой вариант.
 
Не надо нам перебора, хочется видеть размер и структуру кода, в котором используется 8 параметров для фильтрации.

Что значит 8 полей, они все похожие? Тогда не понятно, у тебя есть пример с 2 полями и ты не можешь представить как будет с 8-ю? Или ты хочешь показать, что это будет сильно громоздко? Ок, я могу не полениться и написать, как назвать поля Field1, Field2, ... Field8 подойдет?
 
То есть при использовании твоей библиотеки всё равно надо написать юнит тесты, которые одновременно покроют всё, что делает твоя библиотека.

Нет, можно писать, можно не писать. Проверка запросов и ручные unit-тесты дополняют друг друга, также как статическая типизация и unit-тесты дополняют друг друга.

6yrop

с каким-то разумным и простым подходом
видимо, ты еще новичок в этом вопросе. Разумного и простого подхода просто нет :).

ava3443

кросСУБДшностью не занимался, поскольку в реальности крайне редкий сценарий (этот критерий используется чисто в рекламных целях ORM). Но принципиальных сложностей поддержать другие СУБД я пока не вижу, должно сработать и там.
Да, мы тоже думали что Oracle всех устроит, и в общем-то десять лет всё было ок, пока на прошлой неделе не напоролись на заказчика, успешно использующего Parallel Sysplex (active-active кластер DB/2 на z/OS с узлами, разнесёнными в два дата-центра в разных частях города). И всё, нашла коса на камень, т.к. сделать что-либо сравнимое по надёжности на Oracle уже не получится, да и не нужно это заказчику - слезать с проверенного и отлично работающего решения и прокладывать новую тропу, отважно наступая на все грабли.
Слышать про ненужность кроссплатформенности уже даже не смешно - хочется пожалеть убогого.
P.S. Надо же, какой страшный код можно писать на C#.

Papazyan

Что это вообще за хрень?

6yrop

Слышать про ненужность кроссплатформенности уже даже не смешно - хочется пожалеть убогого.
Убогий тут, кажется, ты, поскольку даже читать не научился. Прочитай мой пост, там нет “ненужность кроссплатформенности”.
в общем-то десять лет всё было ок, пока на прошлой неделе

что ж, это подтверждает мои слова о том, что такое редко встречается. Принципиально кроссубдшность вполне реально поддержать. Есть непроверенный вопрос, как на Oracle работает DbCommand.ExecuteReader(CommandBehavior.SchemaOnly). Если работает нормально, то поддержка Oracle-а в Controllable Query делается за 1-2 дня. Если не совсем правильно работает, то можно найти парсер SQL-я для Oracle ну и немного повозиться, но если идет речь о крупном проекте, то это всё вполне реально.

kokoc88

мепинг таких типов настраивается в каждом проекте как хочешь, можешь просто на String замепить
Интересно видеть конкретику, как указать длину 32, 40, 100, 128...
Это как-то криво, но, повторюсь, можно промепить на просто String.
Да, когда это зависит от тебя, то криво. Что делать, если не зависит, и строки разной длины? Просто отключить валидацию по длине?
кросСУБДшностью не занимался, поскольку в реальности крайне редкий сценарий
SQLite на клиенте и интеграция в большой компании - крайне редкие сценарии?
Проверка запросов упадет с ошибкой. Можно в SQL строке столбцу дать алиас через AS.
Нельзя автоматически переименовать поле?

kokoc88

Нет, можно писать, можно не писать. Проверка запросов и ручные unit-тесты дополняют друг друга, также как статическая типизация и unit-тесты дополняют друг друга.
Можно не писать? Пока что, даже если включить сценарии с injection, юнит тесты будут меньше по размеру, чем обвязка твоей библиотеки.

kokoc88

Ну например так:
То есть задча выбрать или не выбирать столбец превратилась в копипасту? Я боюсь спросить, если придётся выбирать или не выбирать два столбца, то мы напишем 4 варианта кода?

6yrop

Просто отключить валидацию по длине?
да.
SQLite на клиенте и интеграция в большой компании - крайне редкие сценарии?

Ok. Ты работаешь с SQLite? Можешь запустить DbCommand.ExecuteReader(CommandBehavior.SchemaOnly) и распечатать результаты? Могу прислать полный код, если будешь запускать.
Нельзя автоматически переименовать поле?

Пока JetBrains не поддержала Controllable Query ;) , да, нельзя. Но твой пример переименования не так интересен, поскольку всё же базовыми являются имена в запросе.

6yrop

То есть задча выбрать или не выбирать столбец превратилась в копипасту? Я боюсь спросить, если придётся выбирать или не выбирать два столбца, то мы напишем 4 варианта кода?
там почти нет копипасты. Или ты считаешь два разных типа resultset-а порождают копипаст? Ты хочешь один тип resultset-а? Укажи что именно ты считаешь копипастой? Или ты хочешь на Dynamic-ки перейти?

kokoc88

да.
Интересно видеть конкретику, как указывать разную длину для валидации: 32, 40, 100, 128... Завтра заказчик попросит либо ещё какую-то неизвестную цифру; либо поправить существующую, уменьшив длину со 128 до 64.

6yrop

1. Можно вообще не использовать длину, ни где.
2. Если длинна используется, то добавляются вот такие классы

public class L128 : ILength { }
public class L64 : ILength { }

У нас сейчас в проекте таких классов 12 штук.

kokoc88

там почти нет копипасты. Или ты считаешь два разных типа resultset-а порождают копипаст? Ты хочешь один тип resultset-а? Укажи что именно ты считаешь копипастой? Или ты хочешь на Dynamic-ки перейти?
if (yesNo == "Yes")
foreach (var record in MaterializeReader(
_ => (@"SELECT
PublicationId,
ModelCode,

ModelSequence
FROM
(" + _.baseQuery + ") _").ToQuery(
new
{
PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>

ModelSequence = default(NVarChar<L4000>)
}
context => new { baseQuery = baseQueryFunc(context) })
)
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
record.PublicationId,
record.ModelCode,
record.ModelSequence);
else
foreach (var record in MaterializeReader(
_ => (@"SELECT
PublicationId,
ModelCode

FROM
(" + _.baseQuery + ") _").ToQuery(
new
{
PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>)

}
context => new { baseQuery = baseQueryFunc(context) })
)
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}'",
record.PublicationId,
record.ModelCode);
}

kokoc88

1. Можно вообще не использовать длину, ни где.
Ни где?
У нас сейчас в проекте таких классов 12 штук.
То есть если мне нужна валидация банковской анкеты на кредит, то этих классов надо написать ~100 штук.

6yrop

~100 штук
тебя смущает 100 строчек кода?

6yrop

я в Excel-е сделал это за полторы минуты:

public class L1 : ILength { }
public class L2 : ILength { }
public class L3 : ILength { }
public class L4 : ILength { }
public class L5 : ILength { }
public class L6 : ILength { }
public class L7 : ILength { }
public class L8 : ILength { }
public class L9 : ILength { }
public class L10 : ILength { }
public class L11 : ILength { }
public class L12 : ILength { }
public class L13 : ILength { }
public class L14 : ILength { }
public class L15 : ILength { }
public class L16 : ILength { }
public class L17 : ILength { }
public class L18 : ILength { }
public class L19 : ILength { }
public class L20 : ILength { }
public class L21 : ILength { }
public class L22 : ILength { }
public class L23 : ILength { }
public class L24 : ILength { }
public class L25 : ILength { }
public class L26 : ILength { }
public class L27 : ILength { }
public class L28 : ILength { }
public class L29 : ILength { }
public class L30 : ILength { }
public class L31 : ILength { }
public class L32 : ILength { }
public class L33 : ILength { }
public class L34 : ILength { }
public class L35 : ILength { }
public class L36 : ILength { }
public class L37 : ILength { }
public class L38 : ILength { }
public class L39 : ILength { }
public class L40 : ILength { }
public class L41 : ILength { }
public class L42 : ILength { }
public class L43 : ILength { }
public class L44 : ILength { }
public class L45 : ILength { }
public class L46 : ILength { }
public class L47 : ILength { }
public class L48 : ILength { }
public class L49 : ILength { }
public class L50 : ILength { }
public class L51 : ILength { }
public class L52 : ILength { }
public class L53 : ILength { }
public class L54 : ILength { }
public class L55 : ILength { }
public class L56 : ILength { }
public class L57 : ILength { }
public class L58 : ILength { }
public class L59 : ILength { }
public class L60 : ILength { }
public class L61 : ILength { }
public class L62 : ILength { }
public class L63 : ILength { }
public class L64 : ILength { }
public class L65 : ILength { }
public class L66 : ILength { }
public class L67 : ILength { }
public class L68 : ILength { }
public class L69 : ILength { }
public class L70 : ILength { }
public class L71 : ILength { }
public class L72 : ILength { }
public class L73 : ILength { }
public class L74 : ILength { }
public class L75 : ILength { }
public class L76 : ILength { }
public class L77 : ILength { }
public class L78 : ILength { }
public class L79 : ILength { }
public class L80 : ILength { }
public class L81 : ILength { }
public class L82 : ILength { }
public class L83 : ILength { }
public class L84 : ILength { }
public class L85 : ILength { }
public class L86 : ILength { }
public class L87 : ILength { }
public class L88 : ILength { }
public class L89 : ILength { }
public class L90 : ILength { }
public class L91 : ILength { }
public class L92 : ILength { }
public class L93 : ILength { }
public class L94 : ILength { }
public class L95 : ILength { }
public class L96 : ILength { }
public class L97 : ILength { }
public class L98 : ILength { }
public class L99 : ILength { }
public class L100 : ILength { }

6yrop

кое что из выделенного можно устранить. Скажи, как по-твоему, какой тип должна иметь переменная record в том и в другом случае.

6yrop

если решать именно поставленную задачу, то еще есть вот такой вариант:

static void Main
{
var modelCode = Console.ReadLine;
var modelSequence = Console.ReadLine;
var yesNo = Console.ReadLine;

var baseQueryFunc = Func.NewQueryContext context) => Func.Invoke(
_ =>
{
var builder = new StringBuilder(
@"SELECT
PublicationId,
ModelCode,
ModelSequence
FROM
ModelPublication
WHERE
1 = 1");
if (_.ModelCode.HasValue) builder.Append(@"
AND ModelPublication.ModelCode = " + _.ModelCode.ValueOrEx);
if (_.ModelSequence.HasValue) builder.Append(@"
AND ModelPublication.ModelSequence = " + _.ModelSequence.ValueOrEx);
return builder.ToString.ToQuery(
new
{
PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>
ModelSequence = default(NVarChar<L4000>)
});
},
new
{
ModelCode = modelCode.IfNullOrEmptyToNothing.Select(_ => context.Param(_
ModelSequence = modelSequence.IfNullOrEmptyToNothing.Select(_ => context.Param(_
};

if (yesNo == "Yes")
foreach (var record in MaterializeReader(baseQueryFunc
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
record.PublicationId,
record.ModelCode,
record.ModelSequence);
else
foreach (var record in MaterializeReader(
_ => (@"SELECT
PublicationId,
ModelCode
FROM
(" + _.baseQuery + ") _").ToQuery(
new
{
PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>)
}
context => new { baseQuery = baseQueryFunc(context) })
)
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}'",
record.PublicationId,
record.ModelCode);
}

ava3443

Что это вообще за хрень?
я вроде написал. и ссылку на википедию дал...
active-active кластер СУБД с узлами, разнесёнными в два дата-центра в разных частях города

Papazyan

Не, я про то, о чем тут разговор идет.

6yrop

кстати, еще вдогонку вопрос. Delegation pattern ты тоже считаешь копипастой:
 
    interface IA
    {
     string Method1(string arg1, int arg2);
     string Method2(int arg1, double arg2);
     string Method3(int arg2);
    }
    class B : IA
    {
     public B(IA a)
     {
     this.a = a;
     }
     private readonly IA a;
     public string Method1(string arg1, int arg2)
     {
     return a.Method1(arg1, arg2);
     }
     public string Method2(int arg1, double arg2)
     {
     return a.Method2(arg1, arg2);
     }
     public string Method3(int arg2)
     {
     return a.Method3(arg2);
     }
    }

    class C : IA
    {
     public C(IA a)
     {
     this.a = a;
     }
     private readonly IA a;
     public string Method1(string arg1, int arg2)
     {
     return a.Method1(arg1, arg2);
     }
     public string Method2(int arg1, double arg2)
     {
     return a.Method2(arg1, arg2);
     }
     public string Method3(int arg2)
     {
     return a.Method3(arg2);
     }
    }

  

?

Maurog

Delegation pattern ты тоже считаешь копипастой
сам паттерн не является копипастой, копипастой является его воплощение в языках типа C++, C#
засахарить это дело не так уж и сложно :grin:

6yrop

копипастой является его воплощение в языках типа C++, C#
засахарить это дело не так уж и сложно
да, Kotlin добавляет такой сахар. Но даже в C# этот копипаст не имеет большого негативного влияния, первоначально ReSharper сгенерирует вам все строчки, при последующем внесении изменений тоже всё примитивно и вероятность ошибки мала. Копипаст копипасту рознь. Одно дело вы копипастите какую-нибудь бизнес логику, и при развитии кода это разъезжается, и потом у заказчика в одном месте работает так, а в другом по другому. Тогда, да, такой копипаст зло. Но есть случаи, когда визуально выглядит как копипаст, а негативные последствия минимальны (контролируются компилятором и т.п.).

Dasar

а что страшно то?
а что страшно то?
спагетти в итоге получается: одновременно в перемешку используется четыре разных кода:
sql,
склейка строк,
условия на добавления строк,
подстановка параметров.
необходимо в ручную следить, чтобы sql для всех вариантов склейки был валидным
у linq подход по чище.
что-нибудь такое повеселее будет, хотя правда уже не совсем c#

db.ModelPublication
.where ModelPublication.ModelCode == modelCode when not-null
.where ModelPublication.ModelSequence == modelSequence when not-null
.Select ModelPublication.PublicationId,
ModelPublication.ModelCode when isYes ,
ModelPublication.ModelSequence

Dasar

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

     public string Method2(int arg1, double arg2)
     {
     return a.Method2(arg1, arg2);
     }

поменять везде на

     public string Method2(int arg1, double arg2, Option1 opt1)
     {
     return a.Method2(arg1, arg2, opt1);
     }

уже прифигеешь.

kokoc88

вот этот код поменять
Этот-то как раз просто: сменой сигнатуры метода.

Dasar

Этот-то как раз просто: сменой сигнатуры метода.
пока параметры просто передаются - да, а если еще заложена какая-то более сложная логика: перестановка аргументов, конвертация аргументация, условия и т.д., то уже..

6yrop

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

kokoc88

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

kokoc88

Не, я про то, о чем тут разговор идет.
Речь идёт про какое-то поделие Шурика, которое он безуспешно рекламирует в этом разделе. Коротко весь топик выражен в первых двух постах: можно так писать на C#, а можно не писать.

6yrop

ага, твои выводы на том же уровне как тогда, когда ты "как эксперт" обсуждал ASP.NET, но между делом выяснилось, что ты не знаешь, когда страница компилируется. В этом треде то же самое, по твоим вопросам можно судить насколько глубоко твое понимание обсуждаемого вопроса. Ты похож на наших друзей тестеров индусов, которые нифига не разбираются, но хорошо кликают по кнопочкам, и от них есть некоторая польза. И от тебя есть некоторая польза. :grin:

6yrop

тебе стоило написать про переименование столбца в коде сверху.
что именно ты хочешь?

kokoc88

когда ты "как эксперт" обсуждал ASP.NET
Это у тебя такое впечатление от темы, когда ты "как эксперт" пытался обсуждать другие технологии против ASP.NET, а выяснилось, что ты не знаешь ни того, ни другого?
Ты похож на наших друзей тестеров индусов
Так вот, кто тестирует ваши поделки. Обычно, когда я читаю "у нас работают индусы", то жалею тех, кто с ними работает. Но в этот раз мне жаль индусов.
И от тебя есть некоторая польза.
Спасибо. О тебе я такого сказать, к сожалению, не могу.

kokoc88

что именно ты хочешь?
Всё что я хотел, я узнал по теме выше. Понимающие люди, которые дочитали тему до конца, сделали свои выводы. Я все выводы сделал ещё во втором посте, кстати, там я ни капли тебя не троллил.

6yrop

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

kokoc88

Индусы нам навязаны свыше, как бы в нагрузку к проектам.
Меня всё-таки не покидает стойкое ощущение, что это вы навязаны индусам в нагрузку.

6yrop

спагетти в итоге получается: одновременно в перемешку используется четыре разных кода:
sql,
склейка строк,
условия на добавления строк,
подстановка параметров.
это мне напоминает нытье Фаулера, типа скриплеты это ужасно. Однако, лучшее на сегодняшний день (после многолетних экспериментов, в том числе, и в java мире) это ASP.NET MVC, и если бы в Razor нормально ляммбды поддержали, то лучше и не надо. С текстом удобно работать как с текстом :) , и не нужно накручивать абстракции, которые тут же начинают течь.
code:--------------------------------------------------------------------------------
db.ModelPublication
.where ModelPublication.ModelCode == modelCode when not-null
.where ModelPublication.ModelSequence == modelSequence when not-null
.Select ModelPublication.PublicationId,
ModelPublication.ModelCode when isYes ,
ModelPublication.ModelSequence

У меня есть вот такая диаграмма:

Maier’s approach:
"Whatever the database programming model, it must allow complex, data-intensive operations to be picked out of programs for execution by the storage manager, rather than forcing a record-at-a-time interface." David Maier. Representing database programs as objects.
Ты фактически предлагаешь при переходе 2 свернуть налево в "Data operation as data structure". Поддержать высокую степень композиции (составление запросов из кусочков) в этом подходе довольно сложно, такого еще никому не удалось. Не факт, что это вообще возможно. А если это и возможно, то, скорее всего, потребуется, чтобы язык программирования и СУБД разрабатывала одна команда, что не реально в современной ситуации.

6yrop

необходимо в ручную следить, чтобы sql для всех вариантов склейки был валидным
что значит в ручную следить?
Можно просто запустить чекинг запросов в режиме дебага и увидеть как клеится запрос:

Это похоже на то, как Фаулер пишет про работу с динамическими языками:
One day I found myself trying to follow some well-written Ruby code. I found the lack of type information on parameters made life difficult - I kept saying to myself 'what exactly do I have here?' I didn't find this so much of an issue in Smalltalk for two reasons: the excellent environment makes it easy to fire up a debugger ...

Dasar

Ты фактически предлагаешь при переходе 2 свернуть налево в "Data operation as data structure".
я предлагаю его использовать как основной подход.
вставка изредка отдельных кусков текста все-таки лучше, чем всегда писать текст
код следующего вида внятнее, чем ручная склейка текста

db.ModelPublication
.where ModelPublication.ModelCode == modelCode when not-null
.where ModelPublication.ModelSequence == modelSequence when not-null
.Select raw-sql "SUM(Zzz) OVER(PARTITION BY xxx)" as ModelSize,
ModelPublication.ModelCode when isYes ,
ModelPublication.ModelSequence

6yrop

foreach (var record in MaterializeReader(

это не является копипастой, поскольку итерация по коллекции разных типов.
(@"SELECT
PublicationId,
ModelCode,

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

static void Main
{
var modelCode = Console.ReadLine;
var modelSequence = Console.ReadLine;
var yesNo = Console.ReadLine;

var queryParamFunc = Func.New(
(QueryContext context) => new
{
ModelCode = modelCode.IfNullOrEmptyToNothing.Select(_ => context.Param(_
ModelSequence = modelSequence.IfNullOrEmptyToNothing.Select(_ => context.Param(_
});

if (yesNo == "Yes")
foreach (var record in MaterializeReader(
_ => QueryString(_.ModelSequence, _.ModelCode, builder => builder.Append(@",
ModelPublication.ModelSequence".ToString.ToQuery(
new
{
PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>
ModelSequence = default(NVarChar<L4000>)
}
queryParamFunc)
)
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
record.PublicationId,
record.ModelCode,
record.ModelSequence);
else
foreach (var record in MaterializeReader(
_ => QueryString(_.ModelSequence, _.ModelCode, builder => {}).ToString.ToQuery(
new
{
PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>)
}
queryParamFunc)
)
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}'",
record.PublicationId,
record.ModelCode);
}

private static StringBuilder QueryString(IOption<IParam<string>> modelCode, IOption<IParam<string>> modelSequence, Action<StringBuilder> modelSequenceAction)
{
var builder = new StringBuilder(
@"
SELECT
ModelPublication.PublicationId,
ModelPublication.ModelCode");
modelSequenceAction(builder);
builder.Append(@"
FROM
ModelPublication
WHERE
1 = 1");
if (modelCode.HasValue) builder.Append(@"
AND ModelPublication.ModelCode = " + modelCode.ValueOrEx);
if (modelSequence.HasValue) builder.Append(@"
AND ModelPublication.ModelSequence = " + modelSequence.ValueOrEx);
return builder;
}

FROM
(" + _.baseQuery + ") _").ToQuery(
new
{

этого нет в варианте выше.

PublicationId = default(Guid
ModelCode = default(NVarChar<L4000>

Это фактически определение типа для resultset-а, можно устроить наследование одного типа resultset-а от другого. Эта копипаста из разряда "без негативных последствий". Поэтому делать именованные типы и делать наследование не имеет смысла.
context => new { baseQuery = baseQueryFunc(context) })

этого нет в варианте выше.
Таким образом, копипасты "с негативными последствиями" нет.

Dasar

Можно просто запустить чекинг запросов в режиме дебага и увидеть как клеится запрос:
вариантов при этом 2^кол-во if-ов
также тебе пришлось добавлять where 1=1 для общности, что не во всех случаях проходить.

6yrop

вариантов при этом 2^кол-во if-ов
как написано у Фаулера выше, это только для подсказки на вопрос "what exactly do I have here?", тут не обязательно смотреть все варианты, это всего лишь подсказка (в случае поддержки JetBrains можно сделать просмотр всех уникальных вариантов).
также тебе пришлось добавлять where 1=1 для общности, что не во всех случаях проходить.

Это наиболее простой вариант для данного случая, если не работает, то можно по другому клеить строки, но всегда есть возможность склеить как требуется.

6yrop

Это наиболее простой вариант для данного случая, если не работает, то можно по другому клеить строки, но всегда есть возможность склеить как требуется.
Если мы свернули на путь "Data operation as data structure", то там обычно проблемы посерьезнее склейки строк: жертвуем производительностью, закрываем возможности СУБД, копипаст логики и т.д.

kokoc88

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

6yrop

Можно не писать? Пока что, даже если включить сценарии с injection, юнит тесты будут меньше по размеру, чем обвязка твоей библиотеки.
Напиши свой вариант кода. Запрос и тесты.

kokoc88

Напиши свой вариант кода. Запрос и тесты.
У меня другое предложение: сам напиши другой вариант кода, запрос и тесты. Это ты должен доказывать, что твоя библиотека хоть на что-то пригодна, а не наоборот.

6yrop

У меня другое предложение: сам напиши другой вариант кода, запрос и тесты. Это ты должен доказывать, что твоя библиотека хоть на что-то пригодна, а не наоборот.
Так и знал, что код не напишешь. Пока кода нет, твои слова выше выглядят как пустая болтовня.
Если запрос и тесты (Code0) напишу я, то это максимум, что докажет, что мой способ написания Code0 проигрывает коду с использование Controllable Query. Кому это надо?
При использовании Controllable Query для валидации запросов не требуются данные, сохраняемые в БД. Как с этим в твоих вручную написанных тестах?

kokoc88

Так и знал, что код не напишешь. Пока кода нет, твои слова выше выглядят как пустая болтовня.
Пока нет сравнения твоей технологии с нормальным качественным кодом, а не выблевом бешеной пальмовой обезъяны, твои слова выглядят ещё хуже, чем пустая болтовня.
Если запрос и тесты (Code0) напишу я, то это максимум, что докажет, что мой способ написания Code0 проигрывает коду с использование Controllable Query. Кому это надо?
Без этого нельзя до конца понять, умеешь ли ты нормально программировать. Пока что весь твой код в этой теме представляет из себя кучу write-only дерьма, отформатированного в стиле "бешеный Микки Маус".
При использовании Controllable Query для валидации запросов не требуются данные, сохраняемые в БД. Как с этим в твоих вручную написанных тестах?
Данные тебе в любом случае потребуются, мы это выяснили выше.

6yrop

Данные тебе в любом случае потребуются, мы это выяснили выше.
Нет, данные не требуются.

kokoc88

Нет, данные не требуются.
То есть ты без данных проверишь эту опечатку:
if (_.ModelCode.HasValue) builder.Append("AND ModelPublication.ModelCode = " + _.ModelCode.ValueOrEx);
if (_.ModelSequence.HasValue) builder.Append("AND ModelPublication.ModelCode = " + _.ModelSequence.ValueOrEx);

Не так давно ты сказал про неё:
Опечатка вот здесь “ModelPublication.ModelCode”? Тогда это не ловится, но оно также не ловится и в LINQ.

6yrop

Опечатка вот здесь “ModelPublication.ModelCode”? Тогда это не ловится, но оно также не ловится и в LINQ.
Да, это так.

kokoc88

Да, это так.
То есть ты без данных проверишь эту опечатку в юнит тестах?

6yrop

В теоретическом плане полагаю, что для задачи, которую ты сформулировал, решение существует. Если очень надо, то этим можно заняться. Однако, вернемся к вопросу, который обсуждаем здесь (это можно увидеть, если подняться на несколько уровней выше по треду, нажав 5 раз на линк “re:…”, начиная с твоего последнего сообщения):
При использовании Controllable Query для валидации запросов не требуются данные, сохраняемые в БД. Как с этим в твоих вручную написанных тестах?

kokoc88

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

6yrop

под твоей библиотекой
Controllable Query не требует наличие данных. Данные не нужны для работы Controllable Query.

kokoc88

Controllable Query не требует наличие данных. Данные не нужны для работы Controllable Query.
При использовании Controllable Query всё равно надо написать юнит тесты, которые одновременно покроют всё, что делает Controllable Query. А ещё они будут кроссплатформенные, не будут накладывать кучу ограничений, и т.п. При использовании Controllable Query нельзя написать юнит тесты без данных.

6yrop

При использовании Controllable Query всё равно надо написать юнит тесты, которые одновременно покроют всё, что делает Controllable Query.
Что значит надо? Есть разные мнения относительно объема unit-тестирования. Кто-то пишет много unit-тестов, кто-то меньше. Если вы оцениваете стоимость unit-тестов, то при этой оценке надо учитывать Controllable Query. Controllable Query может понизить ценность некоторых unit-тестов.
Еще Controllable Query найдет все запросы. При ручном unit-тестировании за покрытием тестами всех запросов следит человек. Людям свойственно ошибаться.
Вот тут ref1, ref2, ref3 сотрудники University of California занимаются “Static Checking of Dynamically Generated Queries in Database Applications”. Controllable Query делает по сути такую же проверку.

kokoc88

Кто-то пишет много unit-тестов, кто-то меньше.
Пока что мы выяснили, что в твоём случае либо тестов столько же; либо они по размеру меньше необходимой обвязки.
Еще Controllable Query найдет все запросы.

Конечно же, не найдёт. Потому что это поделие нельзя использовать для всех запросов. И даже когда оно будет готово хотя бы на 90%, а сейчас оно не готово и на 20%, ситуация не изменится.

6yrop

в обоих пунктах ты ошибаешься

6yrop

здесь подставляется переменная или эскейптнутое значение?
Если смущает , то вот прикрутил возможность добавлять параметры в стиле Dapper-а:

if (_.ModelCode.HasValue) builder.Append(@"
AND ModelPublication.ModelCode = @ModelCode", new { ModelCode = _.ModelCode.ValueOrEx });

Реализация заняла буквально три строчки:

public static StringBuilder Append<T>(this StringBuilder it, string value, T @params)
{
foreach (var propertyInfo in typeof (T).GetProperties
IParam) propertyInfo.GetValue(@params, null.ToSql(propertyInfo.Name);
return it.Append(value);
}

В релизе рефлекшен заменю на emit.

luna89

Если так хочется статической типизации, то можно все запросы завернуть в хранимки или вьюхи. Тогда правильность хранимок и вьюх будет контролировать СУБД(в компайл-тайм). Единственным нетипизированным местом останется вызов хранимок из C# кода. Но тут все просто: если сигнатура процедуры поменялась, то надо искать все ее вызовы и исправлять. Это делается банальным грепом.
Кстати, конкатенацию строк в таких простых случаях вообще можно убрать:

select * from foo where (:param1 is null or :param1 = foo.bar) and (:param2 is null or :param2 = foo.baz)

luna89

Можно прикрутить Find Usages

В случае оракла зависимость хранимок от таблиц можно вытаскивать из базы простейшим sql запросом.

kokoc88

В случае оракла
Какого ещё Оракла? Ты что, не знаешь, что не существует продуктов, выпущенных не Microsoft и не JetBrains?

6yrop

Если так хочется статической типизации, то можно все запросы завернуть в хранимки или вьюхи. Тогда правильность хранимок и вьюх будет контролировать СУБД(в компайл-тайм). Единственным нетипизированным местом останется вызов хранимок из C# кода
для не динамического sql-я таких решений полным полно, в этом случае C#-методы могут просто генерироваться по хранимым процедурам. Но в реальности часто требуется динамических sql. Не городили бы тогда LINQ.
 
select * from foo where (:param1 is null or :param1 = foo.bar) and (:param2 is null or :param2 = foo.baz)

это известный антипатерн, так делать не рекомендуется (не всегда подходит). По крайней мере, MS SQL Server генерирует не оптимальный план запроса. Как у других СУБД с этим, точно не скажу, но вроде это принципиальная трудность, связанная с кешированием планов запросов. Для того чтобы вырезать то или иное условие СУБД должна проанализировать запрос с учетом текущих параметров, а анализ запроса не делается, если план закеширован. Отказываться от кеширования планов запросов не всегда подходит.

6yrop

В случае оракла зависимость хранимок от таблиц можно вытаскивать из базы простейшим sql запросом.
в MS SQL Server тоже самое. Я об этом писал выше, когда рассказывал возможный алгоритм реализации Find Usages.

6yrop

  
уже не совсем c#

Тут дело не в конкретном языке, а в статической типизации. Вот такой код должен компилироваться или нет?
foreach (var record in db.ModelPublication
    .where ModelPublication.ModelCode == modelCode when not-null
    .where ModelPublication.ModelSequence == modelSequence when not-null
    .Select ModelPublication.PublicationId,
     ModelPublication.ModelCode when isYes,
     ModelPublication.ModelSequence)
{
    Console.WriteLine(record.ModelCode);
}
  

Естественно, интересует общий случай, когда на переменную “isYes” не накладываются никакие ограничения, т.е. “isYes” может быть параметром метода, результатом обращения к веб сервису и т.д.
Уникальность проекта Controllable Query как раз и состоит в том, что в нем удалось совместить динамические строки запросов и аналог статической типизации. Как отмечено в статье ref1
Мы полагаем, что отсутствие поддержки динамических запросов является основной причиной отказа от большинства форм встраиваемого SQL [27].
  

Dasar

Тут дело не в C#-е, а в статической типизации.
есть два варианта раскрытия when внутри select-а.
в простом варианте, при наличии when тип поля заменяется (при необходимости) на Nullable.
соответственно тип записи всегда один и тот же, и содержит поле ModelCode, и данный код скомпилится, но когда isYes - false, то ModelCode будет null
в более сложном варианте (но это мало применимо в .net-е, java-е, C++ и т.д. из-за отсутствия поддержки вариационных классов) создается два типа: один без поля ModelCode, второй с полем ModelCode. record при компиляции имеет первый тип, соответственно, чтобы получить доступ к ModelCode необходимо скастить ко второму типу.

6yrop

есть два варианта раскрытия when внутри select-а.
в простом варианте, при наличии when тип поля заменяется (при необходимости) на Nullable.
соответственно тип записи всегда один и тот же, и содержит поле ModelCode, и данный код скомпилится, но когда isYes - false, то ModelCode будет null
в более сложном варианте (но это мало применимо в .net-е, java-е, C++ и т.д. из-за отсутствия поддержки вариационных классов) создается два типа: один без поля ModelCode, второй с полем ModelCode. record при компиляции имеет первый тип, соответственно, чтобы получить доступ к ModelCode необходимо скастить ко второму типу.
Оба варианта какое-то издевательство над статической типизацией.

Dasar

Оба варианта какое-то издевательство над статической типизацией.
во втором варианте - всё хорошо со статической типизацией, если ее правильно готовить.

6yrop

плохо вот это: "скастить"

Dasar

плохо вот это: "скастить"
можно статически проверить каст правомерен или нет

6yrop

можно статически проверить каст правомерен или нет
каким образом? напиши код. Хочу, чтобы мне не дали перепутать, где кастить в ветке isYes или !isYes.

Dasar

каким образом?
для начала вот такой код не может упасть в runtime-е

var r2 = record as IRecord_With_ModelCode;
if (r2 != null)
{
Console.WriteLine(r2.ModelCode);
}

у этого кода могут быть две проблемы:
1. это код никогда не исполняется. или другими словами, ранее нет ветки, когда record создается такого типа. можно проверить, что ранее есть такая ветка
2. не обработаны все варианты: когда record имеет одно поле, не имеет другого, не имеет никаких полей и т.д. это также можно проверить, что в коде присутствует обработка всех вариантов.
> Хочу, чтобы мне не дали перепутать, где кастить в ветке isYes или !isYes.
я правильно понимаю, что ты фактически хочешь, чтобы в следующем коде тебе сказали, что if(!isYes) никогда не выполнится?

var r2 = record as IRecord_With_ModelCode;
if (r2 != null)
{
Console.WriteLine(r2.ModelCode);
if (!isYes)
{
//никогда не выполнится
}
}

проверить такое можно, но если делать в лоб, то легко вылетить на экспонециальный рост кол-ва проверяемых вариантов.

6yrop

я правильно понимаю, что ты фактически хочешь, чтобы в следующем коде тебе сказали, что if(!isYes) никогда не выполнится?
расскажи что делать вот в этом коде:

var isYes = Console.ReadLine == "Yes";

foreach (var record in db.ModelPublication
.where ModelPublication.ModelCode == modelCode when not-null
.where ModelPublication.ModelSequence == modelSequence when not-null
.Select ModelPublication.PublicationId,
ModelPublication.ModelCode when isYes,
ModelPublication.ModelSequence)
{
Console.WriteLine(Method1(record, isYes;
}

string Method1(? record, bool isYes)
{
return Method2(record, isYes);
}

string Method2(? record, bool isYes)
{
return Method3(record, isYes);
}

string Method3(? record, bool isYes)
{
return MethodN(record, isYes);
}

string MethodN(? record, bool isYes)
{
if (!isYes)
{
return "вы не выбрали Yes";
}
else
{
var r2 = record as IRecord_With_ModelCode;
if (r2 != null)
{
return record.ModelCode;
}
//здесь не компилируется, что делать?
}
}

6yrop

расскажи что делать вот в этом коде:
Controllable Query успешно справился с этой задачей, см. код ниже. Причем справился с помощью стандартных, хорошо известных программистам механизмов языка C#.
Ты же в своих попытках придумать свой язык покушаешься на базовые механизмы декомпозиции, встроенные в C#. Решаемая задача – сформировать строку запроса – не стоит того, чтобы ради нее жертвовать базовыми механизмами декомпозиции языка.
А если смущает немного громоздкий синтаксис, то это дело привычки, через пару дней всё хорошо читаешь и понимаешь; ну естественно, под удобной расцветкой и навигацией в ReSharper-е.

static void Main
{
var modelCode = Console.ReadLine;
var modelSequence = Console.ReadLine;
var isYes = Console.ReadLine == "Yes";

var queryParamFunc = Func.New(
(QueryContext context) => new
{
ModelCode = modelCode.ToOptionIfNullOrEmpty.ProcessValue(_ => context.Param(_
ModelSequence = modelSequence.ToOptionIfNullOrEmpty.ProcessValue(_ => context.Param(_
});

foreach (var func in
isYes
? MaterializeReader(
_ => QueryString(_.ModelSequence, _.ModelCode, onModelSequence: builder => builder.Append(@",
ModelPublication.ModelSequence".ToString.ToQuery(
new
{
PublicationId = default(Guid
ModelCode = default(string
ModelSequence = default(string)
}
queryParamFunc).Select(_ => Func.New => _.ModelSequence
: MaterializeReader(
_ => QueryString(_.ModelSequence, _.ModelCode, onModelSequence: delegate { })
.ToString.ToQuery(
new
{
PublicationId = default(Guid
ModelCode = default(string)
}
queryParamFunc).Select(_ => Func.New => "вы не выбрали Yes"
Console.WriteLine(Method1(func;
}

private static StringBuilder QueryString(IOption<IParam<string>> modelCode, IOption<IParam<string>> modelSequence, Action<StringBuilder> onModelSequence)
{
var builder = new StringBuilder(
@"
SELECT
ModelPublication.PublicationId,
ModelPublication.ModelCode");
onModelSequence(builder);
builder.Append(@"
FROM
ModelPublication
WHERE
1 = 1");
modelCode.ProcessValue(_ => builder.Append(@"
AND ModelPublication.ModelCode = " + _;
modelSequence.ProcessValue(_ => builder.Append(@"
AND ModelPublication.ModelSequence = " + _;
return builder;
}

private static string Method1(Func<string> func)
{
return Method2(func);
}

private static string Method2(Func<string> func)
{
return Method3(func);
}

private static string Method3(Func<string> func)
{
return MethodN(func);
}

private static string MethodN(Func<string> func)
{
return func;
}

Dasar


interface IR
{
int PublicationId {get;}
..
}
interface IR1: IR
{
int ModelCode {get;}
}

сигнатура метода Method1 с точки зрения выполнения

string Method1(IR record, bool isYes)
{
return Method2(record, isYes);
}

а c точки зрения статической проверки вариантов

string Method1(IR record, bool isYes) hint-where record: IR! or IR1!
{
return Method2(record, isYes);
}

если хочется проверить на согласованность два параметра, то будет

string Method1(IR record, bool isYes) hint-where (record: IR!, isYes:false) or (record:IR1!, isYes:true)
{
return Method2(record, isYes);
}

тогда в методе MethodN

string MethodN(IR record, bool isYes) hint-where (record: IR!, isYes:false) or (record:IR1!, isYes:true)
{
if (!isYes) hint-where (record: IR!, isYes:false)
{
return "вы не выбрали Yes";
}
else hint-where (record: IR1!, isYes:true)
{
var r2 = record as IR1;
if (r2 != null) hint 'всегда true'
{
return record.ModelCode;
}

return null hint 'не выполняется никогда';
}
}

зы
двойная система типов используется из-за предположения, что код транслируется под .net, где вариативные типы проблематично реализовать на уровне исполнения
ззы
T! - означает, что тип может быть строго T, не включая наследников

kokoc88

А если смущает немного громоздкий синтаксис, то это дело привычки, через пару дней всё хорошо читаешь и понимаешь;
Через пару дней? Но на Brain Fuck хватает всего пары часов!1

Dasar

ты то что используешь для доступа к базе?

kokoc88

ты то что используешь для доступа к базе?
Какая разница, я это не рекламирую и не собираюсь.
Я вовсе не против какой-то деятельности в направлении статической типизации или каких-то других безумных идей. Но только когда в итоге получается read-write код меньшего размера, чем сумма лобовой копипасты с юнит тестами. И по которому хотя бы можно сделать review, который, как известно, выявляет намного больше проблем, чем статическая типизация вместе с юнит тестами и другими проверками. И ещё я не против дурацких поделок, если их автор не упёрт, как баран, и прислушивается к разумной критике.

6yrop

interface IR1: IR
Если использовать наследование типов resultset-ов, то запись на Controllable Query тоже уменьшается:
 

static void Main
{
var modelCode = Console.ReadLine;
var modelSequence = Console.ReadLine;
var isYes = Console.ReadLine == "Yes";

var queryParamFunc = Func.New(
(QueryContext context) => new
{
ModelCode = modelCode.ToOptionIfNullOrEmpty.ProcessValue(_ => context.Param(_
ModelSequence = modelSequence.ToOptionIfNullOrEmpty.ProcessValue(_ => context.Param(_
});

foreach (var func in
isYes
? MaterializeReader(
_ => QueryString(_.ModelSequence, _.ModelCode, onModelSequence: builder => builder.Append(@",
ModelPublication.ModelSequence".ToString.ToQuery<IR1>
queryParamFunc).Select(_ => Func.New => _.ModelSequence
: MaterializeReader(
_ => QueryString(_.ModelSequence, _.ModelCode, onModelSequence: delegate { }).ToString.ToQuery<IR0>
queryParamFunc).Select(_ => Func.New => "вы не выбрали Yes"
Console.WriteLine(Method1(func;
}

private static StringBuilder QueryString(IOption<IParam<string>> modelCode, IOption<IParam<string>> modelSequence, Action<StringBuilder> onModelSequence)
{
var builder = new StringBuilder(
@"
SELECT
ModelPublication.PublicationId,
ModelPublication.ModelCode");
onModelSequence(builder);
builder.Append(@"
FROM
ModelPublication
WHERE
1 = 1");
modelCode.ProcessValue(_ => builder.Append(@"
AND ModelPublication.ModelCode = " + _;
modelSequence.ProcessValue(_ => builder.Append(@"
AND ModelPublication.ModelSequence = " + _;
return builder;
}

public interface IR0
{
Guid PublicationId { get; }
string ModelCode { get; }
}

public interface IR1 : IR0
{
string ModelSequence { get; }
}

Это полный код и он работает уже сейчас.
P.S. Но применение именованных типов IR0, IR1 и наследования в данной ситуации мне не нравится.

Dasar

Какая разница, я это не рекламирую и не собираюсь.
для окружающих - это скорее минус, чем плюс. получается они не могут использовать твой опыт, а могут использовать только свой опыт и опыт -а.
> И ещё я не против дурацких поделок, если их автор не упёрт, как баран, и прислушивается к разумной критике.
Критика является разумной, когда критикуются способы достижения целей, а не сами цели. Ты же критикуешь именно цели, а не способ достижения - это далеко от разумности.
> Но только когда в итоге получается read-write код меньшего размера, чем сумма лобовой копипасты с юнит тестами. И по которому хотя бы можно сделать review, который, как известно, выявляет намного больше проблем, чем статическая типизация вместе с юнит тестами и другими проверками.
это твои цели, и они не совпадают с целями . при этом ты хочешь, чтобы следовал тем же целям, что и ты. зачем тебе это надо?
зы
в моем понимании, -а интересует предсказуемость изменений кода. тесты и review эту задачу не решают.

Dasar

P.S. Но применение именованных типов IR0, IR1 и наследования в данной ситуации мне не нравится.
да, в шарпе и .net-е в большинстве случаев лучше использовать жирные типы.
ни язык(c# ни платформа (.net) не имеют средств для работы с большим кол-вом типов.

kokoc88

Критика является разумной, когда критикуются способы достижения целей, а не сами цели. Ты же критикуешь именно цели, а не способ достижения - это далеко от разумности.
Я задал большое количество вопросов, половина из которых осталась без ответа, на другую половину был написан код, который невозможно прочитать, не то что поддерживать. Отсюда был сделан вывод: рекламируется неудобное поделие, которое невозможно использовать и которое не имеет плюсов по сравнению со стандартными подходами.
в моем понимании, -а интересует предсказуемость изменений кода. тесты и review эту задачу не решают.
Вряд ли кого-то интересует предсказуемость изменений write-only кода. Да и задача не решена автором даже в таком варианте.

Dasar

Вряд ли кого-то интересует предсказуемость изменений write-only кода.
стоит перевести на осознаваемый уровень, что такое "читабельность кода".
в моем понимании, читабельность кода складывается из трех факторов:
1. есть ключевые точки, прочитав которые и не читая все остальное - можно восстановить общую структуру кода и общее поведение кода,
2. локальные изменения можно внести не разбираясь со всем кодом.
3. для чтения кода можно использовать уже имеющиеся навыки.
код -а не плох по первым двум показателям, а есть только проблема по п.3 из-за страшноватого синтаксиса. Но как раз этот фактор наименее объективен, и зависит лишь от того, у человека уже был опыт работы с данным кодом, или еще нет.
третий фактор больше говорит о пороге входа, а не о самой читабельности кода.

kokoc88

1. есть ключевые точки, прочитав которые и не читая все остальное - можно восстановить общую структуру кода и общее поведение кода,

Я и не спорю, что в коде автора есть много ключевых точек: одна на первой странице 27", вторая на второй, и так далее.
2. локальные изменения можно внести не разбираясь со всем кодом.

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

Dasar

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

kokoc88

это разовая проблема.
Ага, второй кактус разжевать проще, чем первый. А потом уже вся глотка в колючках, так что похер.

Dasar

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

kokoc88

ты так говоришь, как будто кактусы можно не есть при доступе к бд из кода.
Представь себе, можно не есть. Но кому-то из этого раздела это не грозит.

Dasar

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

kokoc88

а представить способ ты боишься, потому что вдруг окажется, что это не так?
У меня нет какого-то "способа". А даже если и буду придумывать, то в разделе полно принцесс на горошине, всё равно у какой-то будут синяки.

Dasar

У меня нет какого-то "способа".
то есть: я не знаю как задачу решать, но представленный способ осуждаю?

kokoc88

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

Dasar

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

kokoc88

критика имеет смысл, если можно решить ту же задачу лучше. если же способа решить лучше нет, то критика по большому счету бессмысленна.
Ты не читал мои сообщения в этом эпическом треде? Технология для работы с базой данных должна позволять менять уровень изоляции и выполнять несколько запросов в рамках одной транзакции. Поделие, которое замахивается на "JetBrains сделает поддержку", должно быть хотя бы с намёком на кроссплатформенность. И так далее...
сейчас же ты фактически сказал, что ты не знаешь способа решить представленную задачу лучше.
Сейчас ты фактически приписал мне какие-то слова, которых я не говорил.

Dasar

Технология для работы с базой данных должна позволять менять уровень изоляции и выполнять несколько запросов в рамках одной транзакции.
транзакция обычно привязывается к соединению (или вообще к текущему scope а не к запросу.
и данный код без изменения стыкуется с тем же TransactionScope
> должно быть хотя бы с намёком на кроссплатформенность.
намек на кроссплатформенность и здесь есть: пока используется портируемый sql - код без изменений будет работать для любой базы.

kokoc88

и данный код без изменения стыкуется с тем же TransactionScope
Ты меня неправильно понял, я вовсе не собираюсь об этом с тобой переписываться.

Dasar

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

kokoc88

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

Dasar

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

kokoc88

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

Dasar

напишешь примеры кода по всем поставленным мною проблемам
а представить как код будет выглядеть без его написания ты не можешь?

kokoc88

а представить как код будет выглядеть без его написания ты не можешь?
И не могу, и не хочу. Ещё раз повторю мысль, которую высказал выше: я не должен доказывать, что поделие автора является гавном, а автор должен доказывать обратное.

Dasar

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

kokoc88

и никто тебе не навязывает роль покупателя
Ага, зато роль продавца всё время навязывают.

6yrop

Ага, зато роль продавца всё время навязывают.
Подожди, ты решил, что тебе тут навязывают роль продавца open source проекта? И как после этого тебя считать адекватным человеком?

kokoc88

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

6yrop

через делегаты и анонимные типы данных
бедненький, ты их до сих пор боишься

kokoc88

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

6yrop

 

таким образом не получится поймать ошибку/опечатку вида

if (_.ModelSequence.HasValue) builder.Append(@"
AND ModelPublication.ModelSequence = " + _.ModelSequence);

Не смотря на то, что такие ошибки с большой вероятностью ловятся в автотесте, мне не очень нравится добавление параметра при конкатинации строк. Вот думаю, перевести отлов ошибок на этап компиляции, обернув StringBuilder и закрыв метод Append(object) (а перегрузку IParam.ToString убираем):
 

if (_.ModelSequence.HasValue) builder._(@"
AND ModelPublication.ModelSequence = ")._(_.ModelSequence.ValueOrEx);


public class QBuilder
{
...
private readonly StringBuilder stringBuilder;

public QBuilder _(string value)
{
stringBuilder.Append(value);
return this;
}

public QBuilder _<T>(IParam<T> param)
{
stringBuilder.Append(param.ToSql;
return this;
}
}

Так вроде совсем safe-ово получается.

6yrop

совместил перечисление столбцов в SELECT и объявление анонимного класса:

static void Main
{
var modelCode = Console.ReadLine;
var modelSequence = Console.ReadLine;

foreach (var record in MaterializeReader(
context => new {
ModelCode = modelCode.ToOptionIfNullOrEmpty.Some(_ => context.Param(_
ModelSequence = modelSequence.ToOptionIfNullOrEmpty.Some(_ => context.Param(_
},
p => {
var q = new Q("FROM ModelPublication WHERE 1 = 1");
p.ModelCode.Some(_ => q._(" AND ModelCode = ")._(_;
p.ModelSequence.Some(_ => q._(" AND ModelSequence = ")._(_;
return q.Query(
_ => new {PublicationId = _._<Guid> ModelCode = _._<string> ModelSequence = _._<string>});
}
{
Console.WriteLine(
"PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
record.PublicationId,
record.ModelCode,
record.ModelSequence);
}
}

В метод _._<T> можно добавлять string аргумент

_._<T>("ModelPublication.")
или
_._<T>("ModelPublication.ModelCode AS ")

PropertyName анонимного класса просто клеится к указанному аргументу.

6yrop

Прикольно это работает с continuous testing tool. Тесты запускаются и подсвечиваются прям при наборе текста:

6yrop

приятная новость в вышедшем две недели назад SQL Server 2012 поддержали вывод типов параметров :D
http://msdn.microsoft.com/en-us/library/ff878260%28v=sql.110...
Оставить комментарий
Имя или ник:
Комментарий: