Data Access Layer для простых смертных

kokoc88

Привожу пример использования моего подхода к разработке кода для доступа к базе данных. Пример сделан на основе модельной задачки, с которой не справился его святейшество патриарх статической типизации.
Моё решение для обычных смертных. Простые вещи стараюсь делать просто. Не использую навороты, которые как бы призваны сделать разработку более безопасной, но в итоге занимают объём в два раза больше, чем прямолинейное решение вместе с несколькими юнит тестами. Задачку решил на небольшом фреймворке из семейства Dapper и ORM Lite собственного производства.
Теперь модельная (в смысле предназначенная для демонстрации кода) задачка.
1. Требуется выгрузить котэ из таблицы Cat (Id PK, Alias, Age, Ver) и истории про котэ из табицы Story (Alias PK, Story, Age) в виде фиксированных типов данных. Названия полей в типах данных могут не совпадать с названиями столбцов.
2. Отфильтровать истории при условии, что фильтр нельзя выполнить в базе данных.
3. Объединить истории и котэ по Alias при условии, что это не выгодно делать на стороне базы данных.
4. Сделать список для записи результатов объединения в таблицу CatStory(Id PK, Preview, Age где Id считывается из котэ, Preview получается с помощью применения некоторой сложной операции к паре котэ - история, а Age является суммой Age в этой же паре.
5. Выполнить MERGE USING VALUES в таблицу CatStory по Id, по результатам либо вставить новую строку, либо обновить поля Preview и Age.
6. Обновить таблицу всех котэ, устанавливая Ver = Ver + 1
7. Запросы 5 и 6 выполнить в одной транзакции с уровнем изоляции repeatable read батчами большого размера.
8. Забить на неточности, потому что задачка модельная. Но если что-то совсем не сходится - пишите, исправлю.
А теперь cенсация! Пост в Development с примером кода!
    public class DataAccessSample
{
class CatDto
{
[Column("Id")]
public int Id { get; private set; }

[Column("Alias")]
public string Name { get; private set; }

[Column("Age")]
public int Age { get; private set; }

[Column("Ver")]
public int Version { get; private set; }
}

class StoryDto
{
[Column("Alias")]
public string Name { get; private set; }

[Column("Story")]
public string Text { get; private set; }

[Column("Age")]
public int Age { get; private set; }

StoryDto
{
}

public StoryDto(string name, string text, int age)
{
Name = name;
Text = text;
Age = age;
}
}

class CatStoryDto
{
[Column("Id")]
public int Id { get; private set; }

[Column("Preview")]
public string Preview { get; private set; }

[Column("Age")]
public int Age { get; private set; }

CatStoryDto
{
}

public CatStoryDto(int id, string preview, int age)
{
Id = id;
Preview = preview;
Age = age;
}
}

readonly string connectionString;

public DataAccessSample(string connectionString)
{
this.connectionString = connectionString;
}

IEnumerable<CatDto> GetCats
{
using (var sql = new SqlServerBatch(connectionString, IsolationLevel.Unspecified
return sql.ReadObjects<CatDto>("SELECT * FROM Cat");
}

IEnumerable<StoryDto> GetStories
{
using (var sql = new SqlServerBatch(connectionString, IsolationLevel.Unspecified
return sql.ReadObjects<StoryDto>("SELECT * FROM Story");
}

void UpdateStories(IEnumerable<CatStoryDto> list)
{
const string updateCommand = @"
MERGE INTO CatStory
USING VALUES(@List) AS Source(Id, Preview, Age)
ON PreviewTable.Id = Source.Id
WHEN NOT MATCHED BY TARGET THEN
INSERT VALUES(Source.Id, Source.Preview, Source.Age)
WHEN MATCHED THEN
UPDATE SET Preview = Source.Preview, Age = Source.Age;";

const string versionCommand = @"
UPDATE Cat
SET Ver = Ver + 1
WHERE Id IN (@List)";

using (var sql = new SqlServerBatch(connectionString, IsolationLevel.RepeatableRead
{
foreach (var partition in list.Partition(300
sql.Write(updateCommand, new {List = partition.ToList});
foreach (var partition in list.Select(story => story.Id).Partition(1000
sql.Write(versionCommand, new {List = partition.ToList});
sql.Commit;
}
}

bool VeryComplexFilter(StoryDto story)
{
return story.Text.Contains("something");
}

string VeryComplexPreview(CatDto cat, StoryDto story)
{
return story.Text.Substring(100);
}

public void Run
{
var filter = GetStories.Where(VeryComplexFilter);
var join = GetCats.Join(filter, cat => cat.Name, story => story.Name,
(cat, story) => new CatStoryDto(cat.Id, VeryComplexPreview(cat, story cat.Age + story.Age;
UpdateStories(join);
}
}

Dasar

Не похоже на реальную работу с БД в большом сложном проекте. Нет самого главного.
- Условных фильтров, выполняемых на стороне БД.
- Условного выбора подмножества колонок (часть колонок тяжелые)

kokoc88

Не похоже на реальную работу с БД в большом сложном проекте. Нет самого главного.
Мои текущий и предыдущий проекты - большие и сложные. В обоих я обошёлся без того, что ты назвал "самым главным". Потому что ты привёл весьма специфические требования, которые нужны в худшем случае для пары-тройки запросов на весь проект. Нет смысла ради этого усложнять сотни других запросов.
Опять же, хочу подчеркнуть, что простые вещи я стараюсь делать просто. Глупо вообще рассматривать какое-то решение, которое простые вещи решает уже сложно.

Dasar

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

kokoc88

В измеримых штуках - это сколько?
Размер базы
Кол-во записей
Кол-во запросов в секунду на пике
Кол-во функциональных точек
Кол-во сущностей в проекте
Кол-во полей у сущности (в среднем)
Кол-во таблиц в БД
Кол-во полей в таблице(в среднем)
Кол-во сущностей и полей, участвующих в одном запросе (в среднем)?
Батенька, вы уже куда-то не в ту степь уехали. У меня солидное резюме, но оно не имеет отношения к этому тредику.

Dasar

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

marat7256

Твой "тезис" полностью соотвествует понятию "модельная задача" из первого поста.

kokoc88

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

Да, в примере кода я выполнил условие задачи. Я ведь три раза специально для таких как ты написал, что она модельная.
По-твоему, что именно я не смогу сделать в "большом и сложном проекте"?

Dasar

Твой "тезис" полностью соотвествует понятию "модельная задача" из первого поста.
нет. Модельная задача - это боевой проект в миниатюре. Задача "модельной задачи" показать все те способы, которые применяются на боевом проекте.
ps
Езда на трехколесном велосипеде не является модельной задачей для езды на мотоцикле.

marat7256

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

Dasar

Цель моих сообщений в этом треде:
 я делюсь тем, что мне печально. Когда я открывал этот тред, я ожидал большего. А увидел классический пример из книжки "Как научиться работать с БД из C# за 10 дней" с задачей, которая встречается только в таких книжках.
Какова цель твоих сообщений?

kokoc88

Когда я открывал этот тред, я ожидал большего. А увидел классический пример из книжки "Как научиться работать с БД из C# за 10 дней" с задачей, которая встречается только в таких книжках.
Я так понимаю, ты можешь решить эту тривиальную задачку намного лучше? Тогда мы ждём пример твоего кода!

marat7256

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

Dasar

Я так понимаю, ты можешь решить эту тривиальную задачку намного лучше? Тогда мы ждём пример твоего кода!
Сдаюсь сразу. На трехколесном велосипеде я лучше этого не проеду.

Dasar

Цель топика показать, что общение с БД может быть реальзовано в простых конструкциях какого-то языка
Я хочу заострить внимание на том, что:
- цель этого топика: показать, что общение с БД в простых случаях может быть реализовано в простых конструкциях языка
- этот тред является логическим продолжением предыдущих тредов
- в предыдущих тредах шло обсуждение: общение с БД в сложных случаях

kokoc88

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

kokoc88

в предыдущих тредах шло обсуждение: общение с БД в сложных случаях
Прежде чем решать сложные задачи, надо научиться хорошо и просто решать простые. Не знаю о каких "предыдущих тредах" ты говоришь. Если о тех которые уже лет пять постит солнцеликий бог статической типизации, то там всё всегда заканчивается тем, что простые примеры решаются невзъебенно сложно.

Dasar

Мои сообщения читаются так:
- зашёл узнать что-то новое
- новое не узнал, был опечален
- по заголовку предвкушал обратное, был раздосован этим несоответствием

kokoc88

- по заголовку предвкушал обратное, был раздосован этим несоответствием
Заголовок тредика вполне очевидно указывает на простоту кода и бренность бытия. Видимо, ты не только не прочитал пост, не заметив, что задачка модельная; но и не прочитал его название.

Dasar

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

Твой код не переносится на эту задачу.

yroslavasako

Не знаю как в .Net, а в JavaFX ты такую работу с БД даже в гуёвую табличку не засунешь, потому что там используются observable collection с частичным обновлением.

kokoc88

Он решает более сложную классическую задачу, чем у тебя в примере.
Я ещё раз повторю: мне по барабану, какую задачу он решает. Код решения простых случаев такой, что хочется сгореть на месте. Видимо, именно так и чувствуют себя смертные перед ликом божественного создания с абсолютными скиллами работы с кодом.

kokoc88

Не знаю как в .Net, а в JavaFX ты такую работу с БД даже в гуёвую табличку не засунешь, потому что там используются observable collection с частичным обновлением.
Щито?

Dasar

Код решения простых случаев такой, что хочется сгореть на месте.
Что ты так волнуешься? Ну, не понимаешь ты код -а и какие задачи он решает. И чёрт с ним! В Mcrosoft тебя взяли и с твоим текущим подходом. Разве это не достаточный уровень подтверждения, что ты крут, а - нет?

yroslavasako

Предположим, ты выгружаешь данные не для пакетной обработки, а для отображения. Виджет таблички в JavaFX дважды умён. Во-первых, он хранит в памяти ровно столько ячеек, сколько отображается на экране, во-вторых, забирает их из observable collection, которая умеет посылать сообщения об обновлении, которые виджет анализирует на предмет не нужно ли ему запросить новые данные. В-третьих, необходимо как-то схему запроса отобразить в схему виджета, банально количество колонок указать нужное. Всё это рождает требования к инфраструктуре гораздо сложнее.
Сложность рождает сложность. Чтобы не повторять сложные конструкции по несколько раз тебе нужен полиморфизм относительно типов объектов, извлекаемых из БД. У тебя его не видно. По идее, должно быть что-то вроде хаскелловского Data.Binary

kokoc88

Всё это рождает требования к инфраструктуре гораздо сложнее.
Задача, которую ты описываешь, не связана и не должна быть связана (уже в терминах coupling) с Data Access Layer. И тем более с конкретной его реализацией.

kokoc88

Ну, не понимаешь ты код -а и какие задачи он решает.
Но я же хочу, как и ты, быть причастным к божественному. Вот и прошу решение простой задачки сделать проще, чтобы смертные могли разуметь. А там глядишь через кровавые жертвоприношения и осилим понять всё сущее.
В Mcrosoft тебя взяли и с твоим текущим подходом. Разве это не достаточный уровень подтверждения, что ты крут, а - нет?
Да как ты можешь такое заявлять? Сравнивать целый магазин автозапчастей с каким-то там поисковиком. Смотри, за это тебя исключат из апостолов.

6yrop

с несколькими юнит тестами
...
А теперь cенсация! Пост в Development с примером кода!
Где код юнит тестов?

kokoc88

Где код юнит тестов?
А где твой код решения задачки?

val63

Ты кажется изобрел BLToolkit с RSDN

kokoc88

Ты кажется изобрел BLToolkit с RSDN
При беглом взгляде совсем не похоже. Я же написал, что это из семейства Dapper. И ещё "изобрёл" - слишком сильное слово, там на всё от силы 500 строк кода.

pilot

Я думал, намного будет… Намного лучше будет это все. :crazy:

kokoc88

Я думал, намного будет… Намного лучше будет это все.
О, а я как раз ждал последнего покемона в этом тредике. You are welcome.

Dimon89

Для человека, который не в теме и просто мимо проходил: чем это лучше стандартного EntityFramework, который как раз и позиционирован для простых смертных?

kokoc88

Для человека, который не в теме и просто мимо проходил: чем это лучше стандартного EntityFramework, который как раз и позиционирован для простых смертных?
Это всё очень легко гуглится. Например: проблема генерации запросов в EF6, сравнение производительности EF7 и Dapper, ещё одно сравнение производительности, танцы с бубном для решения повседневных задач
В общем и целом EF, как и любая другая полновесная ORM, имеет ряд стандартных недостатков. Главными из них на сегодня являются производительность, отсутствие контроля над генерируемыми запросами (например, тяжелее избежать deadlock или сгенерировать приличного вида запрос и необходимость смешивания SQL и LINQ. В последнем случае вполне возможны ситуации, когда кода будет больше, чем на простой обёртке над ADO.NET Для сравнения можешь написать MERGE INTO из моей задачки на EF. (Вполне возможно, что в EF7 это вдруг стало просто.)
С другой стороны EF много лет развивается и совершенно ужасные вещи оттуда постепенно исчезают. Я пристально не следил за его развитием и использовал его только в тех случаях, когда у меня были разовые проекты, которые можно написать и забыть. Думаю, что для более полного сравнения надо оценивать EF7, ожидания от которого очень и очень высокие. Впрочем, как и ожидания от всех других версий EF. :)

marat7256

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

Внезапно - controlable query!

kokoc88

Внезапно - controlable query!
Внезапно ты прав! Контроль над SQL толкается как основная фишка ко-ку.

Dasar

чем это лучше стандартного EntityFramework, который как раз и позиционирован для простых смертных?
В варианте -а, есть сахар для table-value parameters. Остальное очень похоже на System.Data.Linq или EF.

luna89

Код ок, но по-большому счету, он доказывает, что программирование оперденей в тупике. Простейшая задача - две связанные таблички, которые надо редактировать. Все что тут нужно - описание табличек, все остальное - выдуманная сложность. DTO с геттерами и сеттерами являются копипастой описания таблиц. Кастомный код для вставки в эти две таблицы тоже не нужен, по большому счету - он будет одинаковым для любой пары связанных таблиц, отличаться будут только названия этих таблиц.
DTO приходят с клиента, через многочисленные RPC вызовы - создать отдельно кошечек, удалить кошечек, обновить кошечек, создать кошечек вместе с историями, добавить связь кошечек с историей, удалить связь кошечки с историей, тысячи их.
На клиенте можно продолжать безумие - создать описания DTO на typescript, или разработать клиентский ОРМ, который будет обращаться к серверному ОРМу.
Реально, все что нужно - декларативное описание схемы с декларативным описанием прав доступа, из которого сразу бы следовал протокол синхронизации.

kokoc88

Простейшая задача - две связанные таблички, которые надо редактировать.
Здесь решена другая задача.
Кастомный код для вставки в эти две таблицы тоже не нужен, по большому счету - он будет одинаковым для любой пары связанных таблиц, отличаться будут только названия этих таблиц.
Какой код для вставки в две таблицы ты имеешь в виду?
Реально, все что нужно - декларативное описание схемы с декларативным описанием прав доступа, из которого сразу бы следовал протокол синхронизации.
Реально всё, что нужно - это быстро и качественно решать задачи. Не знаю, что именно ты понимаешь под декларативностью, но во что выливается генерация SQL запросов можешь найти выше в моём посте про EF.

Dasar

> Не знаю, что именно ты понимаешь под декларативностью, но во что выливается генерация SQL запросов можешь найти выше в моём посте про EF.
в EF не пахнет особой декларативностью. Там enterpise-ность.

kokoc88

в EF не пахнет особой декларативностью. Там enterpise-ность.
Чтобы не сводить всё к определению того, что такое декларативность, а это на 153% закончится холирваром, лучше просто увидеть, какой, скажем так, "текст" имеет в виду вместо императивного кода. Для решения всё той же простой задачки.

luna89

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

luna89

Реально всё, что нужно - это быстро и качественно решать задачи. Не знаю, что именно ты понимаешь под декларативностью, но во что выливается генерация SQL запросов можешь найти выше в моём посте про EF.
Я вижу ты обновляешь поле versionId. В реальной программе надо думать о concurrency и делать compare and swap по полю versionId. Надо получать от клиента список записей, делать CAS по каждой записи отдельно, и возвращать клиенту обратно список записей, которые он засабмитил, причем для записей, у которых не прошел CAS, выдавать две версии - ту которую засабмитил клиент, и ту которую засабмитил другой пользователь (чтобы пользователь мог их смерджить). Эти две версии должны выдаваться в стандартном формате (такого формата не существует кстати, надо самому придумывать).
Решать эту задачу каким-то императивным adhoc-кодом - безумие, в результате о concurrency никто не думает, придумывая отговорки типа "у нас вебскейл опердень, подумаешь какие-то данные проебутся".
Декларативное решение должно как минимум разруливать автоматически описанную ситуацию.

kokoc88

Я вижу ты обновляешь поле versionId.
У меня в решении задачки нет поля versionId.
В реальной программе надо думать о concurrency и делать compare and swap по полю versionId.
Где-то отсюда я вообще перестал понимать, что ты хотел сказать.

luna89

У меня в решении задачки нет поля versionId.
Есть поле ver, я так понял что это номер версии.
Где-то отсюда я вообще перестал понимать, что ты хотел сказать.
Ну, CAS - простейший concurrency примитив, решает например проблему lost update.

kokoc88

Есть поле ver, я так понял что это номер версии.
Да, поле Ver есть и оно обновляется в базе данных.
Ну, CAS - простейший concurrency примитив, решает например проблему lost update.
Я знаю, что такое CAS, но не понимаю, зачем он нужен для решения этой задачи. Здесь immutable DTO. Если же ты говоришь про работу с БД или архитектуру какой-то системы, то тогда я не понимаю, при чём тут код обращения к БД. Решение задачи, когда два пользователя обновили один и тот же объект, не зависит от технологии выполнения запросов. Кроме этого совершенно не ясно, как декларативное решение разрулит эту проблему автоматически. Это, конечно, можно запилить в виде какого-то адского DSL или фреймворка, но скорее всего получится нечто непригодное для эффективной, быстрой и простой разработки.

stm5872449

Позыв "давайте декларативно генерить опердени" имеет хоть одну историю успеха?

luna89

Решение задачи, когда два пользователя обновили один и тот же объект, не зависит от технологии выполнения запросов.
Как ты предлагаешь решать это в рамках твоего подхода? Мне кажется, тебе придется на каждую сущность писать бойлерплейт с проверкой номера версии.

6yrop

Все что тут нужно - описание табличек, все остальное - выдуманная сложность. DTO с геттерами и сеттерами являются копипастой описания таблиц.
В большинстве систем это генерируется по базе. Зачем Майк это всё руками пишет не ясно.
А в настоящих оперднях типа 1c или Axapta база встроена на уровне языка/среды, там нет разделения на базу и остальное, т.е. и генерировать незачем.
Для того, чтобы избегать кодогенерации в F# запилили Type Providers.

kokoc88

Как ты предлагаешь решать это в рамках твоего подхода? Мне кажется, тебе придется на каждую сущность писать бойлерплейт с проверкой номера версии.
Вообще не придётся. Эта задача хорошо ложится на повторное использование кода. А вот посмотреть на твои декларативные выкладки было бы интересно. Или мы сейчас обсужаем некоторую идеальную технологию, которой пока что не существует в природе?

6yrop

SELECT * FROM Cat
Есть ли какая-либо магия обработка sql-строки? Какой запрос уходит в SQL Server? Если добавить фото котиков ALTER TABLE Cat ADD Photo IMAGE. Будут ли грузиться картинки в память?

kokoc88

Есть ли какая-либо магия обработка sql-строки?
Есть и обработка строки, и обработка параметров.
Если добавить фото котиков ALTER TABLE Cat ADD Photo IMAGE. Будут ли грузиться картинки в память?
В этом конкретном примере будут. Но если тебе нужно работать с дебилами, которые могут сделать такой ALTER TABLE, или ты сам из таких, то в запросе лучше написать "SELECT @CatDto".

6yrop

@CatDto
Что такое CatDto? Имя типа? Из какого неймспейса? Есть ли документация с описанием фич твоего ORM?

marat7256

В код посмотри.

6yrop

В код посмотри.
В какой? Куда смотреть?

marat7256

В первом посте.

6yrop

Там нет ответа на мои вопросы.

marat7256

Ты там тип не увидел?

6yrop

Ты вопросы мои не увидел?

kokoc88

Что такое CatDto? Имя типа? Из какого неймспейса?
Что такое CatDto понятно по коду.
Есть ли документация с описанием фич твоего ORM?
Конечно. У меня детальные javadocs в исходном коде с описанием всех фич.

6yrop

Что такое CatDto понятно по коду.
Если я к твоему коду добавлю:

namespace MyNamespace2
{
class CatDto
{
string P1 { get; private set; }
string P2 { get; private set; }
}
}

Что сгенерируется в результате "SELECT @CatDto"?
Или там можно использовать только имя переданного дженерика?

kokoc88

Что сгенерируется в результате "SELECT @CatDto"?
То же самое.
Или там можно использовать только имя переданного дженерика?
Не только.

6yrop

То же самое.
Не только.
Какой алгоритм поиска типа по имени?
Почему не будет "SELECT P1, P2"?

kokoc88

Какой алгоритм поиска типа по имени? Почему не будет "SELECT P1, P2"?
А какая тебе, собственно говоря, разница? Не важно, что там умеет или не умеет этот фреймворк, я его рекламировать не собираюсь. Важно, что простая задача решена простым методом, а в этом и был смысл поста.

6yrop

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

marat7256

А с помощью controlable query она решается?

pilot

Эта простая задача отлично решается на ванильном ADO.NET, который все знают.
Поясни для простых смертных: ругая CQ майк сел в лужу и расхваливает свое поделие, даже кода которого показать не может? :confused:

kokoc88

Эта простая задача отлично решается на ванильном ADO.NET, который все знают.
Так напиши это на ко-ку и давай сравним решения на "отличность". Ведь ко-ку - это же ванильный ADO.NET, не так ли?

6yrop

Поясни для простых смертных: ругая CQ майк сел в лужу и расхваливает свое поделие, даже кода которого показать не может?
Да, Майк сел в лужу поскольку не выложил полностью код.
Не выложил:
юнит тесты, которые противопоставляет CQ;
нет кода ORM;
нет DDL скрипта базы.

marat7256

Если следовать твоей логике, то ты в луже просто утонул, ибо не выложил ничего вообще.

kokoc88

Да, Майк сел в лужу поскольку не выложил полностью код.
Реально? Я написал код, выложил его без всяких требований и дал столько великолепных возможностей меня потроллить. Блин, да когда я думаю обо всех этих возможностях мне хочется потроллить самого себя!
Но всё, на что ты оказался способен - это жалко промямлить какие-то несвязные претензии в ответ на кукареканье петушка _no_? Как школьница, которую вызвали к доске, и которая покраснела, потому что не сделала домашнее задание.
Скажи, это с тобой сделали ~100 веток и несколько тысяч коммитов в ко-ку; или многолетняя и тяжёлая работа в магазине автозапчастей?

6yrop

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

luna89

Вообще не придётся. Эта задача хорошо ложится на повторное использование кода. А вот посмотреть на твои декларативные выкладки было бы интересно. Или мы сейчас обсужаем некоторую идеальную технологию, которой пока что не существует в природе?
TLDR: я хочу напрямую работать с базой данных с клиента, чтобы можно было быстро прототипировать и потом мягко переходить с прототипа на решение продакшен уровня.
На чтение мне должны быть с клиента доступна вся схема (там могут быть какие-то вычисляемые или виртуальные поля, за которыми стоит произвольный код). Например, фейсбук позволяет делать FQL (типа SQL) запросы к данным, там можно даже какие-то джоины делать. Я думаю, что такой подход правильный, ему должны следовать все.
На запись база тоже должа быть открыта (это ограниченный подход, но позволяет быстро прототипировать).
Нужен стандартный формат синхронизации реляционных данных. Пейлоадом должен быть набор записей типа "добавился такой-то тапл, удалился такой-то, а у третьего обновились такие-то поля". Данные могут как отправляться пакетом, так и непрерывно стримиться по вебсокетам. В респонсе должен отдаваться для каждого тапла статус, удается ли его сохранить, или если не удается, то почему не удалось - ошибка валидации, если конфликт concurrency, то выдать запись, с какой произошел конфликт.
Это должен быть стандартный формат, чтобы не надо было велосипедить.
Кроме того, должна быть имплементация такого сервера. В простейшем случае он должен просто смотреть в базу данных, чтобы можно было быстро прототипировать. Для более сложных случаев нужна имплементация row level security, для еще более сложных - возможность выполнять кастомный код (sort of triggers). В реально сложных случаях работаем с базой уже через слой процедур (как это сейчас делается).

kokoc88

должны
Ты написал это слово 8 раз. Беда в том, что никто ничего никому не должен. Я хочу посмотреть на реальный пример декларативного подхода, решающего задачку в тредике, а не перечисление требований к какой-то системе. Если такой технологии нет, или она "должна быть", но её всё равно нет, то нет и предмета обсуждения.

6yrop

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

kokoc88

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

Codcod

Лучи РАДОСТИ тебе Майк!

6yrop

У тебя тоже будет возможность выложить обновленную версию под критерии.

kokoc88

У тебя тоже будет возможность выложить обновленную версию под критерии.
Это у тебя есть возможность написать код без критериев, как ты любишь.

6yrop

Есть и обработка строки, и обработка параметров.
Для этого используется Razor?

6yrop

Это у тебя есть возможность написать код без критериев, как ты любишь.
Ну глумитесь что ли. Писал в Sublime.
 
 
var stories = new {}.Apply(p => new SqlCommand("SELECT * FROM Cat").Query<Cat>.Join(
new {}.Apply(p => new SqlCommand("SELECT * FROM Story").Query<Story>.Where(VeryComplexFilter
cat => cat.Name, story => story.Name,
(cat, story) => new CatStory(cat.Id, VeryComplexPreview(cat, story cat.Age + story.Age;
MakeTransaction(IsolationLevel.RepeatableRead, transaction => {
foreach (var partition in stories.Partition(300
MergeCatStoryQuery(partition).Execute(transaction);
foreach (var partition in stories.Select(_ => _.Id).Partition(1000
UpdateCatQuery(partition).Execute(transaction);
});

static Query<Non> MergeCatStoryQuery(IEnumerable<CatStory> stories)
{
var command = new SqlCommand;
command.CommandText = new StringBuilder(@"
MERGE INTO CatStory
USING VALUES(").AppendTableValue(stories.Select(_ => new {_.Id, _.Preview, _.Source} command)
.Append(@") AS Source(Id, Preview, Age)
ON PreviewTable.Id = Source.Id
WHEN NOT MATCHED BY TARGET THEN
INSERT VALUES(Source.Id, Source.Preview, Source.Age)
WHEN MATCHED THEN
UPDATE SET Preview = Source.Preview, Age = Source.Age").ToString;
return command.Query<Non>
}

static Query<Non> UpdateCatQuery(IEnumerable<int> ids)
{
var command = new SqlCommand;
command.CommandText = new StringBuilder("UPDATE Cat SET Ver = Ver + 1 WHERE Id IN (")
.AppendList("@id", ids, command).Append(")").ToString;
return command.Query<Non>
}

bool VeryComplexFilter(Story story)
{
return story.Text.Contains("something");
}

string VeryComplexPreview(Cat cat, Story story)
{
return story.Text.Substring(100);
}

class CatStory
{
public int Id { get; }
public string Preview { get; }
public int Age { get; }

public CatStoryDto(int id, string preview, int age)
{
Id = id;
Preview = preview;
Age = age;
}
}

kokoc88

Ну глумитесь что ли. Писал в Sublime.
Глумиться рано. В пункте 1 моей задачи написано: "Названия полей в типах данных могут не совпадать с названиями столбцов." В моём примере видно, что Join выполнен по свойству Name, которое в таблице называется Alias. В твоём решении этот пункт не выполнен?

6yrop

"Названия полей в типах данных могут не совпадать с названиями столбцов."
Не вижу смысла давать стопицот имен одному и тому же. Только путаться.

kokoc88

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

6yrop

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

kokoc88

Ты эту задачу позиционировал как близкую к реальности. Откуда такое требование в реальности?
Сначала позиционировал. Но оказалось, что она всего лишь из книжки "как научиться за 10 дней". Теперь вот оказывается, что одно из простейших требований так сложно выполнить, что про него написано уже 4 поста. Но я буду снисходителен и приведу в пример твою же цитату:
А в настоящих оперднях типа 1c или Axapta база встроена на уровне языка/среды, там нет разделения на базу и остальное, т.е. и генерировать незачем.
Кто сталикивался с интеграцией околоодинэса, тот знает, что в реальности моё требование вполне себе обосновано. Так что пока что у тебя есть какой-то код, но в нём нет решения моей задачки из книжки "как научиться за 10 дней".

6yrop

Для общения с внешней системой я бы использовал отдельные механизмы, к Data Access Layer не имеющие отношения. Там отличий в структуре данных будет больше, чем переименование полей.

kokoc88

Для общения с внешней системой я бы использовал отдельные механизмы, к Data Access Layer не имеющие отношения. Там отличий в структуре данных будет больше, чем переименование полей.
Ну вот и напиши в своём коде эти механизмы. У тебя есть всё тот же один connection string.

6yrop

Как же я напишу, если я о внешней системе ничего не знаю.

kokoc88

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

6yrop

Как во внешнюю систему выгружаются .NET классы?

kokoc88

Как во внешнюю систему выгружаются .NET классы?
Щито? Я уже перестаю тебя понимать. Просто выполни требование задачки или скажи, что не можешь.

Dasar

USING VALUES(").AppendTableValue(stories.Select(_ => new {_.Id, _.Preview, _.Source} command)
.Append(@") AS Source(Id, Preview, Age)
Рассматривался ли следующий вариант?

... = Command("... USING VALUES({0}) ...", stories.Select(_ => new {_.Id, _.Preview, _.Source};

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

6yrop

Щито? Я уже перестаю тебя понимать. Просто выполни требование задачки или скажи, что не можешь.
Мой код делает ровно то же, что и твой. А как называть переменные это уже мое дело. Пожалуйста, расширяй функциональные требования в своей задачке. Сейчас все функциональные требования мой код выполнил.

6yrop

Рассматривался ли следующий вариант?
code:
... = Command("... USING VALUES({0}) ...", stories.Select(_ => new {_.Id, _.Preview, _.Source};
Имхо, общности не хватает, синтаксис для частного случае. У меня ванильные SqlCommand и StringBuilder с добавлением простого метода расширения AppendTableValue.

kokoc88

Мой код делает ровно то же, что и твой.
Нет, не делает. Не выполнено требование, что столблцы имеют другое название. Не надо втирать, что требование неправильное. Оно как раз из книжки "как научиться за 10 дней", там ничего сложного не пишут. Ты можешь отказаться, только если не можешь его выполнить.

6yrop

Razor используется в твоем ORM?

kokoc88

Razor используется в твоем ORM?
Это как-то влияет на то, что ты не можешь решить простейшую задачку?

6yrop

Просто ответь используется или нет?

kokoc88

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

6yrop

Как написать такую библиотеку можно узнать у , а то я позабыл автора и название книжки.
2 что за книжка?

6yrop

Естественно, я могу реализовать такое простое требование, есть множество вариантов. Можно просто повторить разметку атрибутами как во многих ORM, в том числе, в твоей. Можно просто перелить в другой тип (используя Select). Но я этого делать не буду, поскольку это лишнее. Всё работает и так.
Теперь ты ответь, используется или нет Razor в твоем ORM?

Dasar

2 что за книжка?
троллит. Не может забыть и простить, что предложенную им задачу я назвал модельной из книжки. )
ps
По парсерам фундаментальные книги:
- Ахо. Компиляторы
- Dick Grune. Parsing Techniques. A Practical Guide

kokoc88

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

6yrop

Ну вот выбери и реализуй. А пока что твой код вообще не работает.
Зачем мне реализовывать то, что я считаю лишним? Какая у меня мотивация? Код работает. Кто потребитель этих лишних требований? За это кто-то мне заплатит? Кто?

kokoc88

Зачем мне реализовывать то, что я считаю лишним?
За тем, что ты залез в мой тредик и приводишь решение моей задачки. Какой смысл начинать рассматривать твой код, если ты не осилил решить поставленную задачку.
Но если ты настаиваешь, то OK. [Сравнение.] Мой код решает поставленную задачу, твой код не решает. [Конец сравнения.]

Dasar

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

6yrop

Посмотри исходники Dapper-а. Там схожий синтаксис и скорее всего есть ответ на твой вопрос.
В Dapper наколеночный вариант на регекспах. Например, вот такой код дает неправильный результат.
 
 
var row = connection.Query<string>(
"SELECT 'test @@Ids' as c1 WHERE 1 IN @Ids",
new { Ids = new[] { 1, 2, 3 } }).ToList;

 
Предполагаю, что брал его за основу, а не писал полностью с нуля. Он не похож на любителя велосипедов.

У Майка есть синтаксис для table value constructor и @CatDto, в Dapper таких фич нет.
Майк подробно , как я встроил Razor в CQ. , что в моем репозитарии куча веток. Спрыгнул с темы: "", когда я начал расспрашивать подробности как обрабатывается строка. Четыре раза не ответил на прямой вопрос, используется ли Razor.
Как правильно заметил No, Майк не показывает код своего ORM.
Всё это наводит на подозрение, что Майк украл идею и реализацию из CQ и боится это открыть.

kokoc88

Всё это наводит на подозрение, что Майк украл идею и реализацию из CQ и боится это открыть.
Ты не боишься моего ответа, который последует за этим обвинением?

6yrop

Используешь Razor или нет?
Если нет открой код с алгоритмом как обрабатывается строка. Простые смертные должны знать как обрабатывается строка, которую они пишут.

kokoc88

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

6yrop

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

Dasar

У Майка есть синтаксис для table value constructor и @CatDto, в Dapper таких фич нет.
Table-value параметры и маппинг на поля - это семантика, а не синтаксис. На разборе выражения эти фичи не участвуют и не меняют процесс разбора выражения, они участвуют на этапе генерации конечного запроса на основе разобранного выражения и данных.

kokoc88

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

pilot

Он не похож на любителя велосипедов.
А я вроде как раз за велосипеды его и гнобил несколько лет назад :confused:

kokoc88

А я вроде как раз за велосипеды его и гнобил несколько лет назад
Ты гнобил? Звучит смешно, но ты ведь и правда в это веришь?

pilot

Ты гнобил? Звучит смешно, но ты ведь и правда в это веришь?
Главное что ты веришь, баттхертишь до сих пор ;)

Dasar

Любитель велосипедов - этот тот, кто пишет велосипеды для души! )
Вполне предполагаю, что у есть свои годные велосипеды. Но это будут сермяжистые велосипеды, решающие практичные задачи. В душе -а нет места для "еще одного парсера" мета-sql-ных выражений. Нет истинного размаха русской души - бессмысленного и беспощадного. )

kokoc88

В душе -а нет места для "еще одного парсера" мета-sql-ных выражений. Нет истинного размаха русской души - бессмысленного и беспощадного. )
Хмм... Это что за новый вид троллинга? *собака-вопросяка.жпг*

pilot

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

Dasar

кстати, IQueryable вообще не используешь при работе с БД? в своей библиотеке?

kokoc88

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

pilot

Если б капала у меня - я бы писал гандонистые посты про знакомых мне форумчан, которых собеседовал или с которыми знаком по работе. :o

kokoc88

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

Dasar

Вшмышле в его любимом ентерпрайзном говне нет нормального ORM, поэтому он вынужден выдумывать свой?
Штатные устаревают быстро. Крупные open source-ные тоже. И тем, и другим приходится много усилий тратить на обратную совместимость. Последние фичи sql-сервера, .net-а и c# появляются с сильным запаздыванием.
Соответственно, допиливание существующего проекта под себя - вполне выгодный и практичный подход. У -а вижу допиленные table-value-parameters и bulk. Первое появилось недавно, второе вспомогательная фишка. И то, и другое - ускоряет производительность. Это не особо важно на enterprise-ных проектах (им проще заливать проблемы железом но полезно на частных проектах.

kokoc88

кстати, IQueryable вообще не используешь при работе с БД? в своей библиотеке?
В смысле составления запросов по LINQ Expressions? Не использую. Я давно забил на то, чтобы сразу делать cross db проекты, предпочитаю использовать db specific синтаксис и технологии. Для этого проще писать запросы руками - они получаются более короткими и их проще прочитать. Ценность статической типизации считаю завышенной.

Dasar

Ценность статической типизации считаю завышенной.
Да, в IQueryable присутствует статическая типизация. Вторая фишка IQueryable-а - декомпозиция. Появляется возможность отделить друг от друга: маппинг таблицы на класс, фильтрацию, paging, сортировку и т.д.
Как у тебя получается обходится без декомпозиции в sql-запросах? Или ты её как-то по другому делаешь?

kokoc88

Как у тебя получается обходится без декомпозиции в sql-запросах?
Без какой декомпозиции?

6yrop

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

6yrop

Как у тебя получается обходится без декомпозиции в sql-запросах? Или ты её как-то по другому делаешь?
Он с 2010 года пилил в Связном один единственный проект, который сделал на антипатерне для реляционки key-value.
Теперь полгода работает в Бинге, грузит всё в память, как ты сам видел. Он не понимает проблематики. Не сталкивался.

Dasar

Без какой декомпозиции?
в одном месте - необходимы котики в возрасте до года, во втором - с alias-ом pretty, в третьем - version = 1
на IQueryable:

context.Cats
.Where(cat => cat.Age < 1)


context.Cats
.Where(cat => cat.Alias == "pretty")


context.Cats
.Where(cat => cat.Version == 1)

Эти запросы есть возможность передать в единую процедуру по страничной обработки:

void PageProcessing(IQueryable<CatDto> items)
{
var query = items.OrderBy(item => item.Id);
for (var i = 0;;i += 1000)
{
var currentItems = query.Skip(i).Take(1000);
if (!currentItems.Any
break;
..
}
}

Dasar

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

kokoc88

Эти запросы есть возможность передать в единую процедуру по страничной обработки:
Согласен. Но тут надо понять, к чему сводится твой пример или вопрос: к динамическим запросам или к повторному использованию кода с фиксированными условиями. В последнем случае часто встречается ситуация, когда лучше написать три db specific запроса для разных случаев.

6yrop

Декомпозицию можно немного поменять:
 
 
public static Func<int, IQueryable<Cat>> Page(this IQueryable<Cat> items)
{
return i => items.OrderBy(item => item.Id).Skip(i).Take(pageSize);
}

public static void PageProcessing(Func<int, IQueryable<Cat>> items)
{
for (var i = 0;; i += pageSize)
{
var currentItems = items(i);
if (!currentItems.Any
break;
....
}
}

const int pageSize = 1000;

Тогда запросы:
1. Cats.Where(cat => cat.Age < 1).Page;
2. Cats.Where(cat => cat.Alias == "pretty").Page;
3. Cats.Where(cat => cat.Version == 1).Page;
можно покрывать unit тестами на DAL, не касаясь функции PageProcessing.
А как ты покроешь unit тестами DAL? PageProcessing в DAL не входит. Фактически твоя декомпозиция смешивает DAL с другими слоями. Я не говорю что это плохо. Исчезают границы DAL-а, поэтому unit тесты на DAL уже не напишешь.
Есть олдскульный подход: выделять DAL и покрывать его тестами.
Oracle на своем сайте в учебнике до сих пор продвигает еще более олдскульный вариант: весь DAL делать в базе.

6yrop

IQueryable размывает границы классического DAL, смешивая его с другими слоями.
CQ напротив сужает границы классического DAL, получается некий Query Layer.
Для покрытия тестами слоя Query Layer требуется значительно меньше усилий, чем для классического DAL.
Если DAL полностью покрывать тестами, то смысла в IQueryable становится совсем немного. Именно на это и нацелен IQueryable, который позиционируют, как технологию, которая избавляет от написания рутинных тестов на DAL. Поэтому мой первый вопрос в этом треде был: покажи код юнит тестов.

6yrop

 
Data Access Layer для простых смертных

Забавно читать историю:
 

 

:
Я ответил на все твои вопросы. Теперь твоя очередь. Лучше всего, если будет код.

:
Моей очереди не будет. Если ты ещё не понял, то я не занимаюсь созданием какого-то фреймворка для работы с БД.

2012 год .... и .... в 2015 Майк выкладывает код на своем ORM. А я, да, не понимаю. :grin:

6yrop

История забавная штука. :)
Я давно забил на то, чтобы сразу делать cross db проекты, предпочитаю использовать db specific синтаксис и технологии.
В 2013 году Майк обвинял CQ в db specific:
 

Ты не понял. В Controllable Query "автоматическое тестирование" - это SQL Server Only либа, которая ищет все запросы, строит их планы, парсит эти планы и проверяет типы данных.
Не забывай, что общаешься с .NET SQL Server Only типчиком. :smirk:
 

Растет мальчик. Через лет 5 начнет осознавать проблему, которую решает CQ. :)

Dasar

А как ты покроешь unit тестами DAL? PageProcessing в DAL не входит.
Используя модифицированный твой подход.
Допустим у нас есть IQueryable-функция:

IQueryable<Cat> Kittens
{
return context.Cats
.Where(cat => cat.Age < 1);
}

Для этой функции необходимо протестировать следующие варианты на то, что они генерят правильный sql:
- непосредственно её
- Kittens.Skip(x)
- Kittens.Take(x)
- Kittens.OrderBy(cat => cat.<field>) (для всех полей Cat)
- комбинации ранее перечисленного

6yrop

Тестировать надо не саму функцию, а код, который ее вызывает:
 
 
Query<ICat>.New(new {}, "@Kittens");
Query<ICat>.New(new {}, "SELECT * FROM (@Kittens) _ ORDER BY Id");
Query<ICat>.New(new {x = 10}, "SELECT TOP (@x) * FROM (@Kittens) _");
Query<ICat>.New(new {x = 10, y = 20}, @"
SELECT *
FROM (@Kittens) _
ORDER BY Age OFFSET @x ROWS FETCH NEXT @y ROWS ONLY");

internal static QFragment Kittens
{
get { return new {}.QFragment("SELECT * FROM Cat WHERE Age < 1"); }
}

Кстати, гугл по запросу "data access layer testing" выдает первой строчкой: . О ужас, они сказали это! Тесты юнит — это мусор.

Dasar

Тестировать надо не саму функцию, а код, который ее вызывает:
Тестировать стоит и то, и другое.
Функция Kittens тестируется на то, что без ошибок получает данные из БД
Функция PageProcessing тестируется на то, что она без ошибок обрабатывает данные постранично.

6yrop

Тестировать стоит и то, и другое.
Функция Kittens тестируется на то, что без ошибок получает данные из БД
Если функция нигде не вызывается, ее тестировать не надо. А лучше ее вообще удалить. :) Если она вызывается, то см. мой предыдущий пост.

Dasar

> Если она вызывается, то см. мой предыдущий пост.
Это переход к теме Unit testing vs Integration testing.
До этого речь шла о тестирование DAL, что делается в рамках Unit Testing.
ps
В рамках Unit testing, логику вышестоящей функции имеет смысл тестировать без привязки к DAL на inmemory-данных.

stm5872449

В рамках Unit testing, логику вышестоящей функции имеет смысл тестировать без привязки к DAL на inmemory-данных.
И в чем смысл?

Dasar

И в чем смысл?
В том чтобы протестировать логику, которая находится непосредственно в этой функции.
Допустим вышестоящая функция следующая:

void ProcessByEvent(string event)
{
var sources = new Dictionary<string, Func<IQueryable<Cat>>>
{
{"Kitten", Kittens},
{"Pretty", PrettyCats},
{"New", NewCats},
};
var source = sources.Find(event);
if(source != null)
PageProcessing(source);
}

В рамках Unit Testing эту функцию не имеет смысла тестировать на то, что она корректно получает данные из БД.
Имеет смысл для этой функции зафиксировать пару тестов на то, что она вызывает PageProcessing с правильным источником для event-а.

stm5872449

Странный пример, если я его правильно понял, то непосредственно вызов базы происходит только внутри PageProcessing. Естественно роутер, который ничего про БД не знает, можно тестировать без БД.

Dasar

Естественно роутер
В чем тогда вопрос?
ps
Kittens - тестируем на то, что она корректно генерирует запросы к БД (по сценарию приведенному выше в треде)
PageProcessing - тестируем на одном варианте IQueryable<Cat> на то, что она корректно обрабатывает данные постранично
ProcessByEvent - тестируем на то, что она передает корректный source в PageProcessing
В этом кейсе, хочется или требуется протестировать что-то еще?

stm5872449

В рамках Unit testing, логику вышестоящей функции имеет смысл тестировать без привязки к DAL на inmemory-данных.
Интересно тут тестирование PageProcessing. Мое мнение состоит в том, что ее лучше тестировать с настоящей базой, а не inmemory-моками.
П.с.
зафиксировать пару тестов на то, что она вызывает PageProcessing с правильным источником для event-а.
Как я ненавижу такие тесты! Сколько не видел подобных, ни разу они не помогали найти регрессию, а вот боли при рефакторинге доставляют немеренно. :crazy:

6yrop

Имеет смысл для этой функции зафиксировать пару тестов на то, что она вызывает PageProcessing с правильным источником для event-а.
Как будешь проверять правильность источника? И чтобы не плодить посты напиши код тестов. А то о тестах все говорят, но никто их не приводят. Если приводят, то без слез на это не взглянешь: одна табличка из пяти полей и пять сотен строк тестов http://github.com/pkainulainen/jooq-with-spring-examples/bl...

6yrop

Как я ненавижу такие тесты! Сколько не видел подобных, ни разу они не помогали найти регрессию, а вот боли при рефакторинге доставляют немеренно.
А какие тесты отлавливают регрессию и не особо причиняют боль при внесении изменений?

luna89

А то о тестах все говорят, но никто их не приводят. Если приводят, то без слез на это не взглянешь: одна табличка из пяти полей и пять сотен строк тестов http://github.com/pkainulainen/jooq-with-spring-examples/bl...
Ну это же джава, там нормально считается скопипастить 20 строк кода 5 раз подряд.

6yrop

Ну это же джава, там нормально считается скопипастить 20 строк кода 5 раз подряд.
Java да, доставляет. Но тут не только в ней дело. Вот тут чувак пишет, что писали юнит тесты на DAL, а потом понял, что это ненужный мусор . Теперь начал писать интеграционные тесты. Судя по примерам кода, это тесты типа залить в пустую БД тестовый сет данных, покверить, проверить результат. На более менее сложной БД это порочный подход. Какой набор тестов делать? Как бог на душу положит? Тогда как вносить изменения в проект? Кто даст какие-либо гарантии, что не сломается то, что уже работало? Допустим даже уже известен сценарий, кто-то нам явно указал ошибку. Надо написать тест на это. Часто оказывается, чтобы довести пустую базу до того состояния, когда этот сценарий проявляется надо написать кучу кода или переиспользовать куски существующих тестов. Если каждый раз писать заполнение базы с нуля, то просто утонешь в таком коде. Если переиспользовать, то запутанность и сложность тестов становится близкой к коду самой системы. Поэтому нужна некая абстрактная теория, которая скажет какие тесты писать, а какие не писать. Пока я вижу единственный кандидат на место такой теории — это проверить валидность запросов по схеме БД и выдать типизированный result set. Как только начинаешь задумываться над чем-то большем, над каким-либо более глубоким "осмысленным" тестированием, универсальность теории пропадает, получается какие-то частные случаи, которые после завтрашних изменений устаревают и начинают причинять боль. Короче, Дейкстра всё выразил в одном предложении: "Тестирование программы может весьма эффективно продемонстрировать наличие ошибок, но безнадежно неадекватно для демонстрации их отсутствия". В проект надо внести изменения. Мы можем вносить изменения, основываясь на тестах? Нет, конечно, поскольку тесты безнадежно неадекватны для демонстрации того, что мы не поломали того, что уже работало.

luna89

Если переиспользовать, то запутанность и сложность тестов становится близкой к коду самой системы.
А с какого размера базами данных ты работаешь? Интересует количество таблиц и общий размер базы.
Вообще, как я себе представляю, в энтерпрайзе сделать интеграционное тестирование методом заполнения базы тестовыми данными невозможно. Думаю, обычно даже невозможно развернуть среду разработки целиком локально на машине разработчика, тем более сделать это запуском одного скрипта. Ты сам как-то писал про некий проект с ораклом, где даже невозможно было задеплоить код в базу данных. Еще все усугубляется тем, что в энтерпрайзе, в отличие от веба, средой разработки является Windows, в котором нельзя написать скрипт, запаковывающий zip архив, или запустить программу на удаленной машине.
Плюс, еще обычно сложно запускать код, потому что все тормозит (какие-нибудь энтерпрайзные веб-фреймворки, например).
Вот это реальные проблемы, а не отсутствие абстрактной теории написания тестов. Из-за этих проблем и возникают все эти идеи, типа как бы написать код, ни разу не запуская его, или запуская в ограниченном окружении выдуманных тестов.
В вебе, например, этих проблем нет. Все работает быстро (так как иначе веб-проектом никто не будет пользоваться и среда разработки простая (какой-нибудь PHP, mysql, nginx). Поэтому веб-разработчики тебя не поймут, у них таких проблем нет.

yroslavasako

средой разработки является Windows, в котором нельзя написать скрипт, запаковывающий zip архив
WTF?

stm5872449

Поэтому нужна некая абстрактная теория, которая скажет какие тесты писать, а какие не писать. Пока я вижу единственный кандидат на место такой теории — это проверить валидность запросов по схеме БД и выдать типизированный result set.
Херовая "теория", что тут говорить.

6yrop

А с какого размера базами данных ты работаешь? Интересует количество таблиц и общий размер базы.
Несколько сотен таблиц, около тысячи. Сотни гигабайт.

6yrop

Херовая "теория", что тут говорить.
Херовая, если есть лучшая. Еще вариант, ты не знаешь как этой теорией пользоваться, поэтому для тебя она херовая.
Эта теория позволяет сделать program slicing, когда точкой интереса является поле таблицы или таблица. Большинство изменений, которые требуются заказчику, именно и крутятся вокруг небольшой горстки полей.

6yrop

Все описанные проблемы у нас решены, кроме заполнения базы тестовыми данными для интеграционного тестирования. Это не сделано, потому что нет стратегии/теории как это делать. При некотором количестве связанных таблиц код для такого заполнения превращается в ненужный запутанный хлам, как по ссылке выше. И дело не в том, что мы не умеем писать код. Код мы писать умеем. Дело в том, что нет требований на основе, которых надо писать этот код. Требования стихийные, кому как бог на душу положит. А по пришествию времени, результат хорошо виден в коде. Получается тестовая база живет своей жизнью отдельно от прода. Только за жизнь базы на проде нам платят, а за жизнь тестовой базы нет.

6yrop

Поэтому веб-разработчики тебя не поймут, у них таких проблем нет.
Скажи, я ошибаюсь, если думаю, что у веб-разработчиков проблемы с деньгами? Если набрать на hh.ru js или php, то предлагают мало денег.

stm5872449

Херовая, если есть лучшая.
Ложный вывод. Может быть просто полезной теории построить нельзя.
Вот есть теория как надо писать тесты. Нужно "всего лишь" написать тест на каждый путь выполнения в программе.
Это правда приводит к комбинаторному взрыву, но что уж тут поделать. :smirk:
Еще вариант, ты не знаешь как этой теорией пользоваться, поэтому для тебя она херовая.
Ололо, ты правда думаешь, что о каком-то rocket science говоришь? :grin:
Твоя блестящая "теория" спасет в лучшем случае от ран-тайм эксепшенов, которые будет тривиально и быстро пофиксить, и не спасет от, например, off-by-one error в параметре запроса к базе, который будет молча приводить к неверным выборкам и может жить очень долго.

6yrop

Из-за этих проблем и возникают все эти идеи, типа как бы написать код, ни разу не запуская его, или запуская в ограниченном окружении выдуманных тестов.
Требуется внести изменение в проект. Что ты будешь запускать? Функционал всего проекта? Нет. Тогда какое подмножество функционала ты будешь запускать? Откуда ты возьмешь эту информацию? Где информация о проекте хранится? В головах старожилов, в неактуальной документации ... Наиболее достоверный и полный истопник информации это код. Поскольку именно он работает на проде. Каждый раз будешь читать код всего проекта? Нет. Поэтому нужен механизм slicing-а, который вырезает из проекта ломтик, который соответствует определенной точки интереса. Slicing по полю или таблицы базы очень востребованная штука. Получив необходимое подмножество функционала, дальше уже с ним работаешь, вносишь изменения, запускаешь, тестируешь. Проблема именно в нахождении этого подмножества.

6yrop

Ложный вывод.
Докажи, что вывод ложный. Какая у тебя мера херовости?

Ололо, ты правда думаешь, что о каком-то rocket science говоришь? :grin:
Твоя блестящая "теория" спасет в лучшем случае от ран-тайм эксепшенов, которые будет тривиально и быстро пофиксить, и не спасет от, например, off-by-one error в параметре запроса к базе, который будет молча приводить к неверным выборкам и может жить очень долго.

Rocket не rocket, но пока видно, что ты не видишь как использовать эту "теорию". Главная цель "теории" не в отлове ошибок, а в том, что на ее основе можно вносить изменения, не внося ошибок в то, что уже работает. Информация, которую получаем из этой "теории", позволяет доказывать, что изменение не вносит ошибок в то, что уже работает. Какой "теорией" пользуешься ты, когда вносишь изменения в проект?

stm5872449

Главная цель "теории" не в отлове ошибок, а в том, что на ее основе можно вносить изменения, не внося ошибок в то, что уже работает.
Если "теория" не позволяет отлавливать ошибки, она же не позволит отлавливать регрессии, так что нет, нельзя.
Информация, которую получаем из этой "теории", позволяет доказывать, что изменение не вносит ошибок в то, что уже работает.
Ой не надо только про "доказывать", как ты "доказываешь" корректность кода после изменений я помню по эпическому треду про "рефакторинг маленькими шажками".
Какой "теорией" пользуешься ты, когда вносишь изменения в проект?
Никакой. Ее необходимость постулировал ты, я с этим не согласен.
Также я не пользуюь никакой абстрактной теорией о том, как надо программировать, surprise, surprise.

kokoc88

Вообще, как я себе представляю, в энтерпрайзе сделать интеграционное тестирование методом заполнения базы тестовыми данными невозможно
Энтерпрайз - это не библиотеки Java. Почитай определение, посмотри на примеры. Я не представляю, как нужно написать проект, чтобы в нём было невозможно заполнить базу данными для тестирования. Если всё так плохо, то и код этого проекта будет таким, что о каких-то тестах в нём можно будет только мечтать.
Думаю, обычно даже невозможно развернуть среду разработки целиком локально на машине разработчика, тем более сделать это запуском одного скрипта.
Ты хотел сказать, что нельзя развернуть полностью рабочий проект? Facebook - не энтерпрайз, и я гарантрирую, что его нельзя развернуть на машине разработчика запуском одного скрипта.
Из-за этих проблем и возникают все эти идеи, типа как бы написать код, ни разу не запуская его, или запуская в ограниченном окружении выдуманных тестов. В вебе, например, этих проблем нет. Все работает быстро (так как иначе веб-проектом никто не будет пользоваться и среда разработки простая (какой-нибудь PHP, mysql, nginx).

В Facebook используются все эти технологии, и мне почему-то кажется, что код тестируется в "ограниченном окружении". Хотя глупые теории и правда не нужны, тут я с тобой полностью согласен.
Если ты не приверженец TDD, то для написания хорошего набора тестов тебе нужно представлять, как работает конкретный участок кода. Поэтому хуже всего с тестами обстоит в проектах, где даже автор не может разобраться в коде, который он только что написал. Во write only коде даже 100% покрытие тестами не даёт уверенности, что всё идёт правильно, ведь ты спокойно можешь допустить логическую ошибку и в коде, и в тесте для этого кода. Некоторые виды ошибок намного чаще устраняются во время хорошо поставленного процесса code review и/или внешнего тестирования, а не во время разработки тестов.

6yrop

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

luna89

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

6yrop

У вас каждый разработчик использует отдельный инстанс базы, или все используют один общий?
Один общий.
На прошлом месте работы были многократные попытки делать свои базы у каждого разработчика. Так и не прижилось.
Тесты мне напоминают такие деревянные наскоро, сбитые хрени (леса? на которые рабочие встают, когда потолок ремонтируют. Побелили в одной комнате переходят в другую, леса убирают, а программисты хотят оставаться со вспомогательными средствами всю жизнь, и жить со строительными лесами в квартире. Вдруг потребуется потолок замазать, и лампочку удобно вкручивать, если потолки высокие. :)

6yrop

ведь ты спокойно можешь допустить логическую ошибку и в коде, и в тесте для этого кода. Некоторые виды ошибок намного чаще устраняются во время хорошо поставленного процесса code review
Хорошо поставленное code review я описал в соседнем треде. Логические ошибки хорошо ловятся даже, если смотреть на код другого проекта.
Собственно, это отличный пример, что под code review обычно впаривают одно, а на практике это выглядит совсем иначе. Чтобы провести хорошее code review с выявлением логических ошибок надо вникать в детали задачи и фактически решать ее, а потом сравнивать свое решение с кодом. Если хотите делить деньги на двоих, вперед.
На практике, как мы видим в этом разделе, code review сводится к тому как делать indent в многострочных литералах, вопли по поводу длинных методов и т.д. Т.е. вопли, не вникая в суть задачи. Как таким ревью можно найти логические ошибки? Столько раз в этом разделе было code review моего кода, хотя бы одну логическую ошибку кто-нибудь упомянул?

luna89

Энтерпрайз - это не библиотеки Java. Почитай определение, посмотри на примеры. Я не представляю, как нужно написать проект, чтобы в нём было невозможно заполнить базу данными для тестирования.
Возникла путаница в терминах. Под "энтерпрайзом" в данном треде я имею в виду софт, который имеет смысл только в рамках какой-то конкретной компании, и сильно завязан на ее бизнес-процессы и IT-инфраструктуру.
В таких условиях зачастую требуется интегрироваться с проектами, созданными другими командами разработчиков, возможно, из другой компании. Тестовый инстанс базы данных может существовать в единственном экземпляре, и его невозможно заполнить тестовыми данными для прогона тестов (Шурик подтверждает).
Кроме того, у инженеров нет политической силы, и иногда просто нет возможности принимать какие-то технологические решения. Например, надо интегрироваться с каким-то говном, хотя переписать это говно с нуля было бы легче, но переписать нельзя, потому что это под контролем другого департамента.

luna89

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

luna89

Скажи, я ошибаюсь, если думаю, что у веб-разработчиков проблемы с деньгами? Если набрать на hh.ru js или php, то предлагают мало денег.
Как ты оцениваешь среднюю з/п разработчика на C#, на php и на js? Вместо средней можешь оценить выбранную тобой процентиль, нпаример.

6yrop

Как ты оцениваешь среднюю з/п разработчика на C#, на php и на js? Вместо средней можешь оценить выбранную тобой процентиль, нпаример.
Сейчас снова зашел на hh, похоже я ошибался.

stm5872449

Должны быть какие-то абстрактные принципы, которые должны быть известны всем членам команды. Как вы передаете эти принципы вновь прибывшим? Вы их рассказываете?
Прямо абстрактные принципы должны быть по большей части и так известны вновь прибывшим, или как их на работу взяли? Ошибки - да, рассказываем на code-review.
На практике, как мы видим в этом разделе, code review сводится к тому как делать indent в многострочных литералах, вопли по поводу длинных методов и т.д. Т.е. вопли, не вникая в суть задачи. Как таким ревью можно найти логические ошибки? Столько раз в этом разделе было code review моего кода, хотя бы одну логическую ошибку кто-нибудь упомянул?
Видишь ли, это как пирамида Маслоу. Чтобы искать логические ошибки, надо сначала чтобы код было легко читать. А если там месиво, от которого глаза разбегаются, то извини. Тем более когда за это не платят деньги и проект никому не интересен.
Отправить на работе код на доработку со словами "тут ничего не понятно, перепиши проще" - абсолютно нормально имо.

6yrop

Ошибки - да, рассказываем на code-review.
Мне в этом разделе никто никогда не привел код проще для решения той же задачи. Более того, подозреваю, что в задачу никто не вникал.

kokoc88

Тестовый инстанс базы данных может существовать в единственном экземпляре, и его невозможно заполнить тестовыми данными для прогона тестов
А поднять рядом свой инстанс религия не позволит?
Кроме того, у инженеров нет политической силы, и иногда просто нет возможности принимать какие-то технологические решения. Например, надо интегрироваться с каким-то говном, хотя переписать это говно с нуля было бы легче, но переписать нельзя, потому что это под контролем другого департамента.
Это не мешает написать тесты. Если код хорошо спроектирован, то с ним можно делать почти всё, что пожелаешь. Например, натаскать запросов и ответов из самого зачуханного SOAP сервиса и написать тесты, которые проверяют интеграционную часть без обращения на единственный боевой экземпляр этого сервиса. На слух это кажется сложным, но у страха глаза велики. На самом деле всё решает лень.
Оставить комментарий
Имя или ник:
Комментарий: