in
Forums
Blogs
Files
Devexpress.Com
ClientCenter
DevExpress Channel

XPO

eXpress Persistent Objects

July 2007 - Posts

  • XPO - Using the class generation wizard

    Newest versions of XPO (since the 7.2 release) have a wizard that allows you to to create class structures from existing databases. This feature has been requested many times in the past, and we made first steps a while ago by including a tutorial project that supports MS SQL Server (if you're curious, the tutorial is still included - it's called "GeneratingPersistentClasses"). The new wizard takes things to the next level by supporting a variety of additional database systems, so we recommend you start using it instead of the tutorial application. If you give us feedback on the functionality, we will also consider extending it in the future.

    Using the feature is not difficult, but possibly not immediately obvious. In an existing project (currently with the exception of ASP.NET projects - see the end of this post), add a new item to the project and select Persistent Classes X.X from the list of item types (the X.X stands for the precise version of XPO you have installed). You can change the name of the new file to something more useful, but in practice I don't find that very important because I'm going to change the file name anyway once the classes have been generated.

     

     

    Before anything is created, the template brings up a dialog that allows you to select the database provider you want to use, the database itself and any connection parameters, and finally the tables and columns you want to generate classes and properties for.

     

     

    After this step, the class creation process is executed and the result is a new file in your project with all the new classes inside. The wizard currently leaves two important things to do:

    1. Renaming the classes. In traditional databases, tables regularly have weird names that I don't want in my OO class structure. Even if the names aren't weird, they may not be totally suitable. In the screenshot above you can see tables called Categories and Customers (plural). I would rename the resulting classes to Category and Customer (singular), because that makes more sense in an OO context.
      Hint: if renaming sounds like a lot of work, have a look at Refactor! It has a Rename refactoring that makes this extremely easy.
    2. Pull each of the new classes into its own file. Having a single class per file is a convention I recommend very much. Another thing Refactor! can help you with, btw. Why is it not done automatically? No idea, to be honest... but I think the current implementation isn't so bad, because it leaves the decision to the developer and it makes search and replace type operations on the freshly generated source code much easier (for those who don't use a tool like Refactor! <g>).


    Finally, a word about ASP.NET projects. In the current release, the template Persistent Classes X.X (as well as the standard template Persistent Object X.X) is not available if the project type is ASP.NET. The reasons for this are somewhat complicated, you can find an explanation here. We will fix this problem for the next minor update, meanwhile you can import persistent class declarations into a different project and move them over to the ASP.NET one - but do look out for the guidelines given in this post.

  • XPO and ASP.NET - Where to put your persistent classes

    If you have been working with XPO in ASP.NET applications, you may have noticed that the standard XPO Persistent Object X.X (and also the new Persistent Classes X.X) template is not available when the current project is an ASP.NET project. We recently re-evaluated the decision to leave the templates out from that location, and while we found the original reason to be invalid by now, there are still some things to be aware of when working with persistent classes in ASP.NET projects.

    Back in the days of beta testing for VS 2005, we first discovered a problem that appeared when persistent class hierarchies were used in ASP.NET projects. The reason for this issue was that XPO stores certain information about classes inside the database, among those the name of the assembly from which the class was originally read. A lot of the time this information is not used, but when persistent classes are derived from other persistent classes and instances of those derived classes are created and saved, XPO looks up the assembly name information and tries to demand-load the correct assembly.

    In ASP.NET 2.0/VS 2005, a project is built (by default) into several assemblies, and the names of these assemblies include a "key" part that changes regularly after rebuilding. So when XPO stores away the assembly name of a certain class in a first application run, then that information is likely going to be invalid on a later run because by now the name of the assembly has changed.

    When we discovered this issue originally, we found two workarounds. The first workaround is to enable a project level option in VS 2005, called Use fixed naming and single page assemblies (you can find it on the MSBuild Options page). While this makes sure that the assemblies have reliable static names, it has the major disadvantage of creating lots of little assemblies, because every single (web) page now gets its own. Well.

    The second workaround is still our current recommendation, which is to use a separate assembly for your persistent classes -- a standard "class library" project type will do for this. Not only did this solve the issue above, it is also generally a good practice because it allows you to easily create other applications that use the same classes as your web application. Think of admin frontends running on Windows Forms, database update utilities and so on...

    Now, in our recent re-evaluation we found something that was news to us: there is special magic implemented in the .NET framework, so that assemblies with a name that starts with App_Code will be found even if the rest of their name doesn't equal the search string. In other words, a call to Assembly.Load("App_Code.myRandomPostfix") will load the actual App_Code assembly when used from a web application, never mind the fact that the actual assembly obviously uses a different postfix. This magic handling seems to be in place for .NET 2.0 and above. We're not quite sure why we didn't see it earlier, but probably it was added before the final 2.0 release.

    This discovery means that technically your persistent classes are safe when stored inside the App_Code subfolder of your ASP.NET project. We will therefore make the templates available for use in those projects in the next minor update. I would still recommend you use a separate assembly, but at least we can get rid of a somewhat arbitrary restriction now.

  • XPO and ASP.NET - a look at the initialization code

    Mehul got back to me yesterday and asked me to explain the XPO related initialization code that is being used in our new ASPxGridView demo. This is the code in question:

        void Application_Start(object sender, EventArgs e) 
    {
    if(DemoSettings.IsSiteMode) {
    ConnectionStringSettings conn =
    ConfigurationManager.ConnectionStrings["XPOServerMode"];
    if(conn != null) {
    // Code that runs on application startup
    DevExpress.Xpo.Metadata.XPDictionary dict =
    new DevExpress.Xpo.Metadata.ReflectionDictionary();
    dict.GetDataStoreSchema(typeof(ServerSideGridTest).Assembly);
    DevExpress.Xpo.DB.IDataStore store =
    DevExpress.Xpo.XpoDefault.GetConnectionProvider(
    conn.ConnectionString,
    DevExpress.Xpo.DB.AutoCreateOption.SchemaAlreadyExists);
    store = new DevExpress.Xpo.DB.DataCacheNode(
    new DevExpress.Xpo.DB.DataCacheRoot(store));
    object layer = new DevExpress.Xpo.ThreadSafeDataLayer(dict, store);
    Application.Add("XpoLayer", layer);
    }
    }
    }

    Great. Now what's happening here? Let's go over the method line by line:

        if(DemoSettings.IsSiteMode) { 
    ConnectionStringSettings conn =
    ConfigurationManager.ConnectionStrings["XPOServerMode"];

    These are demo specific lines, nothing to do with XPO. In fact, when working with XPO it is a common practice to use helper methods on certain XPO classes to retrieve an auto-generated connection string instead of using a pre-created one. For SQL Server, for example, you'd want a call into DevExpress.Xpo.DB.MSSqlConnectionProvider.GetConnectionString( ... ); to get a connection string. There's nothing wrong with the approach of storing away the connection string, but it's certainly a little easier to break.

        DevExpress.Xpo.Metadata.XPDictionary dict = 
    new DevExpress.Xpo.Metadata.ReflectionDictionary();

    The dictionary of metadata that XPO works with contains important information about the persistent classes in your application, and it is associated with the so-called data layer (an object that implements IDataLayer, if you want to be technical about it). In general, a data layer is only created once per application, so the metadata dictionary is also created just once.

    In a Windows Forms application the code related to configuring the data layer is typically much simpler, probably something like this:

        XpoDefault.DataLayer = XpoDefault.GetDataLayer( ... );

    In ASP.NET, things are not quite as simple because we have to deal with the fact that we're working in a multi-threaded environment. If you take a little peak ahead in the complete code of the Application_Start method above, you will see a class called ThreadSafeDataLayer being instantiated. This specific implementation of IDataLayer can work in multi-threaded applications, but it requires a metadata dictionary that is stable during the application's lifetime. A data layer in a Windows Forms (single threaded) application would be extended during the application run, depending on the classes that are being used. This is not allowed to happen in the ASP.NET multi-threaded scenario, and that's why we have to create and fill the metadata dictionary manually, and before creating the data layer.

        dict.GetDataStoreSchema(typeof(ServerSideGridTest).Assembly); 

    This line does the work: it needs an assembly reference (or several actually, it is possible to pass in more than one assembly reference at once), finds all the metadata in that assembly and adds it to the dictionary. To get to the correct assembly reference, one of the persistent types in the application is used to find the reference dynamically. This is an arbitrary persistent type in your application -- it looks a bit a hack, but it's one of the easiest ways of getting hold of such a reference.

        DevExpress.Xpo.DB.IDataStore store = 
    DevExpress.Xpo.XpoDefault.GetConnectionProvider(
    conn.ConnectionString,
    DevExpress.Xpo.DB.AutoCreateOption.SchemaAlreadyExists);

    Now we are ready to create a connection provider object, which is an object that implements the interface IDataStore. This is basically the DB specific driver that XPO needs to communicate with your backend of choice. The XpoDefault.GetConnectionProvider( ... ) method analyzes the connection string to find out which type of connection provider it needs to create. For connection strings that are retrieved from the XPO helper methods (see above), there's a special flag inserted into the connection string that simplifies identification. In the ASPxGridView demo, the connection string is read from the Web.config file, but it still contains the flag. Here's the string (wrapped):

        XpoProvider=MSSqlServer;data source=(local);user id=servermodeprojects;
    password=smp$;initial catalog=ServerModeGridProjects;Persist Security Info=true

    The first parameter XpoProvider tells the GetConnectionString method that it needs to instantiate the connection provider for MS SQL Server in this case. If your connection string doesn't have such a flag, there are heuristics in that method trying to recognize the string nevertheless, but this may not work totally reliably. You should either retrieve the connection string from one of the helper methods or make sure the flag is in there if you store the complete connection string in a config file.

    Finally, there's an enum AutoCreateOption being passed into that method. This tells XPO how to handle the database schema - SchemaAlreadyExists says that the schema shouldn't be created, because it is expected to be there already. There are several other alternatives documented in the XPO product documentation.

        store = new DevExpress.Xpo.DB.DataCacheNode( 
    new DevExpress.Xpo.DB.DataCacheRoot(store));

    This line wraps the IDataStore in instances of DataCacheRoot and DataCacheNode. DataCacheNode implements IDataStore itself, so the result of the operation can be used anywhere a "normal" connection provider can also be used. The purpose of the wrapping is to establish data layer caching, as described in this technical article.

        object layer = new DevExpress.Xpo.ThreadSafeDataLayer(dict, store);

    With the metadata dictionary and the connection provider in hand, we can finally create our thread-safe data layer. I don't know why the person who wrote the demo used object as the type -- it should really be something explicit, must likely IDataLayer.

        Application.Add("XpoLayer", layer); 

    This line of code stores the data layer away for later use. Apart from the ASP.NET specific approach used here, there's also a mechanim built into XPO to set a default data layer: the static XpoDefault.DataLayer property. The main difference is that if XpoDefault.DataLayer has been set, it is no longer necessary to pass in an explicit data layer reference when constructing instances of Session or UnitOfWork. Of course there's the general argument that this works only as long as just one data layer is being used in an application, but that is really very common -- as I hear, the approach was chosen in this case becauses use of the Application class for this kind of thing is regarded as a good practice in ASP.NET applications. Wouldn't necessarily convince me, but there you go.

Copyright © 1998-2008 Developer Express Inc.
ALL RIGHTS RESERVED