Show Blogger Panel Hide Blogger Panel
Alex Yakunin

October 20, 2009

Upcoming DisconnectedState: preliminary example code

If you don't know about DisconnectedState, please read first section of this post first.

Here is what we're going to deliver:
var keys = new List<Key>();
DisconnectedState state;

using (var session = Session.OpenDisconnected(domain)) {
  // Validation is off here by default,
  // so next line passes even if it violates validation rules
  var p = new Person { Name = "Alex", Age = -1 } 

  // DisconnectedState manages its own local transactions
  // and implements unit of work pattern.
  try {
    () => {
      p.Age = 1;
      throw new Exception();
    }.InvokeTransactionally(); // If you don't know, 
    // that's the way to create and invoke anonymous
    // transactional method. 
    // See TransactionalExceptions type.
  }
  catch {}
  Assert.AreEqual(-1, p.Age); // Passes.

  var prefetchingQuery = 
    Query<Customer>.All.Where(...)
      .Prefetch(c => c.Orders);

  // This code will run in single local and single
  // database transaction. All the fetched instances
  // will be cached in DisconnectedState.
  for (customer c in Query<Customer>.All) {
    c.Name = string.Format("{0} ({1})", // Local change
      c.Name, c.Orders.Count);
    keys.Add(c.Key);
  }

  // Nothing is changed in the database yet -
  // all the changes are cached only in DisconnectedState.

  // Let's detach the state
  state = session.DisconnectedState;
}


// And attach it to a new Session
using (var session = Session.OpenDisconnected(domain, state)) {
  // This loop won't hit the database,
  // since everything it needs is already there
  for (customer c in keys.Select(Query<Customer>.Single(key)) {
    Console.WriteLine("Customer: {0}", c.Name);
    for (order o in c.Orders)
      Console.WriteLine("  Order: {0}", o.ToString());
  }

  // Let's accept changes now.
  // They'll be persisted in a single transaction;
  // version checks and validation will be performed
  // for all the changed objects.
  state.AcceptChanges();
}

It will be possible to extract the changes and apply them separately:
var disconnectedState = ...
var session = ...
// Let's create a change set containing both changes
// and original versions of all the affected objects. 
var changeSet = disconnectedState.GetChangeSet();
// Here we clone it by serialization,
// see Xtensive.Core.Helpers.Cloner
var changeSetClone = Cloner.Default.Clone(changeSet); 
// And finally, we apply the changes to
// another Session.
changeSetClone.Apply(session);

Initially DisconnectedState will track:
  • Entity creation, change and removal
  • Similar EntitySet operations.
So when you call ApplyChanges method (btw, it's possible to pass another Session there), change set is actually applied as sequence of Entity and EntitySet method invocations on objects from this Session. In future it will be possible to replicate top-level SessionBound method calls instead of low-level changed, that must bring true action sequence replication there.

It will be possible to:
  • RejectChanges. This won't affect any fetched information.
  • Prevent any transparent fetches. By default they're on - i.e. such a Session works in semi-connected mode, but you can restrict them to be available in special code blocks only (I'm not fully sure about final syntax of this, my current best option is () => { ... }.InvokeConnected() ).
  • Serialize DisconnectedState and deserialize it. You won't need to create Session and Domain objects on the client to make this working (in this case we will provide "default" domain resembling the structure of original Domain object; Session there will also be "closed" - it won't allow to transparently fetch anything there).
You'll see this feature working in a week or two. I'd be glad to hear any comments.