Transactions |
[This is preliminary documentation and is subject to change.]
This topic contains the following sections:
A BcfTransaction is datasets update-context. It hosts the container for rollback-items, controls the cell and rule calculation, ensures the data consistence and works as undo/redo step.
A BcfDataSet can have one CurrentTransaction at a time.
Each data-update on a BcfDataSet is tracked and rollback info is stored in a transaction. If BcfDataSetCurrentTransaction is null when update will be performed, a transaction will be created (implicit transaction). Implicit transaction will auto commit after the update is performed.
To put many data-updates into one transaction simply create a transaction, perform the updates and than commit the transaction.
BCF supports subtransactions. They are usually explicit created and can be identified using BcfTransactionIsSubTransaction property. The recommended way to create a subtransaction is calling BcfTransactionBeginSubTransaction. However with BcfDataSetCurrentTransaction is not null calling BcfDataSetBeginTransaction will also create a subtransaction.
Since BCF 4.0 creating and committing a subtransaction will not longer enforce relation and key constraints and calculation. If you want to perform calculation on every change see example on BcfMicroTransactionModeSubtransaction.
Commit works different on subtransactions and transactions. When a subtransaction commits, its rollback-info is added to its parent transaction. The subtransaction is not longer referenced. When a transaction commits, it enforces relation constraints and calculates. After commit the transaction will be moved to datasets UndoRepository.
Rollback restores the BcfDataSet-state of the moment the transaction was created.
By default calculation is deferred until the transaction commits. If you need to update computed values within a transaction call BcfTransactionCompute.
The BcfTransactionBuilderName-property should indicate the intention the transaction was created. When creating a transaction using BeginTransaction a name can be specified. For custom naming of implicit transaction see Customize Transactions.
Many data-update operations are compounded but have to be atomic. For example: Setting a cell-value of a column with a trigger and the trigger sets other cells values. The trigger action may fail when a value-constraint is violated. The BcfTransactionMicroTransactionMode specifies how to deal with atomic compounded updates.
BcfMicroTransactionModeDisabled
The compounded updates are not isolated from current transaction. If an update fails the entire transaction has to be rolled back.
This option has best performance. Consider using if you exactly know the update can not fail - or - in case of error you will rollback the transaction anyway.
BcfMicroTransactionModeSubtransaction
Creates a subtransaction for update. After update the subtransaction will be auto committed.
This option has worst performance. It is useful for debugging when using custom transactions.
Bookmarks the internal transaction state and rewinds it in case of error. Only triggers will be executed in subtransactions.
This option performs well and ensures atomicity. Is used as default (see BcfTransactionFactoryBaseTTransaction, TTransactionInfo, TTransactionBuilderMicroTransactionMode).
Default MicroTransactionMode can be specified at BcfDataSetTransactionFactory.
There are four relation constraints:
Specifying key-columns on a relation causes a foreign key constraint. The constraint is violated when child key-values are not null and no parent row with matching key is found.
The BcfRelationHasParentEndNotNullConstraint indicates whether transactions should enforce each child-row has a parent-row for the relation.
The BcfRelationChildEndMinConstraint indicates the minimum number of child-rows a parent-row may have for the relation.
The BcfRelationChildEndMaxConstraint indicates the maximum number of child-rows a parent-row may have for the relation.
Enforcing relation and key constraints can deferred until transaction Commit. This is required e.g. when creating a parent-row with a relation having ChildEndMinConstraint > 0. Use EnforceConstraints-property to get and BcfTransactionSetEnforceConstraints change the behavior.
Default value can be specified at BcfDataSetTransactionFactory.
BcfDataSetTransactionFactory controls creating transactions for dataset. It is recommended to create a custom class inheriting BcfTransactionFactoryBaseTTransaction, TTransactionInfo, TTransactionBuilder and specify its constructor at CreateTransactionFactory (e.g. mySetup.CreateTransactionFactory = dataSet => new MyTransactionFactory(dataSet)).
Specify transactions default MicroTransactionMode, EnforceConstraints on BcfDataSetTransactionFactory. To customize naming of implicit transactions override default implementation of BcfTransactionFactoryBaseTTransaction, TTransactionInfo, TTransactionBuilderCreateTransactionName.
The BcfTransactionBuilder class is used as parameter to create transactions.
You can create a custom TransactionBuilder class inheriting BcfTransactionBuilder. If you do so, ensure the custom class is used as typeparameter in your BcfTransactionFactoryBaseTTransaction, TTransactionInfo, TTransactionBuilder implementation.
The value of BcfTransactionInfo-property is of type: IBcfTransactionInfo. It stores additional data not vital for undo/redo. The BcfTransactionName reflects IBcfTransactionInfoName.
BcfUndoRepositoryUndoItems and BcfUndoRepositoryRedoItems returns the TransactionInfo.
Creating a custom implementation of IBcfTransactionInfo is used to add transaction related info like a time-stamp or UI-state before and after transaction.
You can create a custom TransactionInfo class implementing IBcfTransactionInfo. If you do so, ensure the custom class is used as typeparameter in your BcfTransactionFactoryBaseTTransaction, TTransactionInfo, TTransactionBuilder implementation.
Create a custom transaction class inheriting BcfTransaction to control its behavior and access protected members.
When created a custom transaction class, ensure it is used as typeparameter in your BcfTransactionFactoryBaseTTransaction, TTransactionInfo, TTransactionBuilder implementation.
This example illustrates how to:
using System; using CalculationWorks.BusinessModel; using CalculationWorks.BusinessModel.Design; using CalculationWorks.BusinessModel.UndoRedo; namespace MyNamespace { public class MyTransactionBuilder : BcfTransactionBuilder { /// <summary> /// An additional comment /// </summary> public string Comment { get; set; } } public class MyTransactionInfo : IBcfTransactionInfo { /// <summary> /// The <see cref="IBcfTransactionInfo.Name"/> implementation. /// </summary> public string Name { get; set; } /// <summary> /// The <see cref="MyTransactionBuilder.Comment">additional comment from builder</see>. /// </summary> public string Comment { get; set; } /// <summary> /// Date and time when transaction was committed. /// </summary> public DateTime? CommittedAt { get; set; } } public class MyTransaction : BcfTransaction { public MyTransaction([NotNull] BcfDataSet dataSet, [NotNull] BcfTransactionBuilder builder) : base(dataSet, builder) { // Info is not initialized for subtransactions if(IsSubTransaction) Info = DataSet.TransactionFactory.CreateTransactionInfoObject(); Comment = ((MyTransactionBuilder)builder).Comment; } public MyTransactionInfo MyInfo { get { return (MyTransactionInfo)Info; } } /// <summary> /// The <see cref="MyTransactionBuilder.Comment">additional comment from builder</see>. /// </summary> public string Comment { get { return MyInfo.Comment; } set { MyInfo.Comment = value; } } /// <summary> /// Date and time when transaction was committed. /// </summary> public DateTime? CommittedAt { get { return MyInfo.CommittedAt; } } /// <summary> /// If using file-based undo here is the last chance for updates on <see cref="MyInfo"/> before it is serialized. /// </summary> protected override void OnBeforeCommitComplete() { MyInfo.CommittedAt = DateTime.Now; } } public class MyTransactionFactory : BcfTransactionFactoryBase<MyTransaction, MyTransactionInfo, MyTransactionBuilder> { public MyTransactionFactory([NotNull] BcfDataSet dataSet) : base(dataSet) { // changing default because it sucks EnforceConstraints = false; } public override MyTransaction CreateTransaction(BcfTransactionBuilder transactionBuilder) { return new MyTransaction(DataSet, transactionBuilder); } } // Handwritten part for BCF-Editor generated partial DataSetSetup-class. partial class MyDataSetSetup { // BCF-Editor generates "partial void Initialized();" we can implement in same cs-project the generated DataSetSetup-class resides. partial void Initialized() { CreateTransactionFactory = dataSet => new MyTransactionFactory(dataSet); } } }