Hi,

how can I properly deal with several Sessions in WPF MDI UI?

Best regards


Updated at 09.08.2010 14:49:05

Thank you for your quick reply!

We have been testing your outstanding product over a few years by now. We believe that with version 4.3 it now has reached the stage to develop new "Real Live" applications of varying complexity with DataObjects.NET, Linq and WPF.

Basis for our development is first of all a simple WPF application. To offer the users the option of multiple active dialogs (e.g. to enter addresses) for simplicity we use the WPF controls of DevExpress.

As for simple dialogs the effort of O2O mapping should be saved, the disconnectedState provides the perfect solution.

Program structure (request):

Main-Thread (UI)......................................................................]    Background-Threads
Main-Session (not disconnected)      Disconnected Session 1 ... Disconnected Session n     - Own Sessions (not disconnected)

- Several complex MDI dialogs        - MDI dialog 1             - MDI dialog n             (e.g. Networking)
  with O2O mapping                                 Sessions not merged!!!

- Timer driven DBA routines
  with short transactions

- Several other DBA routines
  with short transactions

In our simple tests with 2 disconnected sessions we get the following exception: "An attempt to automatically activate Session 'Default, #5' inside Session 'Default, #6' (Session switching) is blocked. Most likely, mixed usage of objects from different Sessions is a result of a bug in your code. Use manual Session activation (Session.Activate() or Session.Deactivate()) to avoid this exception, if this is intentional."

...
      session = Session.Open( App.Domain );
      disconnectedState = new DisconnectedState();
      disconnectedState.MergeMode = MergeMode.PreferOriginal;
      disconnectedScope = disconnectedState.Attach( session );
      transactionScope = Transaction.Open();
      using( disconnectedState.Connect() )
      {
        ...

        var personQuery = Query.All<Model.Person>()
          .Prefetch( person => person.Language )
          .Prefetch( person => person.Salutation )
          .Prefetch( person => person.Title )
          .Prefetch( person => person.Addresses,
                     addresses => addresses
                       .Prefetch( address => address.Country )
                       .Prefetch( address => address.Region ) )
          .Prefetch( person => person.Communications )
          .OrderBy( person => person.LastName )
          .ThenBy( person => person.FirstName );
        // ObservableCollections
        foreach( var item in personQuery )
        {
          foreach( var listItem in item.Addresses )
            item.AddressesList.Add( listItem );
          foreach( var listItem in item.Communications )
            item.CommunicationsList.Add( listItem );
          persons.Add( item );
        }
      }
      ...

How can we implement the program structure?

Thank you in advance for your assistance.

Kind Regards


Updated at 10.08.2010 14:42:19

Thank you for your interesting hints!

However, the exception is released although no mixing of object instances and sessions occurs.

I have attached a simple example with 2 listboxes only. The listbox entries each come from a different entity class and are bound to different disconnected sessions. The entries have nothing in common, nevertheless the session switching exception appears.

Kind Regards


Updated at 10.08.2010 17:43:37

The last post has led to the crucial hint!

"by default there should be no active Session in UI thread"

The MDI dialoges now operate as expected. For everyone who are interested in the solution, I have attached the corrected mini-example.

Thank's a lot for your quick and effective support!

Kind Regards

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

asked Aug 07 '10 at 14:11

Editor's gravatar image

Editor
46156156157

edited Sep 03 '10 at 16:18

Alex%20Yakunin's gravatar image

Alex Yakunin
29714412


One Answer:

Alex (Xtensive) wrote:

A lot depends on expectations: if it's enough to have s single Session, the case is the same as with regular WPF application.

Otherwise you should ensure appropriate Session is active when object is created by a particular document/window to make it bound to that Session - AFAIK, that's the only case you should care about. Since all other objects are already bound to their Sessions, and Sessions are automatically activated when methods of these objects are invoked, there is no need to think about this e.g. in case when objects are created inside such calls.

And, if there is MDI, likely, you'll need to merge DisconnectedStates in some cases, as well as deal with version conflicts. See:

  • DisconnectedState.Merge method.

  • DisconnectedState.MergeMode (used in all lazy loading scenarios).

  • Session.NotifyChanged method - it raises change notification via (INotifyXxx) on all cached objects having attached INotifyXxx event handlers.


