Imagine the following scenario in a WinForms online/offline implementation in 3.9.5:

User begins to create a new Order object (using offline object bound to WinForm). Order has a Status (enum: PendingApproval, Approved). By default, Order.Status == OrderStatus.PendingApproval.

There is a combo box with the order statuses, and the user can approve the order by selecting the Approved value. So the user creates and approves an order, but has not persisted the object yet (Key.ID < 0). When the user persists his changes, all of the changes are repeated in the online type.

In the online type, I catch OnPropertyChanged, and if I detect that Status has been changed to Approved, I send an email notification to the customer.

The point is, the email is sent only when the order is persisted. This was easy to handle in 3.9 because I can include the emailing code in the online Order type, but omit it from the offline type.

How do you propose we should be handling such cases in 4.0... when DisconnectedState is released of course =) ?

If 4.0 will still contain IDataObjectEventWatcher type service... then these types of things can be handled by putting such code in a class that implements that interface. It would also be a good way of separating such code from the entity class.


Updated at 05.01.2010 23:03:00

Hi Alex,

But what isn't clear is whether methods are re-executed when persisting (a la online/offline model in 3.9), or whether the entity's state is simply persisted (without re-executing methods in the same order as they were performed "offline").

If the user sets Status = Approved while disconnected, OnPropertyChanged will be raised, and I will NOT send an email (because this.Session.IsDisconnected == true). Now, when the user saves changes, will OnPropertyChanged be re-executed again? If so, this will solve this particular problem (because this.Session.IsDisconnected == false so I can send my email), but this introduces a new (and much larger) set of problems where I must coordinate actions between disconnected and connected operations.

I think the ideal answer to my question is that such an emailing feature MUST be implemented by subscribing to Session events... because if it were possible to implement such a feature by the other methods we've discussed, then this would introduce a lot of problems to manage.


Updated at 05.01.2010 23:11:42

Alex, can you explain the order of events that are raised in our example?

In 3.9, they would be as follows:

  1. User creates offline object. Offline Order.OnCreate is called

  2. User sets Status = Approved Offline Order.OnSetProperty and OnPropertyChanged are called

  3. User persists changes Online Order.OnCreate is called (IDataObjectEventWatcher.OnCreate is also called) Online Order.OnSetProperty and OnPropertyChanged are called (IDataObjectEventWatcher.OnXXX are also called)

What is it like in 4.0 with disconnected state?

This will explain the answer better than the above questions

This thread was imported from our support forum. The original discussion may contain more detailed answer.

asked Jan 05 '10 at 12:53

ara's gravatar image

ara
395868791


One Answer:

1) It will be possible to distinguish between connected and disconnected sessions (i.e. a Session with attached DisconnectedState). So you should send e-mail when ~ !this.Session.IsDisconnected.

2) All event now are exposed directly via Session, this replaces IDataObjectEventWatcher. Attached class diagram shows available events. "Executing" refers to "pre" stage, "Execute" - to "post" stage (action is performed, but there is still chance to throw an exception; some additional data can be available here) and "ExecuteCompleted" - to "completed" stage (no chance to revert the action except transaction rollback).


> But what isn't clear is whether methods are re-executed when persisting (a la online/offline model in 3.9), or whether the entity's state is simply persisted (without re-executing methods in the same order as they were performed "offline").

When DisconnectedState is attached, in addition to acting as "proxy" between RDBMS and ORM, it makes its own OperationLogger instance to listen Session.OperationCompleted event. This is how it gathers all the information of actions performed in session it is attached to.

Operations we log actually aren't restricted to some predefined set of operations. You can create custom ones and log them as well. Let me show EntitySetBase.Add method body to explain how this framework works:

