Session Management and Caching

XPO Team Blog
27 March 2006

This is a draft of an article about session management and caching. It's nearly complete, the main thing missing is a list of some use case descriptions. Apart from that, I'd like to get some feedback on this - so if you find you still have questions after reading this, or you would like specific details included, please leave a comment and tell me so. Eventually this will be published on the main website.

About Sessions

Layers

The XPO Session has evolved since XPO version 1. In the first version of XPO, the Session object encapsulated the object cache, the Dictionary, which stores all the metadata XPO has collected about persistent classes in the current application, and the connection. Most importantly, it also handled the process that XPO runs on initialization, where all the various types are recognized and checked against the persistent structures in the database. 

In XPO 6.1 we have made substantial changes to this system by extracting a separate data layer. The data layer lies between the connection provider (interface IDataStore) and the Session and its tasks are to handle all the metadata management that was previously part of the Session and to perform the database initialization/checking. The main benefit of this change is that Session creation has now become an extremly lightweight process - in many use cases it's desirable to create new Session instances rapidly and regularly, but with XPO 1 this wasn't really an option because of the overhead that each new Session instance had.

So how should Sessions be used?

As usual, there’s no one-size-fits-all solution to this question. But the recommendation, in a nutshell, is to use a fresh Session instance for each coherent batch of work. In many cases, using a UnitOfWork instead of a generic Session instance will be a very good idea (click here for some information about units of work). The best efficiency of a Session and the object cache it includes can be reached if the algorithms running with a single Session instance are long enough to make good use of the caching of relevant data, but short enough so that data doesn’t have to be reloaded. I hope the paragraphs below explain this statement in as much detail as needed.

The Session as an Identity Map

As was already the case in XPO 1, the Session implements an object cache, which serves the purpose of an Identity Map. The Identity Map is best explained in Martin Fowler's book "Patterns of Enterprise Application Architecture" (summary here), but in short, its task is to make sure that when the same object is part of two separately queried result sets, then the exact same object should be returned - not a clone or a copy of the object. The following test should always pass (assuming there's a Person object in the database with the Name Billy Bott):

[CSharp]

Person person1 = session.FindObject<Person>(new BinaryOperator("Name", "Billy Bott"));
Person person2 = session.FindObject<Person>(new BinaryOperator("Name", "Billy Bott"));
Assert.AreSame(person1, person2);
[VB.NET]

Dim person1 As Person = session1.FindObject(Of Person)(New BinaryOperator("Name", "Billy Bott"))
Dim person2 As Person = session1.FindObject(Of Person)(New BinaryOperator("Name", "Billy Bott"))
Assert.AreSame(person1, person2)

Note: The Session's object cache is not an optional feature, it can't be switched off.

The inner workings of the object cache are really very simple. Every time a query to the database is made, XPO checks for new versions of the relevant objects in the database (using Optimistic Locking, see below for details on this). If the database has more current information than the object cache, the relevant objects are updated in the cache. New objects are created as necessary and added to the cache, and the resulting collection of objects is returned. So if an object exists in the cache already, it is never created a second time and the requirements of the Identity Map are fulfilled.

How current is the object cache?

When copies of data are stored on the client side, the question to ask is obviously "how do we make sure that this data is always as current as necessary?" But while that question is pretty easy to come up with, the answer to it is not as easy - mostly because "as current as necessary" means different things in different projects. XPO implements and allows for a number of different approaches to deal with the problem of cache currency.

  • Using a new Session instance creates a new object cache. The shorter your Session instance's lifetime, the more current your object cache content. This is the only approach that doesn't break object cache consistency (for details on object cache consistency, see below) and it's the only generally recommended approach.
  • Using Optimistic Locking (again, see below for details), XPO checks the currency of objects in the cache automatically. The following test code shows how automatic updates work:
[CSharp]

// Create a test object
using (Session session = new Session( )) {
  new Person(session, "Billy Bott").Save( );
}

Session session1 = new Session( );
Session session2 = new Session( );

// Create a collection in session1 and check the content
XPCollection<Person> peopleSession1 = new XPCollection<Person>(session1);
Assert.AreEqual(1, peopleSession1.Count);
Assert.AreEqual("Billy Bott", peopleSession1[0].Name);

// Fetch the test object in session2 and make a change
Person billySession2 = session2.FindObject<Person>(new BinaryOperator("Name", "Billy Bott"));
billySession2.Name = "Billy's new name";
billySession2.Save( );

// Create a new collection in session1 - it will have the change from the other session
// Note that instead of another session, we could create this collection on a
// different client computer with the same outcome.
XPCollection<Person> newPeopleSession1 = new XPCollection<Person>(session1);
Assert.AreEqual(1, newPeopleSession1.Count);
Assert.AreEqual("Billy's new name", newPeopleSession1[0].Name);

