Working with persistent objects (Create, Update, Delete)

Having initialised the database, let us create, update and delete some objects in the database. This section is covered in the GettingStarted sample Step1

Using the Genome Context

While the Genome DataDomain is responsible for accessing the database, the Genome Context is responsible for managing object state in the AppDomain.

Working with persistent object state

To work with persistent object state, a Genome Context has to be instantiated by the client. A persistent object is represented by a Genome proxy, which is independent of the Context. Switching the Context means switching state for the Genome proxy. The proxy itself remains valid after a Context switch or even without any associated Context. However, as long as there is no Context instantiated, the proxy cannot retrieve any state of the referred object.

A Genome proxy holds the object identity values (primary key values) of the referred persistent object. The proxy implicitly looks up the current Context to set or retrieve object state. The Context maintains an identity map to ensure that each object instance state is maintained only once within the Context. The Context also performs automatic lazy loading when the object state does not exist yet. Finally, the Context tracks all updates to persistent objects and commits them to the database when the Context is committed.

Current Context, Context Stack and Context Stack Binding

As previously mentioned, the Genome proxy locates the current Context to work with persistent state in the AppDomain. Genome maintains a Context Stack for storing the current Context and all previously activated Contexts. The client can push and pop Contexts to the Context stack as needed, but only the top element of the stack is used as the current Context by Genome proxies.

Genome implements a strategy pattern to locate the Context Stack providing the current Context to the Genome proxies. By default, Genome implements a context stack binding strategy that associates the Context stack with the current thread, providing an independent Context Stack (and so a separate current Context) for each thread. For other architectural scenarios, different context stack binding strategies might be useful. For example, the Genome Extensions for Web Applications provide a strategy to bind the Context Stack to the HttpContext of the current request that provides a Context Stack for every request. The NorthwindDataBinding sample implements a simple strategy which binds the Context Stack to the AppDomain, providing a single AppDomain-wide Context Stack.

Creating and disposing a Context

The Context might hold references to scarce resources, such as database transactions, and thus should be explicitly disposed when no longer used; hence it implements the IDisposable interface.

Genome provides different factory methods for instantiating Contexts, which can configure Context behaviour for caching, locking and transactional consistency. A Context has to be pushed to the Context stack to become the current Context.

After working with a Context, it has to be popped from the Context stack to make the previous Context current again. Although not mandatory, in most cases the Context is also disposed when popped from the Context stack. If it is to be reused later on (e.g. for caching state from the database retrieved by the Context), it can be kept and pushed to the Context Stack again later.

Since the Context is generally pushed to the Context stack when instantiated and disposed when popped from the Context stack, the Context.Push method returns a context stack cleaner object that implements IDisposable to pop the Context from the Context stack and optionally also disposes the Context. In C# the using statement can be utilised to ensure a Context is popped from the stack and disposed after working with it:

using(Context.Push(LocalContext.Create()))

{

    // work with Context here

}

Context creation and activation with the C# using statement and the context stack cleaner

LocalContext.Create() is one of the Genome factory methods for creating a Context. Context.Push() is a static method of Context to push a given Genome Context to the Context stack.

The code above can also be expressed explicitly:

Context ctx;

ctx = LocalContext.Create();

Context.Push(ctx);

try

{

    // work with Context here

}

 

finally

{

    Context.Pop();

    ctx.Dispose();

}

Explicit context creation and disposal protected with a try/finally to ensure the Context is popped of the stack and disposed

Creating persistent objects

To create persistent objects, a factory method of the DataDomain has to be used. The factory method takes an arbitrary list of parameters to call the appropriate constructor of the class.

The previously implemented Product class specifies a constructor that takes a string for the name of the product and a decimal for the price of the product. A new product can thus be instantiated like this:

Product product;

 

//Create

using (Context.Push(LocalContext.Create()))

{

    product = dd.New<Product>("x", 12.00m);

    Context.CommitCurrent();

}

Code snippet showing how to create new persistent objects

The product instance is not written to the database until the Context is committed. Disposing a Context that has not been committed discards all recorded changes to persistent objects.

When the product is created, the constructor of the Product class is executed. Please note that the constructor of Product is only executed when it is created in the DataDomain. When product is loaded from the DataDomain with a query, the OnLoad() event of the object is called instead, if it implements the ILoadCallback interface.

Updating persistent objects

Persistent objects can be updated by simply setting the desired property values of the object proxy. When the Context is committed, the changes are written to the database. Otherwise, the changes will be discarded.

We will reuse the previously initialised Genome proxy pointing to the product instance we have created. As we have disposed the Context already, we now create a new Context to perform the update:

//Update

using (Context.Push(LocalContext.Create()))

{

    p.Name = "y";

    Context.CommitCurrent();

}

Code snippet showing how to update persistent objects

Note that when you set p.Name, the Genome proxy tries to look up the object state of p in the current Context. As the Context was just instantiated, the state of p has not been loaded yet and a lazy load of p occurs.

When the Context is committed, an update statement that sets the Name property of the p is sent to the database.

Deleting persistent objects

Persistent objects can be deleted using the Context.DeleteCurrent() method (same as Context.Current.Delete()). We will again reuse the Genome proxy pointing to p together with a new Context.

//Delete

using (Context.Push(LocalContext.Create()))

{

    Context.DeleteCurrent(p);

    Context.CommitCurrent();

}

Code snippet showing how to delete persistent objects

Running Sample_CUD()

After putting everything together, the sample looks like this:

static void Sample_CUD()

{

    Product p;

   

    //Create

    using (Context.Push(LocalContext.Create()))

    {

        p = Helper.dd.New<Product>("x", 12.00m);

        Context.CommitCurrent();

    }

 

    //Update

    using (Context.Push(LocalContext.Create()))

    {

        p.Name = "y";

        Context.CommitCurrent();

    }

 

    //Delete

    using (Context.Push(LocalContext.Create()))

    {

        Context.DeleteCurrent(p);

        Context.CommitCurrent();

    }

}

Sample_CUD() of Client/Program.cs

After replacing the call to InitialiseDatabase() with Sample_CUD() in Main(), the Console shows the following output:

Console output of Client running Sample_CUD()

Note that the product has to be loaded up again for each new Context.

Let us modify the sample to use a single Context for all operations that is only committed in the end:

static void Sample_CUD_SingleContext()

{

    Product p;

    

    using(Context.Push(LocalContext.Create()))

    {

       

        //Create

        p = Helper.dd.New<Product>("x", 12.00m);

 

        //Update

        p.Name = "y";

 

        //Delete

        Context.DeleteCurrent(p);

 

        Context.CommitCurrent();

    }

}

Sample_CUD_SingleContext() of Client/Program.cs

Because Genome tries to optimise updates sent by the Context to the database, running Sample_CUD_SingleContext() from Main() yields the following output on the Console:

Console output of Client running Sample_CUD_SingleContext()

The “Genome basics” section, covered in Step1, ends here.

 



Initialising and using a DataDomain Relations in the class model