Starcounter
HomeDownloadDocsCommunity
2.3.2
2.3.2
  • Starcounter Documentation
  • Getting Started
  • Starcounter
    • Collapsing the Stack
      • Complexity and Scalability Tradeoff
      • The Future of Micro-Services
      • 10 Benefits of Collapsing the Stack
    • Integrated Database and Web Server
  • Hello World - Tutorial
    • Create a Database Class
    • Create a Real Time UI
    • First Interactive UI
    • Computed Properties
    • Expense Tracker
    • Cancel and Delete
    • The Next Step
  • Guides
    • Database
      • Database Classes
      • Data manipulation
      • Object Identity and Object References
      • Querying with SQL
      • Data Types
      • Relations
      • Inheritance
      • Sharing data
      • Database Configuration
      • Comparing Database Objects
      • Referential Integrity and Constraints
    • SQL
      • Identifiers
      • Path Expressions
      • Data operators
      • Joins
      • Aggregates
      • Comparisons and Logical Operators
      • Sorting
      • Fetch
      • Offset Key
      • Indexes
      • Literals
      • Query Plan Hints
      • Reserved words
      • Query for Database Classes
      • SQL Isolation Between Applications
    • Transactions
      • Short-Running Transactions
      • Long running transactions
      • Using Transactions
      • Running Background Jobs
      • Commit Hooks
      • Post-commit hooks
    • Typed JSON
      • JSON-by-example
      • Code-Behind
      • Data Bindings
      • Callback Methods
      • Responding with JSON
      • Accepting JSON in Requests
      • Primitive Arrays and Single Value Types
      • Typed JSON Internals
    • Blendable Web Apps
      • Starcounter MVVM
      • Palindrom
      • Client-Side Stack
      • Sessions
      • HTML Views
      • App Shell
      • Web Components
      • View Attaching
      • View Composing
      • HTML Compositions
      • HTML Views Blending Guidelines
      • Avoiding CSS conflicts
      • Debugging
    • Network
      • HTTP
      • Internal Self Calls
      • Middleware
      • Anonymous or Substitute Handlers
      • URL Aliases and Redirects
      • Network Gateway
      • Static File Server
      • WebSocket
      • Avoiding URI conflicts
      • TCP Sockets
      • UDP Sockets
    • Publishing Apps
    • Working with Starcounter
      • Release Channels
      • Installation
      • Starting and Stopping Apps
      • Administrator Web UI
      • Star CLI
      • StarAdmin CLI
      • StarDump CLI
      • Working in Visual Studio
      • Error Log
      • Using HTTPS on NGINX
      • Using HTTPS on IIS
      • Run Starcounter in Production
      • Weaver
      • Investigating App Crashes
      • Configuration Structure
      • Database Refactoring
      • Using Unload/Reload to Modify Database Schema
      • Kernel Questions and Answers
      • Log Files
  • Cookbook
    • Attach an HTTP Request to an Existing Long-Running Transaction
    • Cookie-Based Authentication
    • Timestamp on Object Creation
    • Creating Strongly Typed JSON Collections
    • Migrating From 2.2 to 2.3+
    • Multiple Pages
    • Icons
    • Proposed Project Structure
    • Acceptance Testing with Selenium
    • Requesting a User to Authenticate
    • How to delete unused tables and columns
Powered by GitBook
On this page
  • Introduction
  • Example
  • Q&A
  1. Guides
  2. Transactions

Commit Hooks

PreviousRunning Background JobsNextPost-commit hooks

Last updated 7 years ago

Introduction

Commit hook is a logic flow control pattern similar to in relational databases. It enables to hook the events per objects of particular class. For cases when an object is being created (with a new operator), updated (by writing to a field) and deleted (when Deleteis called, and after the committed delete), additional event handlers of code might be added for execution.

Example

using System;
using Starcounter;

namespace TestHooks
{
   [Database]
   public class Hooked
   {
      public string state { get; set; }
   }

   [Database]
   public class YetAnotherClass
   {
      public int Stock { get; set; }
   }