// This last test shows the workings of the Identity Map - the object in the "old"
// collection has the same change, because it's actually the same object.
Assert.AreEqual("Billy's new name", peopleSession1[0].Name);
Assert.AreSame(peopleSession1[0], newPeopleSession1[0]);
[VB.NET]

' Create a test object					
Using session1 As Session = New Session
  New Person(session1, "Billy Bott").Save
End Using

Dim session1 As New Session
Dim session2 As New Session

' Create a collection in session1 and check the content
Dim peopleSession1 As New XPCollection(Of Person)(session1)
Assert.AreEqual(1, peopleSession1.Count)
Assert.AreEqual("Billy Bott", peopleSession1(0).Name)

' Fetch the test object in session2 and check the content
Dim billySession2 As Person = session2.FindObject(Of Person)(New BinaryOperator("Name", "Billy Bott"))
billySession2.Name = "Billy's new name"
billySession2.Save

' Create a new collection in session1 - it will have the change from the other session
' Note that instead of another session, we could create this collection on a 
' different client computer with the same outcome.
Dim newPeopleSession1 As New XPCollection(Of Person)(session1)
Assert.AreEqual(1, newPeopleSession1.Count)
Assert.AreEqual("Billy's new name", newPeopleSession1(0).Name)

' This last test shows the workings of the Identity Map - the object in the "old"
' collection has the same change, because it's actually the same object.
Assert.AreEqual("Billy's new name", peopleSession1(0).Name)
Assert.AreSame(peopleSession1(0), newPeopleSession1(0))
  • There are several methods that allow the programmer to reload content from the database explicitely - or at least it seems like this is what happens. There's Session.Reload(object), which reloads a single object, and XPBaseObject.Reload(), which has the object call into its session to reload itself. There's an alternate overload Session.Reload(object, forceAggregatesReload), which reloads all aggregated properties in addition to the object itself. And finally, there's XPBaseCollection.Reload(), which reloads all objects in a collection.

    To prevent breaking object cache consistency, the Session.DropCache() method is available to drop the complete object cache content at once. Be aware that dropping the object cache completely will invalidate all objects that have previously been loaded through that cache, so you must not continue working with these object instances after dropping the cache – all collections must be reloaded! This is mainly an alternative to the use of multiple sessions, for cases where it's desirable to reuse the same Session instance.

    The above code example could be modified like this to show collection reloading:

[CSharp]

// Create a test object
using (Session session = new Session( )) {
  new Person(session, "Billy Bott").Save( );
}

Session session1 = new Session( );
Session session2 = new Session( );

// Create a collection in session1 and check the content
XPCollection<Person> peopleSession1 = new XPCollection<Person>(session1);
Assert.AreEqual(1, peopleSession1.Count);
Assert.AreEqual("Billy Bott", peopleSession1[0].Name);

// Fetch the test object in session2 and make a change
Person billySession2 = session2.FindObject<Person>(new BinaryOperator("Name", "Billy Bott"));
billySession2.Name = "Billy's new name";
billySession2.Save( );

// The old session1 collection still has the old "version" of the object
Assert.AreEqual("Billy Bott", peopleSession1[0].Name);

// Now reload the session1 collection and check - the change will be there
peopleSession1.Reload();
Assert.AreEqual("Billy's new name", peopleSession1[0].Name);
[VB.NET]

' Create a test object
Using session1 As Session = New Session
  New Person(session1, "Billy Bott").Save
End Using

Dim session1 As New Session
Dim session2 As New Session

' Create a collection in session1 and check the content
Dim peopleSession1 As New XPCollection(Of Person)(session1)
Assert.AreEqual(1, peopleSession1.Count)
Assert.AreEqual("Billy Bott", peopleSession1(0).Name)

' Fetch the test object in session2 and check the content
Dim billySession2 As Person = session2.FindObject(Of Person)(New BinaryOperator("Name", "Billy Bott"))
billySession2.Name = "Billy's new name"
billySession2.Save

'The old session1 collection still has the old "version" of the object
Assert.AreEqual("Billy Bott", peopleSession1(0).Name)

'New reload the session1 collection and check - the change will be there
peopleSession1.Reload
Assert.AreEqual("Billy's new name", peopleSession1(0).Name)

When reloading data selectively, it is important to note that this is not always as explicit as one might think. The Session.Reload(...) methods are actually the only ones that have an immediate and inevitable effect - when they are called, the given object is refreshed from the database under all circumstances. When collections are reloaded, internally the collection is cleared and marked as "not loaded". So when it's accessed the next time, the "normal" data loading algorithm applies and the currency of the objects in the collection depends on the automatic mechanism above, utilizing Optimistic Locking.

