[C#] Querying
теперь можно писать вот такА можно не писать. Полная свобода действий.
Я, конечно, понимаю, что ты просто тролишь. Но всё же прокомментирую. Как только ты начинаешь использовать метод MaterializeReader и при этом не используешь всякие дурацкие приемы типа рефлекшена , каста и т.п., то полной свободы уже нет, ты уже под контролем машины. Либо проверка запроса проходит успешно, и тогда запрос выполняется правильно и в основном рантайме, либо проверка падает, и тогда правим код.
ты уже под контролем машиныЯ - ещё нет, но кто-то в разделе Development совершенно точно под контролем.
очень остроумно
AND ModelPublication.ModelCode = " + _.ModelCode.ValueOrEx);здесь подставляется переменная или эскейптнутое значение?
все запросы автоматически находятся и проверяютсякакие свойства проверяется?
Можно прикрутить Find Usagesк студии? в ней легко к существующему поиску добавить свой поиск символов? или ты теоретически?
здесь подставляется переменная или эскейптнутое значение?Ты не читал сообщения в прошлой рекламной ветке этой великолепной, прозрачной и понятной каждому программисту по тексту кода, технологии? Разве тебе не очевидно, что получение свойства на самом деле подставляет его в виде параметра, например "@" + paramIndex++; а само значение сохраняется во что-то типа List<T> для использования во время вызова в ADO.NET? Ты что, ещё не под контролем машины?
Ты не читал сообщения в прошлой рекламной ветке этой великолепной, прозрачной и понятной каждому программисту по тексту кода, технологии?приоритеты у ТС были в порядке убывания:
весь код в "одном" исходнике
выразительность
проверяемость на уровне компиляции
отсутствие дублирования
все остальные: прозрачность, красивость и т.д.
и при таком порядке приоритетов понятно почему не удается достичь максимально понятного кода.
осуждать за выбор порядка приоритетов мне представляется неправильным, потому что:
во-первых: нет объективных критериев для определения, а как лучше,
во-вторых: на его задачах и при его подходе к решению задач - может именно такая расстановка приоритетов окупается больше всего.
> Разве тебе не очевидно, что получение свойства
по коду это не очевидно, и не обязано быть очевидным - если при этом есть утилита, которая это требование проверяет
весь код в "одном" исходникеКонечно, весь код должен быть написан в одном файле MainClass.cs; а что, есть несогласные?
выразительностьБезусловно, это требование было выполнено на 100%.
проверяемость на уровне компиляцииПроверяемость строк "SELECT" на уровне компиляции C#. И я о том же.
отсутствие дублированияКогда весь код в "одном" исходнике, какие тут могут быть дублирования?
и при таком порядке приоритетов понятно почему не удается достичь максимально понятного кода.
Точно, на 100% выразительный код - это код, не понятный никому.
по коду это не очевидно, и не обязано быть очевидным - если при этом есть утилита, которая это требование проверяетЧёрт, я и не подумал, что утилита на этапе компиляции проверяет, что Console.ReadLine возвращает только строки без injection. Пойду заменю машину, которая меня контролирует.
фактически, ты понял все приоритеты не тем образом, что они обозначают.
здесь подставляется переменная или эскейптнутое значение?
при первом обращении к 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.
фактически, ты понял все приоритеты не тем образом, что они обозначают.Точно, я совсем забыл, что определения в этом разделе даются после их использования.
Точно, я совсем забыл, что определения в этом разделе даются после их использования.так можно перейти к тому, что сначала необходимо каждое слово определить(вот только каким образом?) и только потом использовать.
так можно перейти к тому, что сначала необходимо каждое слово определить(вот только каким образом?) и только потом использовать.Не знаю, может быть и можно; особенно если ты будешь хлебом называть сметану, а сметану морковкой.
Во-первых, [..] Во-вторых, [..]таким образом не получится поймать ошибку/опечатку вида
if (_.ModelSequence.HasValue) builder.Append(@"
AND ModelPublication.ModelSequence = " + _.ModelSequence);
при этом оно может обычно даже работать, если _ModelSequence обычно вводится как число
таким образом не получится поймать ошибку/опечатку видасделал такую опечатку, запустил, проверка не прошла, выдав ошибку:
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]]
if (_.ModelSequence.HasValue) builder.Append(@"
AND ModelPublication.ModelSequence = " + modelSequence);
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:
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Да, у меня тоже была первая реакция немного поправить сообщение для лучшей читаемости. Но немного спокойствия и можно увидеть:
... <Main>b__0 ... DeclaringType = 'ConsoleApplication2.Program+<>c__DisplayClass4'
Да, у меня тоже была первая реакция немного поправить сообщение. Но немного спокойствия и можно увидетьКонечно, как же я сразу не понял, когда весь код написан в "одном" файле, есть только один единственный класс и один единственный метод Main. Но Великий, в методе Main может быть десять запросов, как же мне интерпретировать b__0? Ведь пока что я умею интерпретировать только O_o и o_O.
Но Великий, в методе Main может быть десять запросов, как же мне интерпретировать b__0?десять запросов в одном методе это редкий случай, и сейчас предлагается воспользоваться Reflector-ом для таких ситуаций. В будущем, возможно, будет добавлена интеграция с pdb файлами, и тогда будет выводиться номер строки.
сейчас предлагается воспользоваться Reflector-ом для таких ситуаций.Воистину, нам сегодня открылась Великая Мудрость! Ибо кто мог бы предположить, что после ошибки запуска юнит тестов нужно декомпилировать код?!
весь код написан в "одном" файлекстати, смех смехом, а последние веянья моды как раз Micro ORM-ы в виде одного файла .
теперь можно писать вот тако боги... Я ведь тоже пишу на этом языке...
о боги... Я ведь тоже пишу на этом языке...покажи как ты пишешь
вот так проверьНу, и где же твой следующий вопрос? Типа такого: нужно ввести в консоли третий параметр типа Да/Нет и в зависимости от значения выбирать или не выбирать столбец ModelSequence. Или что ты там придумываешь?
if (_.ModelSequence.HasValue) builder.Append(@"
AND ModelPublication.ModelSequence = " + modelSequence);
покажи как ты пишешьЗабей, он пишет как обычно.
Message=Method is not static Methodи что ему не понравилось?
и что ему не понравилось?замыкание. Замыкания не разрешены.
вот так написать можно:
context => new
{
ModelCode = modelCode.ToOptionIfNullOrEmpty.Select(_ => context.Param(_
ModelSequence = modelSequence.ToOptionIfNullOrEmpty.Select(_ => context.Param(_
zz = modelSequence
})
а параметры на что проверяется?такая запись не пройдет проверку. Точнее проверка сваливается в кастомный колбек, и в колбеке решаем для параметра данного типа либо кидаем исключение, либо предоставляем множество значений для параметра. В твоем примере тип String, поэтому кидаем исключение.
вот так написать можно:
context => new
{
ModelCode = modelCode.ToOptionIfNullOrEmpty.Select(_ => context.Param(_
ModelSequence = modelSequence.ToOptionIfNullOrEmpty.Select(_ => context.Param(_
zz = modelSequence
})
Забей, он пишет как обычно.От чего же, пусть напишет свой вариант, и посмотрим, чей код лучше принимает изменения, а также другие характеристики кода.
От чего же, пусть напишет свой вариант, и посмотрим, чей код лучше принимает изменения, а также другие характеристики кода.Так ты для этого сделай изменения, о которых тебя попросили:
Типа такого: нужно ввести в консоли третий параметр типа Да/Нет и в зависимости от значения выбирать или не выбирать столбец ModelSequence.Кроме этого, надо расширить список фильтрации; вполне реальная задача КЛАДР может потребовать 8 параметров.
И, конечно же, нужно ответить на вопрос про такую опечатку, и как ты её проверяешь:
if (_.ModelCode.HasValue) builder.Append("AND ModelPublication.ModelCode = " + _.ModelCode.ValueOrEx);
if (_.ModelSequence.HasValue) builder.Append("AND ModelPublication.ModelCode = " + _.ModelSequence.ValueOrEx);
а с условным подтягиванием данных из других таблиц уже разбирался? как, например, то, что спрашивает mike (выбрать или не выбрать поле) - но при этом еще данное поле тянется через join из вспомогательной таблицы (например, справочника)
NVarChar<L4000>Как задаётся и проверяется произвольная длина строки? Допустим, поле длиной 32 символа.
Что делать, если у меня две разных таблицы с разной длиной полей, но используются одинаковые запросы в обе эти таблицы?
Есть ли кроссплатформенность? Если у меня на сервере Oracle, а на клиенте встраивается SQLite, как будет работать эта библиотека?
Где задавать настройки базы? Чтобы на каждый отдельный запрос были отдельные: уровень изоляции транзакций, таймауты, и т.п.
Почему параметры и столбцы названы одинаково? Что будет с названиями столбцов при переименовании полей анонимных классов?
Что будет, если решается задача интеграции со сторонней системой, и в запросе необходимо сделать CAST? А если в запросе необходимо сделать GETUTCDATE или (синтаксис забыл) DATEDIFF(Model.CreationDate, hh, 4)?
Типа такого: нужно ввести в консоли третий параметр типа Да/Нет и в зависимости от значения выбирать или не выбирать столбец 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.
значения выбирать или не выбирать столбец ModelSequence.условие должно было добавиться в select, а не в where
if (_.IsYes && _.ModelSequence.HasValue) builder.Append(@"AND ModelPublication.ModelSequence = " + _.ModelSequence.ValueOrEx);Ты нипонил!1 Не надо считывать из базы и гонять по сети поле ModelSequence; оно должно отсутствовать в SELECT.
Правильно ли я понимаю, что для модификации твоего кода в общем случае в функцию добавляется параметр, затем мы идём куда-то в конец функции прописать параметр в анонимный класс, а затем идём обратно вверх дописывать место, где его надо использовать?
2^8 еще в рамках тупого перебора всех комбинаций.Не надо нам перебора, хочется видеть размер и структуру кода, в котором используется 8 параметров для фильтрации.
Опечатка вот здесь “ModelPublication.ModelCode”? Тогда это не ловится, но оно также не ловится и в LINQ.
То есть при использовании твоей библиотеки всё равно надо написать юнит тесты, которые одновременно покроют всё, что делает твоя библиотека. А ещё они будут кроссплатформенные, не будут накладывать кучу ограничений, и т.п.
То есть при использовании твоей библиотеки всё равно надо написать юнит тесты, которые одновременно покроют всё, что делает твоя библиотека.резко уменьшается кол-во тестов.
для покрытия такого кода требуется по два теста на каждый параметр запроса.
если клеить запросы без такой штуки, то потребуется тестить все виды инъекций на каждый параметр.
если клеить запросы без такой штуки, то потребуется тестить все виды инъекций на каждый параметр.Если клеить их именно так, как тут описано. Сделай простую обёртку над тем же StringBuilder и протестируй её. Мы же сравниваем поделие автора с каким-то разумным и простым подходом, а не с лобовой копипастой.
Мы же сравниваем поделие автора с каким-то разумным и простым подходом, а не с лобовой копипастой.а что еще есть? linq, orm-ы и т.д.?
у них есть траблы с выразительностью. в частности, не всякий sql запрос можно через них выразить.
у них есть траблы с выразительностью. в частности, не всякий sql запрос можно через них выразить.Что-то мне подсказывает, что всякий SQL запрос можно выразить на SQL. Здесь мы до сих пор не увидели: запросы с агрегацией, вызовы хранимых процедур, кроссплатформенность, уровни изоляции транзакций, разные виды JOIN, полностью динамические запросы, несколько запросов в одной транзакции... можно долго перечислять.
условие должно было добавиться в 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;
}
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);
}
Это сильно надо?
но при этом еще данное поле тянется через join из вспомогательной таблицы (например, справочника)там же просто строки, поэтому можно использовать любой SQL, и join и всё что угодно, без ограничений.
в целом, конечно, страшновато, но неплохо с точки зрения проверяемости.а что страшно то? как раз с каким-нибудь велосипедом, закрывающим функциональность SQL Server-а (ORM, LINQ, тем более самописные билдеры вот это да страшно. Я думаю, что этот подход выиграет даже у HaskellDB.
Как задаётся и проверяется произвольная длина строки? Допустим, поле длиной 32 символа.мепинг таких типов настраивается в каждом проекте как хочешь, можешь просто на String замепить. Но вот такая вещь как NVarChar<L4000> круто и вот почему. Если это поле скажем байндится к текстовому полю на UI-е, то получаем возможность валидировать длину без дополнительных усилий. Еще пример, данные выгружаются/загружаются в/из XML, опять получаем возможность проверять длину поля, а также генерировать XSD схему, в которой отображается длинна поля. Это вкусно, поскольку данные можно переливать через промежуточные объекты (скажем DTO и при этом информация о длине поля сохраняется и проверяется компилятором.
Что делать, если у меня две разных таблицы с разной длиной полей, но используются одинаковые запросы в обе эти таблицы?
Это как-то криво, но, повторюсь, можно промепить на просто String.
Есть ли кроссплатформенность? Если у меня на сервере Oracle, а на клиенте встраивается SQLite, как будет работать эта библиотека?
кросСУБДшностью не занимался, поскольку в реальности крайне редкий сценарий (этот критерий используется чисто в рекламных целях ORM). Но принципиальных сложностей поддержать другие СУБД я пока не вижу, должно сработать и там.
Почему параметры и столбцы названы одинаково?
Как удобно было так и назвал. Совпадение случайное, скрытого смысла в этом нет.
Что будет с названиями столбцов при переименовании полей анонимных классов?
Проверка запросов упадет с ошибкой. Можно в SQL строке столбцу дать алиас через AS.
Что будет, если решается задача интеграции со сторонней системой, и в запросе необходимо сделать CAST? А если в запросе необходимо сделать GETUTCDATE или (синтаксис забыл) DATEDIFF(Model.CreationDate, hh, 4)?
поддерживаются любые выражения на SQL, поэтому проблем не вижу.
Здесь мы до сих пор не увидели: запросы с агрегацией, вызовы хранимых процедур, кроссплатформенность, уровни изоляции транзакций, разные виды JOIN, полностью динамические запросы, несколько запросов в одной транзакции... можно долго перечислять.в Controllable Query довольно мало кода, и он практически не закрывает ADO.NET, поэтому твои вопросы странные.... Да, документации пока нет....
Ты нипонил!1 Не надо считывать из базы и гонять по сети поле ModelSequence; оно должно отсутствовать в SELECT.ответил выше -ю
Правильно ли я понимаю, что для модификации твоего кода в общем случае в функцию добавляется параметр, затем мы идём куда-то в конец функции прописать параметр в анонимный класс, а затем идём обратно вверх дописывать место, где его надо использовать?
Да, правильно. Анонимный класс можно сделать не внизу а вверху, но коллеги выбрали "внизу", они сказали, что так похоже на sp_sqlexec (первый вариант был "вверху"). Это не входит в Conrollable Query это всё кастомный код, для каждого проекта может быть свой вариант.
Не надо нам перебора, хочется видеть размер и структуру кода, в котором используется 8 параметров для фильтрации.
Что значит 8 полей, они все похожие? Тогда не понятно, у тебя есть пример с 2 полями и ты не можешь представить как будет с 8-ю? Или ты хочешь показать, что это будет сильно громоздко? Ок, я могу не полениться и написать, как назвать поля Field1, Field2, ... Field8 подойдет?
То есть при использовании твоей библиотеки всё равно надо написать юнит тесты, которые одновременно покроют всё, что делает твоя библиотека.
Нет, можно писать, можно не писать. Проверка запросов и ручные unit-тесты дополняют друг друга, также как статическая типизация и unit-тесты дополняют друг друга.
с каким-то разумным и простым подходомвидимо, ты еще новичок в этом вопросе. Разумного и простого подхода просто нет .
кросСУБДшностью не занимался, поскольку в реальности крайне редкий сценарий (этот критерий используется чисто в рекламных целях ORM). Но принципиальных сложностей поддержать другие СУБД я пока не вижу, должно сработать и там.Да, мы тоже думали что Oracle всех устроит, и в общем-то десять лет всё было ок, пока на прошлой неделе не напоролись на заказчика, успешно использующего Parallel Sysplex (active-active кластер DB/2 на z/OS с узлами, разнесёнными в два дата-центра в разных частях города). И всё, нашла коса на камень, т.к. сделать что-либо сравнимое по надёжности на Oracle уже не получится, да и не нужно это заказчику - слезать с проверенного и отлично работающего решения и прокладывать новую тропу, отважно наступая на все грабли.
Слышать про ненужность кроссплатформенности уже даже не смешно - хочется пожалеть убогого.
P.S. Надо же, какой страшный код можно писать на C#.
Что это вообще за хрень?
Слышать про ненужность кроссплатформенности уже даже не смешно - хочется пожалеть убогого.Убогий тут, кажется, ты, поскольку даже читать не научился. Прочитай мой пост, там нет “ненужность кроссплатформенности”.
в общем-то десять лет всё было ок, пока на прошлой неделе
что ж, это подтверждает мои слова о том, что такое редко встречается. Принципиально кроссубдшность вполне реально поддержать. Есть непроверенный вопрос, как на Oracle работает DbCommand.ExecuteReader(CommandBehavior.SchemaOnly). Если работает нормально, то поддержка Oracle-а в Controllable Query делается за 1-2 дня. Если не совсем правильно работает, то можно найти парсер SQL-я для Oracle ну и немного повозиться, но если идет речь о крупном проекте, то это всё вполне реально.
мепинг таких типов настраивается в каждом проекте как хочешь, можешь просто на String замепитьИнтересно видеть конкретику, как указать длину 32, 40, 100, 128...
Это как-то криво, но, повторюсь, можно промепить на просто String.Да, когда это зависит от тебя, то криво. Что делать, если не зависит, и строки разной длины? Просто отключить валидацию по длине?
кросСУБДшностью не занимался, поскольку в реальности крайне редкий сценарийSQLite на клиенте и интеграция в большой компании - крайне редкие сценарии?
Проверка запросов упадет с ошибкой. Можно в SQL строке столбцу дать алиас через AS.Нельзя автоматически переименовать поле?
Нет, можно писать, можно не писать. Проверка запросов и ручные unit-тесты дополняют друг друга, также как статическая типизация и unit-тесты дополняют друг друга.Можно не писать? Пока что, даже если включить сценарии с injection, юнит тесты будут меньше по размеру, чем обвязка твоей библиотеки.
Ну например так:То есть задча выбрать или не выбирать столбец превратилась в копипасту? Я боюсь спросить, если придётся выбирать или не выбирать два столбца, то мы напишем 4 варианта кода?
Просто отключить валидацию по длине?да.
SQLite на клиенте и интеграция в большой компании - крайне редкие сценарии?
Ok. Ты работаешь с SQLite? Можешь запустить DbCommand.ExecuteReader(CommandBehavior.SchemaOnly) и распечатать результаты? Могу прислать полный код, если будешь запускать.
Нельзя автоматически переименовать поле?
Пока JetBrains не поддержала Controllable Query , да, нельзя. Но твой пример переименования не так интересен, поскольку всё же базовыми являются имена в запросе.
То есть задча выбрать или не выбирать столбец превратилась в копипасту? Я боюсь спросить, если придётся выбирать или не выбирать два столбца, то мы напишем 4 варианта кода?там почти нет копипасты. Или ты считаешь два разных типа resultset-а порождают копипаст? Ты хочешь один тип resultset-а? Укажи что именно ты считаешь копипастой? Или ты хочешь на Dynamic-ки перейти?
да.Интересно видеть конкретику, как указывать разную длину для валидации: 32, 40, 100, 128... Завтра заказчик попросит либо ещё какую-то неизвестную цифру; либо поправить существующую, уменьшив длину со 128 до 64.
2. Если длинна используется, то добавляются вот такие классы
public class L128 : ILength { }
public class L64 : ILength { }
У нас сейчас в проекте таких классов 12 штук.
там почти нет копипасты. Или ты считаешь два разных типа 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);
}
1. Можно вообще не использовать длину, ни где.Ни где?
У нас сейчас в проекте таких классов 12 штук.То есть если мне нужна валидация банковской анкеты на кредит, то этих классов надо написать ~100 штук.
~100 штуктебя смущает 100 строчек кода?
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 { }
кое что из выделенного можно устранить. Скажи, как по-твоему, какой тип должна иметь переменная record в том и в другом случае.
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);
}
Что это вообще за хрень?я вроде написал. и ссылку на википедию дал...
active-active кластер СУБД с узлами, разнесёнными в два дата-центра в разных частях города
Не, я про то, о чем тут разговор идет.
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);
}
}
?
Delegation pattern ты тоже считаешь копипастойсам паттерн не является копипастой, копипастой является его воплощение в языках типа C++, C#
засахарить это дело не так уж и сложно
копипастой является его воплощение в языках типа C++, C#да, Kotlin добавляет такой сахар. Но даже в C# этот копипаст не имеет большого негативного влияния, первоначально ReSharper сгенерирует вам все строчки, при последующем внесении изменений тоже всё примитивно и вероятность ошибки мала. Копипаст копипасту рознь. Одно дело вы копипастите какую-нибудь бизнес логику, и при развитии кода это разъезжается, и потом у заказчика в одном месте работает так, а в другом по другому. Тогда, да, такой копипаст зло. Но есть случаи, когда визуально выглядит как копипаст, а негативные последствия минимальны (контролируются компилятором и т.п.).
засахарить это дело не так уж и сложно
а что страшно то?
а что страшно то?спагетти в итоге получается: одновременно в перемешку используется четыре разных кода:
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
Но есть случаи, когда визуально выглядит как копипаст, а негативные последствия минимальнырефакторятся такие штуки очень плохо.
вот этот код поменять
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);
}
уже прифигеешь.
вот этот код поменятьЭтот-то как раз просто: сменой сигнатуры метода.
Этот-то как раз просто: сменой сигнатуры метода.пока параметры просто передаются - да, а если еще заложена какая-то более сложная логика: перестановка аргументов, конвертация аргументация, условия и т.д., то уже..
пока параметры просто передаются - да, а если еще заложена какая-то более сложная логика: перестановка аргументов, конвертация аргументация, условия и т.д., то уже..но сейчас обсуждаем как раз "тупые" ситуации без копипаста логики.
пока параметры просто передаются - да, а если еще заложена какая-то более сложная логика: перестановка аргументов, конвертация аргументация, условия и т.д., то уже..В этом и есть отличие копипасты от чего-то нормального: тебе стоило написать про переименование столбца в коде сверху.
Не, я про то, о чем тут разговор идет.Речь идёт про какое-то поделие Шурика, которое он безуспешно рекламирует в этом разделе. Коротко весь топик выражен в первых двух постах: можно так писать на C#, а можно не писать.
ага, твои выводы на том же уровне как тогда, когда ты "как эксперт" обсуждал ASP.NET, но между делом выяснилось, что ты не знаешь, когда страница компилируется. В этом треде то же самое, по твоим вопросам можно судить насколько глубоко твое понимание обсуждаемого вопроса. Ты похож на наших друзей тестеров индусов, которые нифига не разбираются, но хорошо кликают по кнопочкам, и от них есть некоторая польза. И от тебя есть некоторая польза.
тебе стоило написать про переименование столбца в коде сверху.что именно ты хочешь?
когда ты "как эксперт" обсуждал ASP.NETЭто у тебя такое впечатление от темы, когда ты "как эксперт" пытался обсуждать другие технологии против ASP.NET, а выяснилось, что ты не знаешь ни того, ни другого?
Ты похож на наших друзей тестеров индусовТак вот, кто тестирует ваши поделки. Обычно, когда я читаю "у нас работают индусы", то жалею тех, кто с ними работает. Но в этот раз мне жаль индусов.
И от тебя есть некоторая польза.Спасибо. О тебе я такого сказать, к сожалению, не могу.
что именно ты хочешь?Всё что я хотел, я узнал по теме выше. Понимающие люди, которые дочитали тему до конца, сделали свои выводы. Я все выводы сделал ещё во втором посте, кстати, там я ни капли тебя не троллил.
Так вот, кто тестирует ваши поделки. Обычно, когда я читаю "у нас работают индусы", то жалею тех, кто с ними работает. Но в этот раз мне жаль индусов.Индусы нам навязаны свыше, как бы в нагрузку к проектам. Ну а раз уж они есть, мы им находим хоть какое-то применение, так же и с тобой, раз уж у тебя такое желание пообщаться .
Индусы нам навязаны свыше, как бы в нагрузку к проектам.Меня всё-таки не покидает стойкое ощущение, что это вы навязаны индусам в нагрузку.
спагетти в итоге получается: одновременно в перемешку используется четыре разных кода:это мне напоминает нытье Фаулера, типа скриплеты это ужасно. Однако, лучшее на сегодняшний день (после многолетних экспериментов, в том числе, и в java мире) это ASP.NET MVC, и если бы в Razor нормально ляммбды поддержали, то лучше и не надо. С текстом удобно работать как с текстом , и не нужно накручивать абстракции, которые тут же начинают течь.
sql,
склейка строк,
условия на добавления строк,
подстановка параметров.
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". Поддержать высокую степень композиции (составление запросов из кусочков) в этом подходе довольно сложно, такого еще никому не удалось. Не факт, что это вообще возможно. А если это и возможно, то, скорее всего, потребуется, чтобы язык программирования и СУБД разрабатывала одна команда, что не реально в современной ситуации.
необходимо в ручную следить, чтобы 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 ...
Ты фактически предлагаешь при переходе 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
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) })
этого нет в варианте выше.
Таким образом, копипасты "с негативными последствиями" нет.
Можно просто запустить чекинг запросов в режиме дебага и увидеть как клеится запрос:вариантов при этом 2^кол-во if-ов
также тебе пришлось добавлять where 1=1 для общности, что не во всех случаях проходить.
вариантов при этом 2^кол-во if-овкак написано у Фаулера выше, это только для подсказки на вопрос "what exactly do I have here?", тут не обязательно смотреть все варианты, это всего лишь подсказка (в случае поддержки JetBrains можно сделать просмотр всех уникальных вариантов).
также тебе пришлось добавлять where 1=1 для общности, что не во всех случаях проходить.
Это наиболее простой вариант для данного случая, если не работает, то можно по другому клеить строки, но всегда есть возможность склеить как требуется.
Это наиболее простой вариант для данного случая, если не работает, то можно по другому клеить строки, но всегда есть возможность склеить как требуется.Если мы свернули на путь "Data operation as data structure", то там обычно проблемы посерьезнее склейки строк: жертвуем производительностью, закрываем возможности СУБД, копипаст логики и т.д.
это не является копипастой, поскольку итерация по коллекции разных типов.Я понял, у индусов копипаста - это вовсе не копипаста, а норма.
Можно не писать? Пока что, даже если включить сценарии с injection, юнит тесты будут меньше по размеру, чем обвязка твоей библиотеки.Напиши свой вариант кода. Запрос и тесты.
Напиши свой вариант кода. Запрос и тесты.У меня другое предложение: сам напиши другой вариант кода, запрос и тесты. Это ты должен доказывать, что твоя библиотека хоть на что-то пригодна, а не наоборот.
У меня другое предложение: сам напиши другой вариант кода, запрос и тесты. Это ты должен доказывать, что твоя библиотека хоть на что-то пригодна, а не наоборот.Так и знал, что код не напишешь. Пока кода нет, твои слова выше выглядят как пустая болтовня.
Если запрос и тесты (Code0) напишу я, то это максимум, что докажет, что мой способ написания Code0 проигрывает коду с использование Controllable Query. Кому это надо?
При использовании Controllable Query для валидации запросов не требуются данные, сохраняемые в БД. Как с этим в твоих вручную написанных тестах?
Так и знал, что код не напишешь. Пока кода нет, твои слова выше выглядят как пустая болтовня.Пока нет сравнения твоей технологии с нормальным качественным кодом, а не выблевом бешеной пальмовой обезъяны, твои слова выглядят ещё хуже, чем пустая болтовня.
Если запрос и тесты (Code0) напишу я, то это максимум, что докажет, что мой способ написания Code0 проигрывает коду с использование Controllable Query. Кому это надо?Без этого нельзя до конца понять, умеешь ли ты нормально программировать. Пока что весь твой код в этой теме представляет из себя кучу write-only дерьма, отформатированного в стиле "бешеный Микки Маус".
При использовании Controllable Query для валидации запросов не требуются данные, сохраняемые в БД. Как с этим в твоих вручную написанных тестах?Данные тебе в любом случае потребуются, мы это выяснили выше.
Данные тебе в любом случае потребуются, мы это выяснили выше.Нет, данные не требуются.
Нет, данные не требуются.То есть ты без данных проверишь эту опечатку:
if (_.ModelCode.HasValue) builder.Append("AND ModelPublication.ModelCode = " + _.ModelCode.ValueOrEx);
if (_.ModelSequence.HasValue) builder.Append("AND ModelPublication.ModelCode = " + _.ModelSequence.ValueOrEx);
Не так давно ты сказал про неё:
Опечатка вот здесь “ModelPublication.ModelCode”? Тогда это не ловится, но оно также не ловится и в LINQ.
Опечатка вот здесь “ModelPublication.ModelCode”? Тогда это не ловится, но оно также не ловится и в LINQ.Да, это так.
Да, это так.То есть ты без данных проверишь эту опечатку в юнит тестах?
При использовании Controllable Query для валидации запросов не требуются данные, сохраняемые в БД. Как с этим в твоих вручную написанных тестах?
В теоретическом плане полагаю, что для задачи, которую ты сформулировал, решение существует. Если очень надо, то этим можно заняться.И так, для того, чтобы написать тесты под твоей библиотекой всё равно нужны данные. И если покликать на "re:...", то можно прочитать:
То есть при использовании твоей библиотеки всё равно надо написать юнит тесты, которые одновременно покроют всё, что делает твоя библиотека. А ещё они будут кроссплатформенные, не будут накладывать кучу ограничений, и т.п.
под твоей библиотекойControllable Query не требует наличие данных. Данные не нужны для работы Controllable Query.
Controllable Query не требует наличие данных. Данные не нужны для работы Controllable Query.При использовании Controllable Query всё равно надо написать юнит тесты, которые одновременно покроют всё, что делает Controllable Query. А ещё они будут кроссплатформенные, не будут накладывать кучу ограничений, и т.п. При использовании Controllable Query нельзя написать юнит тесты без данных.
При использовании 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 делает по сути такую же проверку.
Кто-то пишет много unit-тестов, кто-то меньше.Пока что мы выяснили, что в твоём случае либо тестов столько же; либо они по размеру меньше необходимой обвязки.
Еще Controllable Query найдет все запросы.
Конечно же, не найдёт. Потому что это поделие нельзя использовать для всех запросов. И даже когда оно будет готово хотя бы на 90%, а сейчас оно не готово и на 20%, ситуация не изменится.
в обоих пунктах ты ошибаешься
здесь подставляется переменная или эскейптнутое значение?Если смущает , то вот прикрутил возможность добавлять параметры в стиле 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.
Кстати, конкатенацию строк в таких простых случаях вообще можно убрать:
select * from foo where (:param1 is null or :param1 = foo.bar) and (:param2 is null or :param2 = foo.baz)
Можно прикрутить Find Usages
В случае оракла зависимость хранимок от таблиц можно вытаскивать из базы простейшим sql запросом.
В случае ораклаКакого ещё Оракла? Ты что, не знаешь, что не существует продуктов, выпущенных не Microsoft и не JetBrains?
Если так хочется статической типизации, то можно все запросы завернуть в хранимки или вьюхи. Тогда правильность хранимок и вьюх будет контролировать СУБД(в компайл-тайм). Единственным нетипизированным местом останется вызов хранимок из C# кодадля не динамического sql-я таких решений полным полно, в этом случае C#-методы могут просто генерироваться по хранимым процедурам. Но в реальности часто требуется динамических sql. Не городили бы тогда LINQ.
select * from foo where (:param1 is null or aram1 = foo.bar) and (:param2 is null or aram2 = foo.baz)
это известный антипатерн, так делать не рекомендуется (не всегда подходит). По крайней мере, MS SQL Server генерирует не оптимальный план запроса. Как у других СУБД с этим, точно не скажу, но вроде это принципиальная трудность, связанная с кешированием планов запросов. Для того чтобы вырезать то или иное условие СУБД должна проанализировать запрос с учетом текущих параметров, а анализ запроса не делается, если план закеширован. Отказываться от кеширования планов запросов не всегда подходит.
В случае оракла зависимость хранимок от таблиц можно вытаскивать из базы простейшим sql запросом.в MS SQL Server тоже самое. Я об этом писал выше, когда рассказывал возможный алгоритм реализации Find Usages.
уже не совсем 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].
Тут дело не в C#-е, а в статической типизации.есть два варианта раскрытия when внутри select-а.
в простом варианте, при наличии when тип поля заменяется (при необходимости) на Nullable.
соответственно тип записи всегда один и тот же, и содержит поле ModelCode, и данный код скомпилится, но когда isYes - false, то ModelCode будет null
в более сложном варианте (но это мало применимо в .net-е, java-е, C++ и т.д. из-за отсутствия поддержки вариационных классов) создается два типа: один без поля ModelCode, второй с полем ModelCode. record при компиляции имеет первый тип, соответственно, чтобы получить доступ к ModelCode необходимо скастить ко второму типу.
есть два варианта раскрытия when внутри select-а.Оба варианта какое-то издевательство над статической типизацией.
в простом варианте, при наличии when тип поля заменяется (при необходимости) на Nullable.
соответственно тип записи всегда один и тот же, и содержит поле ModelCode, и данный код скомпилится, но когда isYes - false, то ModelCode будет null
в более сложном варианте (но это мало применимо в .net-е, java-е, C++ и т.д. из-за отсутствия поддержки вариационных классов) создается два типа: один без поля ModelCode, второй с полем ModelCode. record при компиляции имеет первый тип, соответственно, чтобы получить доступ к ModelCode необходимо скастить ко второму типу.
Оба варианта какое-то издевательство над статической типизацией.во втором варианте - всё хорошо со статической типизацией, если ее правильно готовить.
плохо вот это: "скастить"
плохо вот это: "скастить"можно статически проверить каст правомерен или нет
можно статически проверить каст правомерен или неткаким образом? напиши код. Хочу, чтобы мне не дали перепутать, где кастить в ветке isYes или !isYes.
каким образом?для начала вот такой код не может упасть в 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)
{
//никогда не выполнится
}
}
проверить такое можно, но если делать в лоб, то легко вылетить на экспонециальный рост кол-ва проверяемых вариантов.
я правильно понимаю, что ты фактически хочешь, чтобы в следующем коде тебе сказали, что 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;
}
//здесь не компилируется, что делать?
}
}
расскажи что делать вот в этом коде: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;
}
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, не включая наследников
А если смущает немного громоздкий синтаксис, то это дело привычки, через пару дней всё хорошо читаешь и понимаешь;Через пару дней? Но на Brain Fuck хватает всего пары часов!1
ты то что используешь для доступа к базе?
ты то что используешь для доступа к базе?Какая разница, я это не рекламирую и не собираюсь.
Я вовсе не против какой-то деятельности в направлении статической типизации или каких-то других безумных идей. Но только когда в итоге получается read-write код меньшего размера, чем сумма лобовой копипасты с юнит тестами. И по которому хотя бы можно сделать review, который, как известно, выявляет намного больше проблем, чем статическая типизация вместе с юнит тестами и другими проверками. И ещё я не против дурацких поделок, если их автор не упёрт, как баран, и прислушивается к разумной критике.
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 и наследования в данной ситуации мне не нравится.
Какая разница, я это не рекламирую и не собираюсь.для окружающих - это скорее минус, чем плюс. получается они не могут использовать твой опыт, а могут использовать только свой опыт и опыт -а.
> И ещё я не против дурацких поделок, если их автор не упёрт, как баран, и прислушивается к разумной критике.
Критика является разумной, когда критикуются способы достижения целей, а не сами цели. Ты же критикуешь именно цели, а не способ достижения - это далеко от разумности.
> Но только когда в итоге получается read-write код меньшего размера, чем сумма лобовой копипасты с юнит тестами. И по которому хотя бы можно сделать review, который, как известно, выявляет намного больше проблем, чем статическая типизация вместе с юнит тестами и другими проверками.
это твои цели, и они не совпадают с целями . при этом ты хочешь, чтобы следовал тем же целям, что и ты. зачем тебе это надо?
зы
в моем понимании, -а интересует предсказуемость изменений кода. тесты и review эту задачу не решают.
P.S. Но применение именованных типов IR0, IR1 и наследования в данной ситуации мне не нравится.да, в шарпе и .net-е в большинстве случаев лучше использовать жирные типы.
ни язык(c# ни платформа (.net) не имеют средств для работы с большим кол-вом типов.
Критика является разумной, когда критикуются способы достижения целей, а не сами цели. Ты же критикуешь именно цели, а не способ достижения - это далеко от разумности.Я задал большое количество вопросов, половина из которых осталась без ответа, на другую половину был написан код, который невозможно прочитать, не то что поддерживать. Отсюда был сделан вывод: рекламируется неудобное поделие, которое невозможно использовать и которое не имеет плюсов по сравнению со стандартными подходами.
в моем понимании, -а интересует предсказуемость изменений кода. тесты и review эту задачу не решают.Вряд ли кого-то интересует предсказуемость изменений write-only кода. Да и задача не решена автором даже в таком варианте.
Вряд ли кого-то интересует предсказуемость изменений write-only кода.стоит перевести на осознаваемый уровень, что такое "читабельность кода".
в моем понимании, читабельность кода складывается из трех факторов:
1. есть ключевые точки, прочитав которые и не читая все остальное - можно восстановить общую структуру кода и общее поведение кода,
2. локальные изменения можно внести не разбираясь со всем кодом.
3. для чтения кода можно использовать уже имеющиеся навыки.
код -а не плох по первым двум показателям, а есть только проблема по п.3 из-за страшноватого синтаксиса. Но как раз этот фактор наименее объективен, и зависит лишь от того, у человека уже был опыт работы с данным кодом, или еще нет.
третий фактор больше говорит о пороге входа, а не о самой читабельности кода.
1. есть ключевые точки, прочитав которые и не читая все остальное - можно восстановить общую структуру кода и общее поведение кода,
Я и не спорю, что в коде автора есть много ключевых точек: одна на первой странице 27", вторая на второй, и так далее.
2. локальные изменения можно внести не разбираясь со всем кодом.
Действительно, зачем разбираться в чём-то, если единственный вариант - это copy-paste?
код -а не плох по первым двум показателямКак ты смеешь высказываться подобным образом об этом простом, понятном и читаемом коде?
третий фактор больше говорит о пороге входа, а не о самой читабельности кода.Вот и я говорю: самый читаемый код имеет самый высокий порог вхождения.
Вот и я говорю: самый читаемый код имеет самый высокий порог вхождения.это разовая проблема.
это разовая проблема.Ага, второй кактус разжевать проще, чем первый. А потом уже вся глотка в колючках, так что похер.
Ага, второй кактус разжевать проще, чем первый. А потом уже вся глотка в колючках, так что похер.ты так говоришь, как будто кактусы можно не есть при доступе к бд из кода.
разница лишь в том, что для той системы которую ты используешь для доступа к бд - кактус ты съел давно, глотка уже зажила, а в нужных местах подушки привязаны
ты так говоришь, как будто кактусы можно не есть при доступе к бд из кода.Представь себе, можно не есть. Но кому-то из этого раздела это не грозит.
Представь себе, можно не есть. Но кому-то из этого раздела это не грозит.а способ ты не представляешь, из-за опасений что вдруг окажется, что это не так?
иначе я плохо понимаю, почему ты не написал тоже самое в виде: я использую систему Zzz, там никаких кактусов нет.
а представить способ ты боишься, потому что вдруг окажется, что это не так?У меня нет какого-то "способа". А даже если и буду придумывать, то в разделе полно принцесс на горошине, всё равно у какой-то будут синяки.
У меня нет какого-то "способа".то есть: я не знаю как задачу решать, но представленный способ осуждаю?
то есть: я не знаю как задачу решать, но представленный способ осуждаю?Я просто аргументированно критикую представленное для критики поделие. И я без понятия, при чём тут знания о том, как решать какую-то задачу.
И я без понятия, при чём тут знания о том, как решать какую-то задачу.критика имеет смысл, если можно решить ту же задачу лучше. если же способа решить лучше нет, то критика по большому счету бессмысленна.
сейчас же ты фактически сказал, что ты не знаешь способа решить представленную задачу лучше.
критика имеет смысл, если можно решить ту же задачу лучше. если же способа решить лучше нет, то критика по большому счету бессмысленна.Ты не читал мои сообщения в этом эпическом треде? Технология для работы с базой данных должна позволять менять уровень изоляции и выполнять несколько запросов в рамках одной транзакции. Поделие, которое замахивается на "JetBrains сделает поддержку", должно быть хотя бы с намёком на кроссплатформенность. И так далее...
сейчас же ты фактически сказал, что ты не знаешь способа решить представленную задачу лучше.Сейчас ты фактически приписал мне какие-то слова, которых я не говорил.
Технология для работы с базой данных должна позволять менять уровень изоляции и выполнять несколько запросов в рамках одной транзакции.транзакция обычно привязывается к соединению (или вообще к текущему scope а не к запросу.
и данный код без изменения стыкуется с тем же TransactionScope
> должно быть хотя бы с намёком на кроссплатформенность.
намек на кроссплатформенность и здесь есть: пока используется портируемый sql - код без изменений будет работать для любой базы.
и данный код без изменения стыкуется с тем же TransactionScopeТы меня неправильно понял, я вовсе не собираюсь об этом с тобой переписываться.
Ты меня неправильно понял, я вовсе не собираюсь об этом с тобой переписываться.ок. скажу по другому: ты привел какие-то замечания.
они все имеют тривиальные ответы:
либо - нет, это не представляет в данном случае проблемы,
либо - да, проблема есть, но и при других подходах проблема остается такой же.
соответственно, сложно говорить о какой-то конструктивности критики.
они все имеют тривиальные ответы:Скорее, они все остались без ответа и без практических примеров.
соответственно, сложно говорить о какой-то конструктивности критики.Соответственно, очень сложно говорить о какой-то конструктивности рекламы данного поделия; и естественно, что при этом критика может казаться тебе неправильной.
Скорее, они все остались без ответа и без практических примеров.ты же только что сказал, что ты об ответах и слышать не хочешь.
вроде логично, что когда не слышишь ответов, то вопросы так остаются без ответов.
ты же только что сказал, что ты об ответах и слышать не хочешь.Я только что сказал, что не собираюсь с тобой переписываться на эту тему.
Но вот если ты возьмёшься скачать поделие автора, напишешь примеры кода по всем поставленным мною проблемам, то тогда это можно будет сравнить хотя бы с использованием ADO.NET и юнит тестов. Иначе мы можем нафлудить ещё две-три сотни сообщений с тем же уровнем конструктива, в котором ты меня обвиняешь.
напишешь примеры кода по всем поставленным мною проблемама представить как код будет выглядеть без его написания ты не можешь?
а представить как код будет выглядеть без его написания ты не можешь?И не могу, и не хочу. Ещё раз повторю мысль, которую высказал выше: я не должен доказывать, что поделие автора является гавном, а автор должен доказывать обратное.
я не должен доказывать, что поделие автора является гавном, а автор должен доказывать обратное.не симметричность справедлива только для отношений продавец-покупатель (и то, только для случая, когда спрос меньше предложения).
а здесь вроде форум, а не рынок - и никто тебе не навязывает роль покупателя.
соответственно, автор отвечает лишь за объяснение зачем ему это надо. выяснить нужно ли это тебе, и зачем - это уже твоя ответственность.
и никто тебе не навязывает роль покупателяАга, зато роль продавца всё время навязывают.
Ага, зато роль продавца всё время навязывают.Подожди, ты решил, что тебе тут навязывают роль продавца open source проекта? И как после этого тебя считать адекватным человеком?
Подожди, ты решил, что тебе тут навязывают роль продавца open source проекта? И как после этого тебя считать адекватным человеком?Я только что понил, ты воспринимаешь окружающий мир через делегаты и анонимные типы данных.
через делегаты и анонимные типы данныхбедненький, ты их до сих пор боишься
бедненький, ты их до сих пор боишьсяТы написал бота, который пишет на форуме логически не связанные посты? Впрочем, текст каждого твоего высера тоже логически не связан, бот бы справился лучше...
таким образом не получится поймать ошибку/опечатку вида
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-ово получается.
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
теперь можно писать вот таквсе запросы автоматически находятся и проверяются. Можно прикрутить Find Usages и т.п.