[Transactional]
    internal bool Add(Entity item)
    {
      if (Contains(item))
        return false;

      try {
        using (var context = OpenOperationContext(true)) {
          if (context.IsEnabled()) // So at least one logger is listening to events
            context.Add(new EntitySetItemOperation( // Any Operation descendant
              Owner.Key, 
              Field, 
              Operations.OperationType.AddEntitySetItem, 
              item.Key));

          SystemBeforeAdd(item); // "Pre" event

          if (Field.Association.IsPaired)
            Session.PairSyncManager.Enlist(OperationType.Add, Owner, item, Field.Association);
          if (Field.Association.AuxiliaryType != null && Field.Association.IsMaster)
            GetEntitySetTypeState().ItemCtor.Invoke(Owner.Key.Value.Combine(item.Key.Value));

          State.Add(item.Key); // State modification
          Owner.UpdateVersionInternal(); // Owner version update, if there is a field marked as [Version]

          SystemAdd(item); // "Post" event
          SystemAddCompleted(item, null); // "Completed" event
          context.Complete(); // Session.OperationCompleted event
          return true;
        }
      }
      catch (Exception e) {
        SystemAddCompleted(item, e); // "Completed" event with exception
        throw;
      }
    }

Logged operations are stored in OperationSet type. Its Apply(...) method repeats the sequence of operations by sequentially executing Prepare (may add some objects to prefetch queue) and Execute methods on each Operation; OperationSet.Apply can be executed in any Session (even disconnected - it does not matter).

So it's clear that disconnected Session does not differ much from the connected one. But if Session is disconnected, the code operating there must really care about this in certain cases:

  • Disconnected session isn't able to interact with DRBMS outside of DisconnectedState.Connect() region - query execution will lead to an exception in this case.

  • Since queries are executed against real storage, the results they return might differ from the state you see "through" disconnected session, since DisconnectedState there exposes all the cached changes, but they aren't in storage yet.

So because of above we recommend "turning off" any complex logic in disconnected mode.

And, as you see, by adding disconnected operation mode this way, we reached the following goals:

  • there is no need to create separate "offline" entities; online = offline

  • if there is no complex logic (~= just CRUD operations are expected there), there is no need add any special code for handling disconnected scenario properly

Disadvantage is that now connected and disconnected modes separation is less explicit. On the other hand, this is what most of people expect from us here (do almost nothing to get their sessions "disconnected" - for WPF, etc.). When a completely separate set of entities is necessary for disconnected mode, likely, the most probable scenario is DTO (e.g. for Silverlight, etc.), and we're targeting it as well, but by completely different (even better, completely separate) part of the framework handling object-to-object mapping scenarios (with LINQ!). O2O mapper is scheduled for v4.2; it is in development ~ from mid-December.

**> Alex, can you explain the order of events that are raised in our example? In 3.9, they would be as follows:

  1. User creates offline object. Offline Order.OnCreate is called**

Session.KeyGenerated Session.EntityCreated Session.OperationCompleted // new EntityOperation(Key, OperationType.CreateEntity)

> 2. User sets Status = Approved Offline Order.OnSetProperty and OnPropertyChanged are called

Session.EntityFieldValueSetting Session.EntityFieldValueGetting Session.EntityFieldValueGet Session.EntityFieldValueGetCompleted Session.EntityFieldValueSet Session.EntityFieldValueSetCompleted Session.OperationCompleted // new EntityFieldSetOperation(entity.Key, field, value)

> 3. User persists changes Online Order.OnCreate is called (IDataObjectEventWatcher.OnCreate is also called) Online Order.OnSetProperty and OnPropertyChanged are called (IDataObjectEventWatcher.OnXXX are also called)

Two logged operations are executed on Session the OperationSet is applied to (DisconnectedState.ApplyChanges() applies them to the Session disconnected state is attached to).

Identical sequence of events is raised there.

answered Jan 05 '10 at 14:28

Alex%20Yakunin's gravatar image

Alex Yakunin
29714412

Forgot to add: likely, Session.KeyGenerated will be eliminated further (it will be anyway visible via OperationCompleted event).

(Jan 05 '10 at 14:28) Alex Yakunin Alex%20Yakunin's gravatar image
Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!
toggle preview

powered by OSQA