Object Cache Consistency

The topic of object cache consistency has been mentioned a number of times in the previous paragraphs - so what do we mean by this? To explain, consider how the object cache in a given Session instance is filled. Objects are loaded, in collections or singly, and they are stored in the object cache. It is possible that objects which are returned in the result set of a specific XPCollection may be of mixed age - some may have been queried a while ago and existed in the object cache since then, while others have been queried and created just now.

On the surface, it appears to be very desirable to have the most current information available, everywhere and at every time. But if we give the matter a little more thought, it becomes clear that an automatic inplace update of objects has a lot of drawbacks of its own. For example, in one part of the application, using data from a global session, one algorithm might be happily doing its work, while in another part, for the same session, a different algorithm suddenly updates data from the database - that might be fatal to the outcome of the first routine, which obviously doesn't expect the data it works with to change suddenly. The whole topic is even more complicated if you imagine UI controls bound to data collections or single objects, and all the countless other situations where application state depends on data. Automatic notification is often not possible, so changes to data catch everybody unawares.

As these explanations try to show, current data is not always the single most valuable target. Sometimes it's much more important to have data that is consistent in itself, where the objects are all of the same "generation" and contain information that forms a complete picture correctly, from the application's point of view. Every partial change to this picture would contort the result.

This is why we talk about object cache consistency. Generally, as soon as you use a single instance of the object cache, i.e. a single Session instance, to do two or more kinds of work, you will fill the object cache with data that might not be logically consistent. Even worse, every time some of this data is refreshed from the database selectively, it's extremely probable that you destroy this consistency. The logical conclusion is simple: one single instance of the object cache, and as a conclusion, one single Session instance, should only ever contain data that is guaranteed to be logically consistent. A good way to be sure of this is to use Session instances liberally, one for each logical "part", each data handling "process" your application performs.

Despite our belief in these general recommendations, we have tried to make XPO 6.1 so flexible in its design that you can make these decisions yourself. We are trying to shed some light on the pros and cons of single vs. multiple session scenarios, as well as the various reloading practices - it's your decision which way your application is going to go.

Variations

Some of you may have noticed that the problem with object cache consistency is very similar to the problems that relational database systems try to solve with isolation levels. We can't directly reuse that idea in XPO, but we have introduced a flag to switch XPO's behavior when changed objects are encountered in the database. This can be used to configure the "isolation" of data in the object cache, and thereby influence object cache consistency.

The flag is accessible as XpoDefault.OptimisticLockingReadBehavior (for the default value) and as Session.OptimisticLockingReadBehavior (for the session instance specific value) and it can have the following values:

Ignore Changed objects are never reloaded. Best object cache consistency.
ReloadObjects Changed objects are automatically reloaded.
ThrowException When changed objects are encountered, a LockingException is thrown.
Mixed Outside of transactions, the behavior is ReloadObjects, inside transactions it's Ignore.

The default value for this flag is currently "Mixed".

Data Layer Caching

DatacachelayersIn addition to the object cache described above, XPO also includes functionality for a cache on the data level. This system caches queries and their results as they are being executed on the database server. Two classes must be combined to form a cache structure, DataCacheRoot and DataCacheNode. It's possible to build cache hierarchies out of a single DataCacheRoot instance and any number of DataCacheNode instances - this makes sense when certain parts of an application (or certain applications, thinking of client/server setups) need to use different settings for their data access, such as particularly current data. The minimum setup of the structure needs one DataCacheRoot and one DataCacheNode. The DataCacheNode is the one that actually caches data, the DataCacheRoot keeps some information global to that cache hierarchy.

The easiest case is this: whenever a query passes the cache that has been executed before, the result from that query is returned to the client immediately, without a roundtrip to the server.

The DataCacheRoot stores information about updates to tables. Every time a DataCacheNode contacts its parent, table update information is pushed in the direction from Root to Node. Obviously these regular contacts between a DataCacheNode and its parent are important to provide for current update information, so in the case where there’s no contact necessary for a longer stretch of time, the MaxCacheLatency comes into operation. This property defines the maximum time that is allowed to pass before a contact to the parent becomes mandatory. So if a DataCacheNode receives a query, it finds a cached result set for this query, and more time than given by MaxCacheLatency has passed since its last parent contact, it will perform a quick request to its parent to retrieve update information.

An important thing to mention is that this system relies on the idea that the cache structure knows about all changes that are being made to the data. So even in a multi-user setup you should make sure that all updates (and selects, obviously) are performed in a way that allows them to be recognized by the cache structure.

