I have been breaking my head over this for the last couple of hours and I'm unable to identify what exactly I'm doing wrong. It's probably very simple. I tried to mimic the 4.3 WPF sample as this covers most of what I need to be able to do.

Since I am developing a WPF application I resort to using the disconnected state approach. (I migrated from DO 4.3 to DO 4.4.) (Also, I don't know if it's vital, but I plan to use 2 different domains simultaneously.)

I created an Entity called GlobalSettings which contains 2 fields, namely Id (int) and Data (string).

When the application starts I initialize my service which creates a new Domain using an SQL CE database file. (Also note that my database should contain only 1 instance of a GlobalSettings object)

private void Reset() {
  m_transactionScope.DisposeSafely();
  m_disconnectedScope.DisposeSafely();
  m_session.DisposeSafely();

  m_session = m_domain.OpenSession(new SessionConfiguration(SessionOptions.ClientProfile | SessionOptions.AutoActivation));
  m_disconnectedState = new DisconnectedState();
  m_disconnectedScope = m_disconnectedState.Attach(m_session);
  m_transactionScope = m_session.OpenAutoTransaction();
}

public string Get() {
  string settings = string.Empty;
  using (m_disconnectedState.Connect()) {
    foreach (GlobalSettings gs in m_session.Query.All<GlobalSettings>()) {
      m_globalSettings = gs;
      settings = m_globalSettings.Data;
    }
  }

  return settings;
}

public void Set(string data) {
  if (m_globalSettings = null) {
    m_globalSettings = new GlobalSettings();
  }
  m_globalSettings.Data = data;

  m_transactionScope.Complete();
  m_transactionScope.Dispose();
  m_transactionScope = null;

  m_disconnectedState.ApplyChanges();
  Reset();
}

When I start the application the first time my database file is created, Get() returns an empty string as expected, I fill it in with default data and continue my application run. When the application is closing the Set() function is called and does not report any errors. So it looks like everything went OK.

However, when I start my application a second time I expect to be able to read out the previously stored data. But when I enter the Get() function and the Query.All<t>() call is performed it doesn't return any objects so it returns an empty string again.

I see no exceptions or errors, hence I assume everything went OK. What am I doing wrong here?

asked Feb 18 '11 at 08:52

jensen's gravatar image

jensen
399913


2 Answers:

