Предпочтительный способ написания приложений баз данных Delphi с компонентами транзакций и данных
Каков предпочтительный способ написания приложений баз данных Delphi с использованием транзакций, а также компонентов, учитывающих данные?
Я должен написать клиентское приложение, которое обращается к таблицам InnoDB, и делать какие-то основные детали внутри транзакций. Проведя некоторое исследование транзакций (с общей точки зрения), я скромно сделаю вывод, что компоненты, не учитывающие данные, и код SQL, написанный вручную, будут "идеальным соответствием" транзакций; Но компоненты, учитывающие данные, не будут. Кажется, они не созданы друг для друга.
У меня есть реальная необходимость использовать транзакции, но, с другой стороны, я не мог просто выбросить компоненты, учитывающие данные, потому что они значительно упрощают работу.
Может ли кто-нибудь, пожалуйста, просветить меня? Я гуглил это, но я не нашел никакого полезного ответа. Возможно, потому что мой английский недостаточно хорош, чтобы мои ключевые слова были ограничены.
Кстати, я использую Delphi 7 и в настоящее время оцениваю UniDAC в качестве библиотеки доступа к данным.
Спасибо.
РЕДАКТИРОВАТЬ
Пример, чтобы описать аспект моего вопроса:
Представьте себе форму с 2 DBGrids на нем. Первая сетка - это MasterGrid, а над ней находятся следующие кнопки: Add, Edit & Delete. Вторая сетка - это DetailGrid. Если пользователь нажимает кнопку "Добавить", он выглядит так:
- Connection.StartTransaction
- Master.Append, затем Master.Post, затем Master.Edit (поэтому набор основных данных имеет первичный ключ автоинкремента, и теперь он доступен для редактирования).
- Покажите модальную форму редактирования, в которой пользователь заполняет основные записи, а также добавьте некоторые подробные записи, используя другую форму.
- Если пользователь нажмет OK, приложение будет выполнять Master.Post и Connection.Commit. Если пользователь нажмет "Отмена", приложение выполнит Connection.Rollback.
Я знаю, что транзакции должны быть максимально короткими, но вы можете видеть выше, что транзакция настолько коротка, насколько скорость пользователя заполняет форму.
Если бы я использовал компоненты, не поддерживающие данные, я бы сделал пользовательские вставки SQL на основе пользовательского ввода, а затем выполнил бы SQL между StartTransaction и Commit. Так что я могу добиться очень короткой сделки.
РЕДАКТИРОВАТЬ 2
Я благодарю всех вас за ваше любезное участие. Я выбираю ответ от vcldeveloper, потому что это наиболее близкое решение для моей текущей потребности.
5 ответов
Другие упоминали об использовании комбинации DatasetProvider и ClientDataset для пакетного обновления, но в случае использования компонентов ADO или UniDAC вам не требуется дополнительный слой DatasetProvider + ClientDataset, потому что и ADO, и UniDAC поддерживают пакетные обновления.
Для ADO вам нужно установить LockType вашего набора данных в ltBatchOptimistic. Для UniDAC вы должны установить для свойства CacheUpdate значениеTrue.
Это изменение заставляет ваш набор данных кэшировать все изменения, которые вы вносите в его набор записей в памяти, и отправлять их все вместе в базу данных только при вызове метода UpdateBatch (ADO) или метода ApplyUpdates (UniDAC).
Теперь вы должны позволить своему пользователю вставить / отредактировать запись в основном наборе данных и любые записи, которые он / она хочет в наборе данных, используя любые компоненты с поддержкой данных, которые вам нравятся. Все изменения будут кэшированы. Когда ваш пользователь закончил, вы можете начать новую транзакцию и сначала вызвать UpdateBatch (или ApplyUpdate в случае UniDAC) для основного набора данных, а затем для набора данных деталей, и, если все пойдет хорошо, зафиксировать транзакцию.
Это сделает ваши транзакции короткими без дополнительного уровня ClientDataset.
С уважением
Я понимаю ваш вопрос, я думаю. При открытии набора TADODataSet, например, с 10 строками данных, которые нужно редактировать в форме, с компонентами, учитывающими данные, существуют ситуации, в которых вы хотите кэшировать все изменения, внесенные во все 10 строк (и, возможно, удаления и вставки). и передайте это как одну партию. Вы не можете открыть транзакцию при первом изменении, так как это заблокирует других пользователей, изменяющих те же данные. Транзакции должны быть максимально короткими.
В набросанном сценарии я использую следующие компоненты в цепочке:
TADOConnection >> TADODataSet >> TDataSetProvider >> TClientDataSet >> TDataSource >> TDBEdits и т. Д.
Теперь все изменения кэшируются в TClientDataSet, и вы можете вызвать его метод ApplyUpdates для публикации всех изменений в одной быстрой транзакции. Помните, что также возможно использовать несколько TADODataSets и несколько TClientDataSets для структуры master-detail(-detail-etc) с вложенными наборами данных. Все основные изменения также могут быть кэшированы и применены в одном пакете за одну транзакцию. Смотрите справку и ресурсы в другом месте для всех деталей о реализации этого. Сначала это не легко. Но если вы поняли это, это легко и предлагает массу возможностей. (Автономное редактирование, проверка изменений перед их применением и т. Д.)
Чтобы избежать необходимости выполнять большие транзакции, я использую DataSetProviders и ClientDatasets (даже локально).
Подумайте об использовании этого как своего рода кеша, и он даст вам лучшее из обоих миров. Вы можете использовать элементы управления с учетом данных, чтобы упростить работу во время работы с пользовательским интерфейсом. Действия пользователя над наборами данных "записываются" ClientDataSets (тип кэша базы данных).
Когда ваш пользователь готов сохранить изменения в базе данных (например, все данные счета-фактуры на месте), вы вызываете метод ApplyUpdates для набора (-ов) данных.
В простейшем сценарии, когда все наборы данных находятся в отношении master-detail (вложено провайдером), провайдер запускает и фиксирует / откатывает транзакцию самостоятельно, поэтому вы автоматически попадаете в ситуацию "все или ничего".
Если у вас более сложные отношения, вы можете вызвать StartTransaction перед началом применения обновлений для каждого задействованного набора ClientDataSet, а также по окончании вызова Commit или Rollback по мере необходимости). Логика для провайдера заключается в том, что если у соединения есть активная транзакция при вызове ApplyUpdates, то он ничего не делает с транзакцией, а просто публикует изменения в базе данных, предполагая, что вы контролируете транзакцию.
Вы должны прочитать о TClientDataSet и о том, как обрабатывать OnReconcileError и поэкспериментировать с технологией, прежде чем применять ее в производственных средах, но она работает очень и очень хорошо для меня.
Мои 2 цента.
Вы абсолютно правы в том, что транзакция записи должна быть настолько короткой, насколько это возможно, и она не должна быть активной все время, пока пользователь заполняет форму.
Общее решение, как уже было сказано, заключается в использовании промежуточного уровня (ClientDataSet). Но реальная проблема в вашем сценарии заключается в том, что вы не можете получить значение автоинкремента для мастер-таблицы без Master.Append и Master.Post, и, следовательно, вы начинаете транзакцию записи задолго до того, как она действительно потребуется.
Поэтому, если вы не хотите использовать промежуточный уровень и все еще использовать компоненты, учитывающие данные, с короткими транзакциями записи, вам следует подумать о базе данных, которая поддерживает получение автоинкрементного значения без выполнения INSERT (для основной таблицы). Пример - база данных Firebird, и компоненты доступа к данным FibPlus для Firebird полностью поддерживают эту функцию.
Транзакции должны быть настолько короткими, насколько это необходимо. Проблема в том, как разные базы данных обрабатывают блокировку. Базы данных, которые выполняют только блокировку на уровне строк и могут немедленно вернуться из блокировки без ожидания, имеют гораздо меньшую вероятность возникновения тупиковой ситуации. Обычно вставки менее проблематичны (хотя другой пользователь не увидит новые строки до фиксации, в зависимости от уровня изоляции), обновления и удаления более проблематичны. Слишком частое совершение может быть и "плохим". Кэширование изменений и их применение в одной операции - это еще одна возможность, но вам придется решать проблемы, связанные с тем, что другие пользователи изменяют записи. Нет лучшего решения - все зависит от реальных потребностей. Для некоторых приложений (и некоторых баз данных) сохранение записи заблокированной, пока они меняются, в порядке, для других - нет. Пакетные обновления могут быть в одних случаях нормальными, а в других - нет. Вы должны выбрать модель, которая лучше всего подходит для вашего приложения и базы данных.