For example, a typical client/server setup would expose the interface of the DataCacheRoot (which is called ICacheToCacheCommunicationCore) via Remoting, and use a DataCacheNode on each client. All XPO requests would be routed through the client-side DataCacheNode and through the DataCacheRoot server-side. An elaborate example of this Remoting setup is not subject of this article, but look out for a future article on Remoting – for the time being, there is a pair of samples called “CacheRoot” and “CacheNode” that comes with the XPO distribution.

If, for any reason, there are changes in the database that have been made without going through the cache structure, there are two utility methods that can be helpful. These are NotifyDirtyTables(…) (to inform the cache about specific changes) and CatchUp() (to synchronize the cache completely).

This test code shows how a data cache with two nodes can be constructed, where caching takes place and where the DataCacheRoot stops caching because of updates. This sample uses an InMemoryDataStore, which could of course be replaced by any other connection provider.

[CSharp]

// Create the data store and a DataCacheRoot
InMemoryDataStore dataStore = 
  new InMemoryDataStore(new DataSet( ), AutoCreateOption.SchemaOnly);
DataCacheRoot cacheRoot = new DataCacheRoot(dataStore);

// Create two DataCacheNodes for the same DataCacheRoot
DataCacheNode cacheNode1 = new DataCacheNode(cacheRoot);
DataCacheNode cacheNode2 = new DataCacheNode(cacheRoot);

// Create two data layers and two sessions
SimpleDataLayer dataLayer1 = new SimpleDataLayer(cacheNode1);
SimpleDataLayer dataLayer2 = new SimpleDataLayer(cacheNode2);
Session session1 = new Session(dataLayer1);
Session session2 = new Session(dataLayer2);

// Create an object in session1
Person billySession1 = new Person(session1, "Billy Bott");
billySession1.Save( );

// Load and check that object in session2
XPCollection<Person> session2Collection = new XPCollection<Person>(session2);
Assert.AreEqual("Billy Bott", session2Collection[0].Name);

// Make a change to that object in session1
billySession1.Name = "Billy's new name";
billySession1.Save( );

// Reload the session2 collection. The DataCacheNode returns the result 
// directly, so the change is not recognized. For the fun of it, do this 
// several times. This whole block doesn't query the database at all.
for (int i = 0; i < 5; i++) {
  session2Collection.Reload( );
  Assert.AreEqual("Billy Bott", session2Collection[0].Name);
}

// Do something (anything) in session2. This makes the cacheNode2 
// contact the cacheRoot and information about the updated data in 
// the Person table is passed on to cacheNode2.
new DerivedPerson(session2).Save( );

// Reload the session2 collection again, and now the change is recognized.
// In this case, the query goes through to the database.
session2Collection.Reload( );
Assert.AreEqual("Billy's new name", session2Collection[0].Name);
[VB.NET]

' Create the data store and a DataCacheRoot				
Dim dataStore As New InMemoryDataStore(New DataSet, AutoCreateOption.SchemaOnly)
Dim cacheRoot As New DataCacheRoot(dataStore)

' Create two DataCacheNodes for the same DataCacheRoot
Dim cacheNode1 As New DataCacheNode(root1)
Dim cacheNode2 As New DataCacheNode(root1)

' Create two data layers and two sessions
Dim dataLayer1 As New SimpleDataLayer(cacheNode1)
Dim dataLayer2 As New SimpleDataLayer(cacheNode2)
Dim session1 As New Session(dataLayer1)
Dim session2 As New Session(dataLayer2)

' Create an object in session1
Dim billySession1 As New Person(session1, "Billy Bott")
billySession1.Save

' Load and check that object in session2
Dim session2Collection As New XPCollection(Of Person)(session2)
Assert.AreEqual("Billy Bott", collection1(0).Name)

' Make a change to that object in session1
billySession1.Name = "Billy's new name"
billySession1.Save

' Reload the session2 collection. The DataCacheNode returns the result
' directly, so the change is not recognized. For the fun of it, do this
' several times. This whole block doesn't query the database at all.
Dim i As Integer = 0
Do While (i < 5)
  session2Collection.Reload
  Assert.AreEqual("Billy Bott", session2Collection(0).Name)
  num1 += 1
Loop

' Do something (anything) in session2. This makes the cacheNode2
' contact the cacheRoot and information about the updated data in 
' the Person table is passed on to cacheNode2.
New DerivedPerson(session2).Save

' Reload the session2 collection again, and now the change is recognized.
' In this case, the query goes through to the database.
session2Collection.Reload
Assert.AreEqual("Billy's new name", session2Collection(0).Name)

Optimistic Locking