I see several issues in this code:

  1. TransactionScope is created before DS.Connect() - normally this isn't a good practice, since this implies you're going to keep it longer when connection block continues.
  2. It is closed in Set and Reset methods; Reset method closes it with rollback by default. That's a bit strange as well. Standard practice is to keep DS connected (nearly always) and use explicit transactions when it's necessary (e.g. when you perform several queries). So transaction scope must be opened & closed for relatively short period, and always inside DS.Connect() block.
  3. If you use v4.4, I recommend you to pass Session object to GlobalSettings..ctor explicitly.
  4. Use SingleOrDefault() instead of foreach loop.
  5. It's recommended to use singular nouns for class names that aren't collections \ Enums (that's about GlobalSettings).

On the other hand, I don't see any explicit reason why you don't get the changes persisted. On the other hand, this isn't all the code - i.e. probably I don't know something important. To identify the reason, you should debug Set method and check if:

  • Changes are actually persisted (see SQL Profiler log)
  • Transaction where they're persisted is committed (see SQL Profiler log as well)
  • You don't create the second GlobalSettings object. Since you use foreach loop to iterate all of them and don't explicitly define the order there, there is a chance the object you create is enumerated but skipped there.

answered Feb 20 '11 at 23:33

Alex%20Yakunin's gravatar image

Alex Yakunin
29714412

edited Feb 20 '11 at 23:34

I added some comments in a new answer, but I also created a sample application of what I'm trying to do. You can download it from http://www.aimproductions.be/jensen/DOExample.zip I know it is possible I'm doing things completely wrong here, but as I said, I tried to mimic the 4.3 implementation of the WPF sample as much as I could, since that seems the way I need to use it.

(Feb 21 '11 at 08:40) jensen jensen's gravatar image

Ok, I cleared up all the confusion. I am using the 3.5 build which doesn't ship all the samples that are part of the 4.0 build. So I was looking at this all wrong. I accidentally download the 4.0 build and found it contained more samples, including a WPF sample. And then everything made sense.

(Feb 21 '11 at 09:34) jensen jensen's gravatar image

I am adding another answer since I don t have enough space to type everything in the comment box.

It is pretty much all the code, the only missing part is the domain creation. This function is only called once by the constructor of my service.

private void SetupDomain() {
  m_domain = null;
  string cs = "sqlserverce://localhost/" + m_dbFile;
  DomainConfiguration dc = new DomainConfiguration(cs) { UpgradeMode = DomainUpgradeMode.Default; }
  dc.Types.Register(typeof (GlobalSettings).Assembly, typeof (GlobalSettings).Namespace);
  m_domain = Domain.Build(dc);
}

I was unable to attach the SQL profiler. The links that point out how are no longer available. I did found something in Google cache, but I am creating a standalone library. My main .exe has nothing to do with DataObjects, so when I tried to add several things to the app.config file it would throw some exceptions. Is there a way to configure this from within the code?

I modified the code according to your suggestions.

So, in the constructor of my service I create the Domain and call the Reset() function. I changed the Get() and Set() functions of my service to the following:

public string Get() {
  string data = string.Empty;
  using (m_disconnectedState.Connect()) {
    using (var ts = m_session.OpenAutoTransaction()) {
      m_globalSettings = m_session.Query.SingleOrDefault<GlobalSettings>();
      if (m_globalSettings != null) data = m_globalSettings.Data;
      ts.Complete();
    }
  }
  return data;
}

public void Set(string data) {
  using (m_disconnectedState.Connect()) {
    using (var ts = m_session.OpenAutoTransaction()) {
      if (m_globalSettings == null) m_globalSettings = new GlobalSettings(m_session);
      m_globalSettings.Data = data;
      ts.Complete();
    }

    m_disconnectedState.ApplyChanges();
  }
  Reset();
}

I create my domain using the Default profile. When the database file is created it is 148kb in size, but normally after adding the settings it does not grow in size, and I would expect that to happen significantly.

I do find it strange that I need to call Reset() after doing the Set(). But that's the way things are done in the WPF example that is part of DO4.3.

Also, you say I should minimize the transaction scope. I don't think I can do that for most things in my application. Since I am creating a WPF (MVVM) application I need to have a start (where I init) and I need to be able to do hundreds of operations over a few hours and only at the end my user has the option to save or not. When looking at other ORM frameworks this is much easier as they don't store changes on objects until I explicitly call Save() or something alike. (Given, within a transaction scope, but it's done on the object itself rather than on the total system state.)

Oh, I also tried without using the disconnected state object and since Get() and Set() should only be called once in the entire application, I first approached them apart from each other. Each created the domain, the session and did everything on their own, but when I called the Set() I got an error that I needed to open a Session() first, but that was clearly done. So that's why I started using the disconnected state.

edit: I got the logging working. As far as I can see it correctly detects that my schema did not change and thus it doesn't perform any upgrades.

In my Get() function it logs the following data:

75,84s @9     Debug Orm                          Session 'Default, #4'. Transaction: started.
A first chance exception of type 'System.ArgumentException' (Key values array is empty) occurred in Xtensive.Orm.dll
 81,45s @9     Debug Orm                          Session 'Default, #4'. Transaction: completed.

Whether or not I start from a clean DB file, it always throws the same exception.

In my Set() function it logs the following:

203,64s @9     Debug Orm                          Session 'Default, #4'. Transaction: started.
213,24s @9     Debug Orm                          Session 'Default, #4'. Transaction: completed.
214,44s @9     Debug Orm                        Session ''. DisconnectedState.Attach: completed.
214,45s @9     Debug Orm                        Session 'Default, #4'. DisconnectedState.ApplyChanges: started.
214,46s @9     Debug Orm                          Session 'Default, #4'. Transaction: started.
214,47s @9     Debug Orm                            Session 'Default, #4'. Transaction: started.
214,49s @9     Debug Orm                              Session 'KeyGenerator, #5'. Transaction: started.
214,50s @9     Debug Orm                              Session 'KeyGenerator, #5'. Transaction: completed.
214,61s @9     Debug Orm                            Session 'Default, #4'. Transaction: completed.
214,66s @9     Debug Orm                          Session 'Default, #4'. Transaction: completed.
214,66s @9     Debug Orm                        Session 'Default, #4'. DisconnectedState.ApplyChanges: completed.
214,66s @9     Debug Orm                        Session ''. DisconnectedState.Attach: started.

answered Feb 21 '11 at 05:22

jensen's gravatar image

jensen
399913

edited Feb 21 '11 at 07:36

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