   class Program 
   {
      static void Main()
      {
         Hook<Hooked>.BeforeDelete += (s, obj) =>
         {
            obj.state = "is about to be deleted";
            Console.WriteLine("Hooked: Object {0} is to be deleted", obj.GetObjectNo());
         };

         Hook<Hooked>.CommitInsert += (s, obj) =>
         {
            obj.state = "is created";
            Console.WriteLine("Hooked: Object {0} is created", obj.GetObjectNo());
            var nobj = new YetAnotherClass() { Stock = 42 };
         };

         Hook<Hooked>.CommitUpdate += (s, obj) =>
         {
            obj.state = "is updated";
            Console.WriteLine("Hooked: Object {0} is updated", obj.GetObjectNo());
         };

         Hook<Hooked>.CommitUpdate += (s, obj) => // a second callback
         {
            Console.WriteLine("Hooked: We promise you, object {0} is updated", obj.GetObjectNo());
         };

         Hook<Hooked>.CommitDelete += (s, onum) =>
         {
            Console.WriteLine("Hooked: Object {0} is deleted", onum);
            Hooked rp = (Hooked)DbHelper.FromID(onum); // returns null here
            // the following will cause an exception
            // Console.WriteLine("We cannot do like this: {0}", rp.state);
         };

         Hook<YetAnotherClass>.CommitInsert += (s, obj) =>
         {
            Console.WriteLine("Never triggered in this app, since it happens to get invoked inside another hook");
         };

         Hooked p = null;
         Db.Transact(() =>
         {
           p = new Hooked() { state = "created" };
         });

         Db.Transact(() =>
         {
            p.state = "property changed";
            Console.WriteLine("01: The changed object isn't yet commited", p.GetObjectNo());
         });

         Console.WriteLine("02: Change for property of {0} is committed", p.GetObjectNo());

         Db.Transact(() =>
         {
            Console.WriteLine("03: We have entered the transaction scope");
            Console.WriteLine("04: We are about to delete an object {0}, yet it still exists", p.GetObjectNo());
            p.state = "deleted";
            p.Delete();
            Console.WriteLine("05: The deleted object {0} is no longer be available", p.GetObjectNo());
            Console.WriteLine("06: Were are about to commit the deletion");
         });
         Console.WriteLine("07: Deletion is committed");
      }
   }
}
Hooked: Object 29 is created
01: The changed object isn't yet commited
Hooked: Object 29 is updated
Hooked: We promise you, object 29 is updated
02: Change for property of 29 is committed
03: We have entered the transaction scope
04: We are about to delete an object 29, yet it still exists
Hooked: Object 29 is to be deleted
05: The deleted object 29 is no longer be available
06: Were are about to commit the deletion
Hooked: Object 29 is deleted
07: Deletion is committed

Q&A

Why there are separate pre-delete (BeforeDelete) and post-delete (CommitDelete) hooks?

How much should commit hooks be used?

In general, in situations where you can choose, we recommend to avoid using commit hooks. They introduce non-linear flows in the logic, hence producing more complicated and less maintainable code. Commit hooks is a powerful tool that should only be used in situations where benefits of using them overweight the drawbacks. One popular example is separate logging of changes in objects of selected classes.

Can I do DB operations inside commit hooks?

The answer is "Yes", since all commit hooks relate to write operations (create/update/delete), thus there must always be a transaction spanning these operations, and all event handlers are run inside this transaction. For example, in TestHooks we create an instance of a class YetAnotherClass inside CommitInsert, but do not introduce a transaction scope around this line. The reason being for it is that there is already a transaction from Main which spans this call.

Notes.

  1. It is currently not possible to detach commit hook event handlers.

  2. CRUD operations introduced inside a hook are not triggering additional hooks. For instance, in TestHooks the insert hook for YetAnotherClass is never invoked, because the only place for it triggered is in CommitInsert, which is itself a commit hook.

  3. It is recommended to avoid sync tasks in commit hooks. Instead, wrap the tasks in Session.ScheduleTask or Scheduling.ScheduleTask. In essence, when doing anything more than updating database objects, an asynchronous task should be scheduled for it. Otherwise, unexpected behavior might occur, such as Self.GET calls returning null.

The output produced is as follows (accurate to ):

Those familiar with .NET recognize Starcounter follows a convention of .NET for commit hooks. Currently, the first argument of the callback isn't used. The second argument is a reference to an object being transacted (for create, update and pre-delete events) or an ObjectNo of the object which itself is already deleted (for post-delete event). As in the .NET convention one can have an arbitrary number of event handlers registered per event, which will be triggered in the order of registration on the event occurrence.

Remember that after object is physically deleted in the end of a successful transaction scope, you can no longer access it in a post-delete commit hook delegate. However you might still want to do something meaningful with it just around the moment of deletion. That is why the pre-delete hook is introduced. Note that a pre-delete hook triggers callback inside the transaction scope, but not in the end of transaction. It means that, in case a transaction has been , any pre-delete hook for any object deleted inside this transaction will also be executed N times, while all other hooks will be executed exactly once, right after a successful transaction commit. Thus, consider pre-delete hook behaving as a transaction side-effect.

trigger
CRUD
ObjectNo
EventHandler
retried N times