The primary purpose of Optimistic Locking is to prevent multiple clients from making modifications to the same object in the database. This is made possible by a field, by default called OptimisticLockField, that XPO adds to all persistent class tables. When objects are read from the database, the optimistic locking field is read together with the object content and stored. When a modification is made to an object in the database, the optimistic locking field is first checked and if it has been changed in the meantime, XPO throws an exception. If everything is okay, the modification is written to the database and the optimistic locking field is updated - incremented normally, as this is an integer field by default.

As you have seen in various places in this article, the Optimistic Locking functionality accounts for a number of additional features in XPO. It is possible to switch off Optimistic Locking by setting the Session.LockingOption property to LockingOption.None instead of the default LockingOption.OptimisticLocking. But be aware that by doing this, you will not only lose the change detection functionality described in the previous paragraph, but also the most of the selective reloading functionality - the only things that will still work are the XPBaseObject.Reload() and the Session.DropCache() methods.

Common Usage Scenarios

To be added for the final article.

Tags
31 comment(s)
Anonymous
Petros Amiridis
Well this article begins to shed some light on the whole session topic. I know that use cases are coming, but just wanted to say that without specific use cases it is difficult to appreciate the theory. For example, I cannot understand the Data Layer caching and when we should use this technique. Is this to be used even for a simple client / server setup where there is no need for remoting?

One of the problems that I have with all this coming from working on simple client server apps for a long time targeting Oracle using a single session for the whole app and using a DataSet and a Query component to cover almost all the communication requirements, is that I cannot easilly understand how to use all these options that you give to us. I mean, the relational way, the only thing I needed to do was MyOracleSession.LogOn and DataSet.SQL.Add("bla bla") and then DataSet.Open and voila. Everything works.

Now XPO comes and gives me the ability to adjust many options depending on what I need to do. But until I am ready to make the adjustments, it would be very helpful if you gave us predefined configurations for typical scenarios we used to follow in our relational eras.
5 April, 2006
Anonymous
Joni
I agree with Petros. Nice article, very informative, but without concrete cases from the real world, it is a bit difficult to apply in practice. I specifically would like to see more concrete information on ASP.NET applications that outlines best practices for common ASP.NET 2 applications using XPO.
And more information on the DAL in general, when to reuse, when to create a new DAL with each session etc.
Also, is the DataLayer Caching a new feature of XPO 2006, or did that sort of cacheing exist earlier?
6 April, 2006
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)
Thanks for your comments, Petros and Joni!

Petros:
About data layer caching - generally the purpose of any caching technology is to provide informaiton directly which would otherwise take longer to obtain. Whether that's of any use in your particular application scenario, I can't judge conclusively. I think that in most client/server applications a performance benefit will arise from data layer caching, while the only setup which definitely cannot benefit from it would be the one where all data in the data store changes so rapidly that cached data is (statistically) never of any use.

Joni:
About data layer reuse, recreation: In most applications it is never necessary to create a new data  layer after initialization.
Data layer caching is a new feature in XPO 2006.
6 April, 2006
Anonymous
Stig Nielsson
Your description of collection reloading matches what I have tested, but do you have any clue why knowledge base article A643 "How the XPO cache works" in the end writes:

"Secondly, the XPCollection.Reload method can be used to refresh only the scope of objects, and it is not applicable to reload the objects' content."

I find this to be incorrect, or have I missed something?
24 April, 2006
Anonymous
Mike M.
Your examples all small looks like small unit tests, where a "using (session...) {..." consrtruction typically used. I really really miss some discussions about how units of work and sessions are used in windows forms applications, where xpcollections typically would be bound to an Xtragrid. In this case, the sessions and collection will live for a long time - a long as the form is instantiated. How does this  affect ones application design?
24 April, 2006
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)
Stig: There's a lot in that KB article which seems a bit unclear to me. We are currently in the process of a major overhaul of the XPO documentation, and we'll get to that article sooner or later. Until then, please note that the article (similar to 90% or more of all others, for now) is marked as relevant to XPO version 1, while the current version 6.1 introduced major changes in this specific area.

Mike: You are right, the article leaves this to the reader for the time being. I can't give you a general answer because this depends on a lot of things: is your grid read-only or read/write, is the data that's bound to the grid handled in other places at the same time, and so on... a general suggestion that we like to make is to use XPViews with XtraGrid (and other UI) binding, but right now this is a read-only solution.
25 April, 2006
Anonymous
Carel Lotz
Oliver

Thanks for the comprehensive article.  It clears out some questions I had.  I have one question surrounding the management of different sessions.  Perhaps this belongs to another category but please bear with me. You mention the following:

> The logical conclusion is simple: one single instance of the object cache, and as a conclusion, one single Session instance, should only ever contain data that is guaranteed to be logically consistent. A good way to be sure of this is to use Session instances liberally, one for each logical "part", each data handling "process" your application performs.

I want to know what is the best way to share the Session instance between the different layers.  If we say that a logical "part" consists of interaction between the UI and Business Layer and Data Layer, what is the best way to share this single Session instance between these layers?  I see that you have added the XpoDefault class to v6.1 and that it has a Session property that is used by default when doing queries etc.  But what about scenarios where I have multiple clients hitting my application concurrently - each having a "unique logical part" and thus unique Session instance?  How do I share each client's unique session instance? Am I right in saying that the XpoDefault.Session cannot be used for this and that I will have to create the Session instance in "ASP.NET Session state" and share it in that way?  This implies that all my code would first need to retrieve the Session instance it needs to work with before doing any XPO related work.

Thanking you in advance
25 April, 2006
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)
Carel,

If I understand you right, you're doing ASP.NET apps, right? In that case, the recommended session handling is to create a session per client request. You should use steps like this:

 * Use Application_Start in global.asax to configure your data layer.
 * Use Page_Init or Page_Load to create a per-request Session or UnitOfWork instance.
 * Use this session instance for all the work the page has to do.
 * Use Page_Unload to call Dispose() on the session object.
 
Of course there might be cases where you actually need to reuse a session instance across client requests, in these cases you should indeed persist the session in the ASP.NET session state. But with regard to your question - in the situation where the session instance would have to be used in various logical layers of your application, I would assume that it could just be passed as a parameter to method calls, from the method you use to customize your pages for incoming requests.
25 April, 2006
Anonymous
Daniel
Hello,

Regarding the last comment where it is mentioned:
* Use Application_Start in global.asax to configure your data layer.
* Use Page_Init or Page_Load to create a per-request Session or UnitOfWork instance.
* Use this session instance for all the work the page has to do.
* Use Page_Unload to call Dispose() on the session object.

Can you provide a code sample on what this looks like?

Thanks,

Daniel
27 April, 2006
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)
Hm... not sure what precisely you're asking. I'm not an ASP.NET expert, but I have used this successfully like this:

In Global.asax:

 void Application_Start(object sender, EventArgs e) {
   DevExpress.Xpo.XpoDefault.DataLayer =
     DevExpress.Xpo.XpoDefault.GetDataLayer(
     DevExpress.Xpo.DB.MSSqlConnectionProvider.GetConnectionString("server", "database"),
     DevExpress.Xpo.DB.AutoCreateOption.DatabaseAndSchema);
 }

In the page source code file:

 public partial class MyPage : System.Web.UI.Page {
   UnitOfWork unitOfWork;
   
   protected void Page_Load(object sender, EventArgs e) {
     unitOfWork = new UnitOfWork();

     // Now do work that requires the session, or configure existing
     // XpoDataSource instances to use this session.
     // You can also commit and dispose the session here if you want.
   }

   protected void Page_Unload(object sender, EventArgs e) {
     unitOfWork.CommitChanges();
     unitOfWork.Dispose();
     unitOfWork = null;
   }
 }

Please be aware that this obviously neglects all error handling and similar things - it's really just the barest necessary construct for data layer and session creation.
28 April, 2006
Anonymous
Andres Kandus
Dear Oliver,

Very nice article. But I would need something more concrete. I am building a multiuser, multisession application using winforms. Which would be the best approache to use the speed of the cache but be sure that everything is thread safe?

Thanks

Andres
8 May, 2006
Anonymous
Francisco Ferreira
Hi Oliver,

I am evaluating XPO at this time. I've found the article very interesting. But i am developing a sample program with XPO and I keep getting "DifferentObjectsWithSameKeyException" everytime I run the application.

Here is my model:

Person N x M Project
Person 1 x N Issue

Check out the code below. What am i missing?

Thanks in advance.
Regards,
Francisco