Alex (Xtensive) wrote:

Session switching exception indicates that our Session activation aspect injected into some method of sessionBoundObjectO, that belongs to sessionX, tries to activate session of this object (i.e. sessionX), while sessionY (i.e. different Session) is already active.

Normally this means object that belongs to some different Session is, by some reason, used in the current Session - e.g. you pass EntitySet from sessionA to a method of entity from sessionB. So this is purely a check designed to prevent possible unintentional errors.

How to resolve it: obviously, all depends if this is really intentional, or not:

  • This can be intentional, if you e.g. clone objects from some other database, or to/from a Session with attached DisconnectedState, i.e. you write something like this: clone.Property = source.Property. This is the case when you should use Session deactivation block (described below).

  • Also, this can be an unintentional attempt to pass an object from different Session. In this case you should fix your own code - i.e. ensure the objects from appropriate Session is passed as an argument.

To detect, if the case you have is intentional or not, study the exception stack trace, or break on this exception and study local variables on the stack.

So what is session deactivation block?

using (Session.Deactivate()) {
  // Any Session can be activated here w/o exception, i.e. session switching check will pass inside this block
}

So e.g. code with property cloning must look like this in this case:

using (Session.Deactivate()) {
  clone.Property1 = source.Property1; // Note that clone.Property1 call will activate clone.Session inside it, as well as source.Property1 call will activate source.Session
  ...
  clone.PropertyN = source.PropertyN;
}

Note that:

  • Currently there is no way to turn off this check at all, although I'm thinking about adding support for this. So if you'll see this is necessary, any proposals are welcome.

  • It should be stressed everything will work even without this check. Earlier there were no such check at all - I decided to add it to help discovering such issues earlier. I.e. people tend to think this is some kind of DO-specific requirement, although it isn't. Normally it's wrong when you use objects from different Sessions, and moreover, make them using each other, so there is a default check for this. But I agree, if there is at least one case when this check must be disabled at all, there must be API for this.


Alex (Xtensive) wrote:

Forgot to add one important note: don't manually activate Session in UI thread, i.e. use Session.Open(domain, false) for to open it (or deactivate it there). Otherwise, you'll get Session switching exception when UI thread will try to e.g. read property of Entity from another Session.

This doesn't mean Session can't be activated when UI thread e.g. reads properties of your entities - on contrary, it will be activated in this case, but you must ensure no Session is already active at this moment. I.e. by default there should be no active Session in UI thread.


Alex Kofman wrote:

I recommend you to look at my sample of WPF application with DataObjects.Net DisconnectedState. You can find it here: http://alex-kofman.blogspot.com/2010/01 ... ample.html

There are several separated windows, each one uses its own disconnected session. In order to simplify usage of DisconnectedState I've introduced UiSession class that incapsulated UI-related operations with Session and DisconnectedState. Usually UiSession is used in following way:

private readonly UiSession uiSession = new UiSession();

public void EditCustomer(Key customerKey)
{
  uiSession.Open();
  using (uiSession.Activate())
  using (uiSession.Connect()) {
    DataContext = customerKey==null ? 
      new Customer() : 
      Query.Single<Customer>(customerKey);
  }
  Show();
}

private void ButtonOkClick(object sender, RoutedEventArgs e)
{
  try {
    uiSession.SaveChanges();
    Close();
  }
  catch(Exception exception) {
    MessageBox.Show(exception.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
  }
}

Although I'm not sure that this sample correctly works with DO 4.3, it was written for version 4.2...


Alex (Xtensive) wrote:

This sample is shipped as Sandbox project in v4.3, but AFAIK, it doesn't fully work now (i.e. I seen some exceptions while testing it few weeks ago).

I.e. the proposed approach there is correct in general (it's ~ like the one described here), but there are some minor issues.

answered Aug 09 '10 at 07:54

Alex%20Yakunin's gravatar image

Alex Yakunin
29714412

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