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:
- 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.