using (UnitOfWork unitOfWork = new UnitOfWork())
       {        
           XPCollection c = new XPCollection(unitOfWork, typeof(Person), new BinaryOperator("ID","1"));
   
            foreach (Person p in c)
           {
               Response.Write("Person: " + p.Name + " " + p.Email + "<br>");
               foreach (Issue i in p.Issues)
               {
                   Response.Write("Issue: " + i.Name + " " + i.Description + "<BR/>");
               }
               foreach (PersonProject pr in p.Projects)
               {
                   Response.Write("Project: " + pr.key.project.Name + "<BR/>");

               }

                    p.Name = "Chicão " + DateTime.UtcNow;
                   Issue i2 = new Issue(unitOfWork);
                   i2.Name = "issue " + DateTime.UtcNow;
                   i2.Description = "some desc";
                   i2.Creator = p;
                   i2.Save();
                 
                   Issue i3 = new Issue(unitOfWork);
                   i3.Name = "issue " + DateTime.UtcNow;
                   i3.Description = "via p";
                   p.Issues.Add(i3);
                   p.Save();
                         
                   Project p1 = new Project(unitOfWork);
                   p1.Name = "project " + DateTime.UtcNow;
                   PersonProject pp = new PersonProject(unitOfWork);
                   pp.key.project = p1;
                   pp.key.person = p; // pessoa do loop
                   p1.Persons.Add(pp);
                   p.Save();
                   
                   Project p2 = new Project(unitOfWork);
                   p2.Name = "project via person " + DateTime.UtcNow;
                   PersonProject pp2 = new PersonProject(unitOfWork);
                   pp2.key.project = p2;
                   pp2.key.person = p;                    
                   p.Projects.Add(pp2);
                   p2.Save();
                                       
           }

               unitOfWork.CommitChanges();
       }
16 May, 2006
Anonymous
Hrom
Hi Oliver. Nice and useful article!!! Write more about XPO. In my project i have problem similar Francisco Ferreira problem. Can you help us? Thanks a lot!
18 May, 2006
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)
Guys, I'm sorry - this is not supposed to become a general discussion forum or an addition to our support infrastructure. I have looked over the code above and I don't see an immediate problem. Please create a small sample application that reproduces the issue, create an entry in our support center at http://www.devexpress.com/support/center for this and attach your sample. Be sure to include as much information about the problem as possible (for example, additional info about the exception you're getting - without the call stack it's not worth much if the support guy can't reproduce the problem) and we'll be happy to help you out.
19 May, 2006
Anonymous
Hrom
Hi, Oliver. I have a question about Reload function work. If i call next code

_session.Reload(someObject, true);

Agregated objects will reloaded with parent object. But will agregated objects of agregated objects (:)) reloaded too or not? Thanks in advance!
26 May, 2006
Anonymous
Willie Swart
Hi Oliver, Regarding object cache consistency: Would it not be helpful to have an OnDropChache event on XPObject's so that the objects still living in the app will know to reload or discard themselves? This will ensure object cache consistency especially in complex applications.
12 July, 2006
lilo smith
lilo smith

I don't want to anger Dev Ex, maybe this tool is useful in small applications that have very few users, very few data,  and don't require speed.   but based on personal experience you should just learn ADO.net and do regular coding.

13 November, 2007
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)

Hey Lilo,

I'm afraid I just deleted your first comment, which was rather abusive. There's nothing wrong with stating negative opinions, but of course they should be on topic and supported by reasonably detailed evidence.

With regard to the performance issues you are seeing, please feel free to browse our knowledge base and/or contact our support department, and I'm sure we'll be able to help you figure out what the problem is. I can assure you that we have a large number of customers using XPO in scenarios with many users and lots of data.

Regards

Oliver Sturm

14 November, 2007
lilo smith
lilo smith

I understand that you don't want to scare your customers with negative comments, specially here.  This could have been a case of the code becoming too complex and slowing down the app.   Database backend was just fine.  I don't have any details, other than the app waited over 1 minute for each mouse click.

16 November, 2007
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)

This doesn't have anything to do with scaring our customers, but simply with making unsupported claims. To put it bluntly: if you want to come here and badmouth our product, and you even expect us to leave your comments online, then the least you can do is support everything you say by hard facts and lots of details.

I regard this discussion as complete now. Please don't post any further comments on this topic.

Regards

Oliver

16 November, 2007
Anonymous
Kelly

Hello,

Can we use XPO with SQL Compact ans Sql Server.

I want the application to use SQLCE in monopost mode (suggested by Microsoft instead of access) and SQL Server in clinet/server mode.

Thanks

18 November, 2007
Anonymous
Serge Desmedt

I would like to elaborate a little bit more on the DataCacheRoot/DataCacheNode sample code. At a certain point in that code you write:

38 // Do something (anything) in session2. This makes the cacheNode2    

39 // contact the cacheRoot and information about the updated data in    

40 // the Person table is passed on to cacheNode2.  

41 new DerivedPerson(session2).Save( );  

What is "anything"?

I reproduced your sample using SQL Server 2005 and following code:

DataCacheRoot root = new DataCacheRoot(XpoDefault.GetConnectionProvider(strConnection,AutoCreateOption.DatabaseAndSchema));

Session workSession = new Session(new SimpleDataLayer(new DataCacheNode(root)));

Session readSession = new Session(new SimpleDataLayer(new DataCacheNode(root)));

MyPersistentObject newObject = new MyPersistentObject(workSession);

newObject.Property1 = "Prop11.V1Value";

newObject.Save();

XPCollection<MyPersistentObject> myobjectCollection2 = new XPCollection<MyPersistentObject>(readSession);

lstItemsCachingBehaviourBefore.Items.Clear(); // This is a listbox

foreach (MyPersistentObject myObject in myobjectCollection2)

{

   lstItemsCachingBehaviourBefore.Items.Add(myObject.Property1);

}

OtherPersistentObject otherObject = new OtherPersistentObject(readSession);

otherObject.Otherproperty = "someOtherProp11";

otherObject.Save();

newObject.Property1 = "Prop11.V2Value";

newObject.Save();

myobjectCollection2.Reload();

lstItemsCachingBehaviourBefore.Items.Clear();

foreach (MyPersistentObject myObject in myobjectCollection2)

{

   lstItemsCachingBehaviourBefore.Items.Add(myObject.Property1);

}

// If using only one of the following lines, and thus commenting out the other, you will not

//  see the updated version and thus there is no database access. If you use the two lines

//  he does show the updated version and thus there is database access.

otherObject.Reload();

myobjectCollection2.Reload();

lstItemsCachingBehaviourAfter.Items.Clear();

foreach (MyPersistentObject myObject in myobjectCollection2)

{

   lstItemsCachingBehaviourAfter.Items.Add(myObject.Property1);

}

What I find is that if I use the two lines with "Reload()" near the end of the codeblock I have database access, but if I use only one line (either which one), I don't have database access. I'm using the SQL Profiler to monitor the database access.

So my question is: what exactly triggers the database access?

Thanks.

12 March, 2008
Michael Proctor [DX-Squad]
Michael Proctor [DX-Squad]

Still no documentation :(

I cannot for the life of me force a DataCacheRoot to reload an Object from the DataStore, I must be missing something small but can't seem to find it. Have placed SC, Forum and Twitter calls for help, hopefully I will find the answer and it can be helpful to other who may run into this.

Ultimately XPO with XPView make it absolutely brilliant with things such as reporting and also for live data feeds, the data level caching works like a charm (except a little too good as I can't seem to "reload" from the datastore :)), Now in v9.2 there is Async operations as well. Overall XPO is a great platform, just sometimes is let down by a lack of documentation. http://search.devexpress.com is a great tool to find out information, however the documentation should better explain some of these "complex" classes.

Cheers

30 August, 2009
Anonymous
Christopher Fieldhouse

I agree with Michael, I love the concept of XPO but I struggle sometimes to do the simplest things, i.e. I also cannot fathom how to re-load an XPView.

Hope this gets better in the near future.

21 December, 2009
duchino
duchino

Can i change Session.LockingOption=None in runtime.

I need something like "Force Saving" function, User need to have possibility to save the changes even if someone else is editing the object in the same time.

3 May, 2010
Salam
Salam

Hello,

     How to get the Changes before CommitTransaction????????

I wanna see the Changeset.

Regards

Salam

22 May, 2013
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)

Hi Salam,

Check out the GetObjectsToSave() and GetObjectsToDelete methods on the UnitOfWork instance.

Oliver

22 May, 2013
Pham Hung 4
Pham Hung 4

hi who i can help me linQ to xpo

LinQ :

 var bind = from c in cloud_products

                  join s in sale_Prices on c.productid equals s.ProductId

                  where s.CustomerGroupId==-1

                  select new

                  {

                      c.productname,

                      s.SellPrice

                  };

Xpo ?

1 April, 2014
Oliver Sturm (DevExpress)
Oliver Sturm (DevExpress)

Hi,

You don't do this kind of joining with XPO (or indeed most other ORM tools) - why deal with joining yourself if your computer can do it for you? Assuming you have your types in place, you might end up with code that looks like this:

var customerGroup = unitOfWork.FindObject<CustomerGroup>(... <criteria> ...);

foreach (var salesPrice in customerGroup.SalesPrices) {

 // Now do stuff with the info in salesPrice. Members are accessible like this:

 // salesPrice.SellPrice

 // salesPrice.CloudProduct.ProductName

 // ...

}

Thanks

Oliver

2 April, 2014
Anonymous
XPO Team Blog

We have received many comments and questions on our previous blog post. Thank you all for that! In this post, I will describe our updated benchmark based on your feedback:

19 April, 2019
Anonymous
XPO Team Blog

Many times, customers have asked us how we use XPO – or if we even do it at all. We decided to sit down with developers working on and with XPO and conduct an interview, with the goal of answering the most common questions. The result makes for an interesting

19 April, 2019

Please login or register to post comments.