in
Forums
Blogs
Files
Devexpress.Com
ClientCenter
Support Center
DevExpress Channel

XPO

eXpress Persistent Objects

August 2008 - Posts

  • Using SQL Server 2008 spatial data from XPO

    Boris posted me some code that I used to play around with, in order to make XPO work with SQL Server 2008 spatial data. It is possible to make it work, quite easily actually, but there are a few things that need to be considered, and where more than one solution is possible. Let's see.

    First, we need a class to store data in the client application. This is the one I'm using:

      public class PolygonData: XPObject {

        public PolygonData(Session session)

          : base(session) { }

     

        private string name;

        public string Name {

          get { return name; }

          set { SetPropertyValue("Name", ref name, value); }

        }

     

        SqlGeography polygon;

        [ValueConverter(typeof(GeographyConverter))]

        [DbType("geography")]

        public SqlGeography Polygon {

          get { return polygon; }

          set { SetPropertyValue("Polygon", ref polygon, value); }

        }

      }

    This is already the first thing that probably needs some thought. In order to use the SqlGeography class as the property type, it is necessary to have a reference to the assembly Microsoft.SqlServer.Types. That assembly is not currently part of the .NET Framework, but it is installed together with SQL Server. As the SqlGeography class has several pieces of useful functionality (and there are other useful classes in the same assembly), it seems weird that they aren't available in the "standard client". Apparently Microsoft prepared these classes for server-side use, and obviously this makes some sense if you're going to use .NET on the server in some way, but still... there are a number of blog posts out there on the topic (try searching SqlGeography client side, for instance), and at least one of them points to a future solution that involves a separate redistributable which might become available at some point. For the time being, it would probably be possible to install the assembly in question on the client manually -- but please note that I haven't looked into legal issues involved with this, so be careful!

    If, for whatever reasons, you don't want to go this way, you would have to create your own client-side class to represent the spatial information. The other approaches described in this post would still be quite similar though.

    Now, as you can see, the property for the Polygon in my code is decorated with two attributes. The DbType attribute makes sure that the field gets created with the correct type "geography" in SQL Server. The ValueConverter attribute is used to convert the geography into the string format needed in SQL. Here it is:

      public class GeographyConverter : ValueConverter {

        public override object ConvertFromStorageType(object value) {

          if (value is string)

            return SqlGeography.Parse((string) value);

          else return value;

        }

     

        public override object ConvertToStorageType(object value) {

          return value == null ? null : ((SqlGeography) value).ToString( );

        }

     

        public override Type StorageType {

          get { return typeof(string); }

        }

      }

    The ConvertFromStorageType function implementation is a bit weird, since it simply skips the conversion if the object that's getting passed in is not a string. This is perhaps not the most secure way of implementing this, but it's good enough for the purpose of the demo. The reason I'm expecting objects that might not be strings is explained further down and it has to do with performance -- depending on the decision you make at that point, you might implement this function in slightly different ways.

    To create a bit test data, I'm using the following helper function in my code (the SqlGeographyBuilder is another class from Microsoft.SqlServer.Types):

        private static SqlGeography CreatePolygon( ) {

          SqlGeographyBuilder builder = new SqlGeographyBuilder( );

          builder.SetSrid(4326);

          builder.BeginGeography(OpenGisGeographyType.Polygon);

          builder.BeginFigure(55.36728, -2.74941);

          builder.AddLine(55.40002, -2.68289);

          builder.AddLine(55.39908, -2.74913);

          builder.AddLine(55.36728, -2.74941);

          builder.EndFigure( );

          builder.EndGeography( );

          return builder.ConstructedGeography;

        }

    With this in place, I can create and store some data:

          using (UnitOfWork uow = new UnitOfWork( )) {

            new PolygonData(uow) {

              Name = "Test 1",

              Polygon = CreatePolygon( )

            }.Save( );

            uow.CommitChanges( );

          }

    If you are following along and you've built your own sample with these code snippets, you will be able to execute the sample at this point. It will get a table created in SQL Server and the spatial data inserted. Wonderful!

    Finally, of course we want to read data back from the database. In my sample, I'm using this simple piece of code to do it:

          using (UnitOfWork uow = new UnitOfWork( )) {

            var polygons = new XPCollection<PolygonData>( );

            foreach (var polygon in polygons) {

              Console.WriteLine(polygon.Name);

              Console.WriteLine(polygon.Polygon);

            }

          }

    If you try to run this, you will see an exception though. The reason for that is that there are types here which don't match up. The SQL Server client library, somewhat confusingly, returns an object that is actually of type SqlGeography, whereas our type converter defines a storage type of string ins StorageType property. As a result, a function called ReformatReadValue is called on the connection provider we're using (MSSqlConnectionProvider by default), and that function attempts to use the .NET Framework standard Convert.ChangeType function to convert a SqlGeography into a string. That function in turn expects the object to implement IConvertable, and throws an exception because it doesn't do that. Phew.

    So what's the solution to this problem? Well, making the ReformatReadValue function do the conversion of SqlGeography into string, that's one solution. Let's derive a connection provider and override that function:

      public class GISProvider : MSSqlConnectionProvider {

        public GISProvider(IDbConnection connection, AutoCreateOption autoCreateOption)

          : base(connection, autoCreateOption) {

        }

     

        protected override object ReformatReadValue(object value, ReformatReadValueArgs args) {

          if (value != null) {

            Type valueType = value.GetType( );

            if (valueType == typeof(SqlGeography) || valueType == typeof(SqlGeometry))

              return value.ToString( );

          }

          return base.ReformatReadValue(value, args);

        }

      }

    Now we need to make sure this is the provider we're using, instead of the standard MSSqlConnectionProvider. We can do this with an initialization line like this:

          XpoDefault.DataLayer =

            new SimpleDataLayer(new GISProvider(

              new SqlConnection("data source=.;integrated security=SSPI;initial catalog=XPOSql2008Spatial"),

              AutoCreateOption.DatabaseAndSchema));

    If you're following along, try running your application again, with the reading code in place, and you should see the geography information read back to the client and shown on the console.

    There's one problem with this code, and I'm sure you have noticed already: we are getting back a SqlGeography instance from the database client code, and this gets passed in to the ReformatReadValue method in the XPO infrastructure. There it is converted into a string. Then it gets passed into the configured value converter for the Polygon property, and gets converted in a SqlGeography instance. Sounds suboptimal, doesn't it? Yeah...

    There is a solution to this, but it's not entirely perfect. It is possible to simply ignore what ReformatReadValue wants us to do and just not convert the object (leave off the ToString() from the value the method returns). Then the SqlGeography type object will be passed in to the value converter, which needs to be implemented to ignore the fact that this is not really a string it receives. Remember, I already pointed this out above - if you want to go this "better performance" way, your value converter will have to be able to deal with the fact that it might receive objects to convert that have actually already been converted.

    I'm only describing this solution instead of showing the code, because it's not really something I want to recommend. This solution neglects the contracts of the XPO infrastructure, and that is of course a bad thing when changes are made in the future. It might also be a problem if the data has to be serialized after being read from the database - having it in string format is what XPO normally assumes, and that works well with all types of serialization. I haven't made any real tests, so I can just say I don't know precisely what will happen if SqlGeography instances are sent across the wire using Remoting, XML Web Services, WCF or other frameworks. So - if you're interested in the performance gain, Remoting and so on are perhaps not a concern of yours (it seems unlikely anyway that Remoting and the perf gain from a few saved string conversions are *both* important to you), then it shouldn't be hard to use this approach I described. Please note though that you should take extra care to have relevant unit tests in place, so that changes in future XPO versions don't catch you out.

    Overall it seems a bit weird the way this feature has been implemented. The client side seems to convert the spatial data into the original type automatically, pretty much whether you want it or not - at least that's how I understand it at this point. Since I push in string data through SQL in order to store the information, it would seem logical to be able to retrieve the string data only, and make my own decision about converting it back into object data, and specifically about the right point in time to do this. Perhaps it fits in somehow with Microsoft's decision not to make Microsoft.SqlServer.Types available in the client by default. Or perhaps there's something I missing right now in this regard :-)

    Anyway, have fun! Here's the complete code of the sample I created: XPOSql2008Spatial.zip (4201 bytes)

  • XPO and FILESTREAM - update

    Yesterday I wrote about using the new SQL Server 2008 FILESTREAM feature with XPO, and this morning Boris from our XPO team sent me an IM going "na na na, I found an easier way to do this". Okay, maybe he didn't quite put it like that :-) Anyway, here's the code he sent me:

      public class Something : XPObject {

        public Something(Session session)

          : base(session) { }

        public override void AfterConstruction( ) {

          base.AfterConstruction( );

          streamId = Guid.NewGuid( );

        }

        private string strVal;

        public string StrVal {

          get { return strVal; }

          set { SetPropertyValue("StrVal", ref strVal, value); }

        }

        private byte[] streamData;

        [DbType("VARBINARY(MAX) FILESTREAM")]

        public byte[] StreamData {

          get { return streamData; }

          set { SetPropertyValue("StreamData", ref streamData, value); }

        }

        private Guid streamId;

        [DbType("uniqueidentifier ROWGUIDCOL UNIQUE NOT")]

        public Guid StreamId {

          get { return streamId; }

          set { SetPropertyValue("StreamId", ref streamId, value); }

        }

      }

    This code removes the need for any separate structural updates, which is of course very convenient. Now, in my previous post I had already mentioned I'd been playing with ways of using the DbTypeAttribute, and my solution had looked almost exactly the same - only I hadn't been aware that the UNIQUE keyword could be used in that precise way, and so I wasn't able to get the necessary index created on the ROWGUIDCOL field. Boris' code takes care of that now.

    Is this preferable? I'm not entirely sure. I had one other reason to decide not to post my own similar code in my previous article, and that was the fact that the DbType parameter for the StreamId field looks very much like SQL injection. As you can see, it ends in "NOT", and that is to combine with the "NULL" that XPO inserts into the SQL code anyway, so the field ends up being "NOT NULL". Hm... I guess this is SQL injection, seeing how the text from the DbType is inserted into the DML code without any checks. Then again, that's the exact point of having a DbType attribute in the first place - the ability to use types that XPO isn't aware of - and of course it's compile time data that is injected. In the end, that code has the same effect as the other code I published previously. It is important to point out that while this may look similar to code used for SQL injection attacks, it's quite a different thing really, and there doesn't seem to be a way of exploiting this at runtime.

    So is it preferable? I don't know - I guess it's more convenient to use in some cases. It's also less dynamic. With the code I published previously, it would be possible for the initialization code to determine whether SQL Server 2008 is being used, and to make the necessary changes only if it is. By decorating the field with the DbType attribute, the class can only be used with SQL Server 2008. I guess in the end everybody will have to decide for themselves.

    Update: Another idea from one of our untiring XPO team members - Alex this time - you could use an XPCustomObject and establish a Guid type primary key. This will be marked with ROWGUIDCOL automatically (we introduced this originally to make the databases replication friendly) and of course it will have the required unique index. I actually had a similar idea early on, but I wasn't going to do this because I didn't want to mix the requirements of the FILESTREAM feature with other structure... Of course you'll still have to get the FILESTREAM type set correctly and using DbType on that one still makes your structure specific to SQL Server 2008. I guess my own overall decision might still be to make the modifications through SQL/DML statements, but of course (as Alex also pointed out) I should pay attention whether my table has a Guid type primary key perhaps - in which case SQL Server will not let me establish a second column with the ROWGUIDCOL attribute set.

    Finally, I was asked to show how initialization code can be used in the context of an XAF application. That's actually very simple, since XAF has its own mechanism of updating database structure and content with new versions of applications. That mechanism can easily be used to call some structure modification code. It's documented in the XAF help here.

  • XPO and SQL Server 2008 FILESTREAM support

    In a recent forum post, Reinhold Erlacher asked me whether we supported the new FILESTREAM feature in SQL Server 2008 yet. I was not familiar with the feature, not being a SQL Server expert really, but I decided to have a look.

    First, I had to set up SQL Server to actually support the feature in question. A well documented process, but a bit weird nonetheless - why do they introduce features that are only available after jumping through so many additional hoops? Ah well... Here are the instructions on MSDN - there's a lot of confusing information around in blogs, apparently from pre-release versions of SQL Server 2008, so be sure to use these to get it to work with your database.

    Now, first I played around a bit to try and get XPO to create a database structure automatically that would be compatible with the FILESTREAM feature. That is not currently possible, as it turns out, since there are a few requirements:

    • The database must have a file group for the file streams
    • A table that has associated file streams must also have a column marked ROWGUIDCOL
    • The ROWGUIDCOL column must have a unique index applied

    Finally, of course, the blob column that will store the stream data must be marked FILESTREAM. We don't support any of these things directly - i.e. through a built-in feature - in XPO, and I don't think it's likely that we will. Fortunately, it's quite easy to make the necessary changes either manually (configuring the file group in SQL Server Management Studio) or automatically (using a bit of SQL code that's executed after the default schema creation process from the XPO application). The process shouldn't be hard to integrate with a typical deployment scenario, and of course we can't help the fact that Microsoft have made it quite hard to benefit from the new feature.

    I'm using this basic persistent class for my demo:

     

      public class Something: XPObject {

        public Something(Session session)

          : base(session) { }

     

        public override void AfterConstruction( ) {

          base.AfterConstruction( );

          streamId = Guid.NewGuid( );

        }

     

        private string strVal;

        public string StrVal {

          get { return strVal; }

          set { SetPropertyValue("StrVal", ref strVal, value); }

        }

     

        private byte[] streamData;

        public byte[] StreamData {

          get { return streamData; }

          set { SetPropertyValue("StreamData", ref streamData, value); }

        }

     

        private Guid streamId;

        public Guid StreamId {

          get { return streamId; }

          set { SetPropertyValue("StreamId", ref streamId, value); }

        }

      }

    I experimented briefly with the DbTypeAttribute and I had some success with it, but since custom SQL code was still required, I decided to make all relevant changes in one place instead of spreading them out. My test application is this:

     

      class Program {

        static void Main(string[] args) {

          XpoDefault.DataLayer = XpoDefault.GetDataLayer(

            MSSqlConnectionProvider.GetConnectionString(".", "XPOFileStream"),

            AutoCreateOption.DatabaseAndSchema);

     

          // This sets up the DB structure. Of course we would only do this

          // once in a real app. Since we're boring in this sample, we're just

          // going to kill the db and go from scratch.

          using (UnitOfWork uow = new UnitOfWork( )) {

            uow.ClearDatabase( );

          }

          CreateDBStructure( );

     

          // Create some test data

          using (UnitOfWork uow = new UnitOfWork( )) {

            Something something = new Something(uow);

            something.StrVal = "Entry";

            something.StreamData = UTF8Encoding.Default.GetBytes("Stuff in my stream");

            uow.CommitChanges( );

          }

          // Read the object back and show the stream data

     

          using (UnitOfWork uow = new UnitOfWork( )) {

            Something something = uow.FindObject<Something>(null);

            Console.WriteLine("Stream says: " + UTF8Encoding.Default.GetString(something.StreamData));

          }

        }

     

        private static void CreateDBStructure( ) {

          // Get the basic db structure created

          using (UnitOfWork uow = new UnitOfWork( )) {

            uow.UpdateSchema(typeof(Something));

          }

     

          // Apply schema modifications

          Action<string> execute = CommandExecutor(XpoDefault.DataLayer.Connection);

          execute("alter table Something alter column StreamId uniqueidentifier not null");

          execute("alter table Something alter column StreamId add rowguidcol");

          execute("alter table Something add constraint streamid_unique unique(StreamId)");

          execute("alter table Something drop column StreamData");

          execute("alter table Something add StreamData varbinary(max) FILESTREAM");

        }

        static Action<string> CommandExecutor(IDbConnection connection) {

          return delegate(string command) {

            SqlCommand sqlCommand = new SqlCommand(command, (SqlConnection) connection);

            sqlCommand.ExecuteNonQuery( );

          };

        }

      }

    You can download the complete sample from here (VS 2008 SP1): XPOSQL2008FileStream.zip (4267 bytes)

    Note: If you try to run the sample out of the box, you will be in trouble because the database is not configured to support file streams! You should therefore create a database called "XPOFileStream" manually and set it up to support file streams correctly (again, as described here), and then run the sample.

  • XPO Publication Service and XAF

    I was involved in some debugging action this morning, regarding my XPO Publication Service. Please read this post (and others linked from there) to find out the basics about that service.

    The problem in question was that a customer was receiving a weird Remoting exception when trying to connect to a data store published through the publication service. The first test I did was to change the dependencies in the service to 8.2, build it, and run my functional tests on it - all of those worked fine.

    So I set out to create a simple client that would attach itself to a data store published through the Publication Service. At this point, I think a little excursion would be good. I have noticed that many people have the weirdest ideas of how to connect to a published service from XPO. Often they involve a line like this:

    IDataStore dataStore = (IDataStore)Activator.GetObject(typeof(IDataStore), 
      @"http://tcp:2635/something.rem");
    

    Now, this is not wrong, but it's also not necessary. Retrieving a reference from the Activator is one way of interfacing with the .NET Remoting infrastructure on the client side. But - that code is already contained in the XPO libraries, so all the gory details of using Remoting are hidden. You can easily use XPO to get a connection provider (= data store) created directly, like this:

    XpoDefault.GetConnectionProvider("tcp://localhost:9999/something.rem", 
      AutoCreateOption.DatabaseAndSchema)
    

    Or, even better, you can get a data layer set up directly, like this:

    XpoDefault.DataLayer = XpoDefault.GetDataLayer("tcp://localhost:9999/something.rem", 
      AutoCreateOption.DatabaseAndSchema);

    What this means is really that a connection URL for a data store published through Remoting can be used in very similar places to any other connection string. XPO will figure it out for you.

    On a related note, guess how to set up an XAF application to connect to a data store publication? That's right, just use the URL as a connection string. For instance, put this into your app.config:

    <add name="ConnectionString" connectionString="tcp://localhost:9999/something.rem" />
    

    Right, end of excursion. Now, I'd created that little client app and, in contrast to all the lines above, I was using HTTP, not TCP, for the connection. The publications.xml file for the Publication Service had this Channels section:

    <Channels>
      <xpopub:HTTPTargetChannel>
        <Port>9999</Port>
      </xpopub:HTTPTargetChannel>
    </Channels>
    

    I was quite surprised to find this, but I got the same Remoting exception as described in the support center issue.

    One idea I quickly had was that the problem might be related to that other weird issue I had encountered at some point when using HTTP for a publication. That issue is described in this post on my personal blog, and it's certainly not irrelevant if you use Remoting or some other protocol to publish things through HTTP. So go to this article and have a look if you're interested.

    Anyway, that wasn't it, since the error messages were different. Nevertheless, I was able to solve the problem by switching to TCP. Probably a good idea anyway, unless you specifically need to use HTTP for firewall reasons (though it will forever remain a mystery to me why people don't have a problem tunneling unknown content through HTTP, while being scared $$(*&less by the same data on a different port). So I changed my publications.xml to this:

    <Channels>
      <xpopub:TCPTargetChannel>
        <Port>9999</Port>
      </xpopub:TCPTargetChannel>
    </Channels>
    

    I changed all the URLs I was using to tcp://, as shown in the code lines above, and then it worked. Great. A quick test with the more complex XAF Main Demo application showed that this worked as well. Fine.

    So, back to the "why"... why is everything fine with TCP, but not with HTTP? Well, the answer came to me after some discussion with our support guy Dennis: the serialization formats don't match. Yeah. Let me say that again - the serialization formats don't match. Why doesn't Remoting throw a TheSerializationFormatsDontMatchException? I don't know. Anyway, that's what's happening.

    The XPO Publication Service defaults to binary encoding, even for the HTTP channel. There's a property called UseBinaryEncoding on the channel, which can be changed through the config file, but it defaults to true. The XPO library, on the other hand, defaults to SOAP encoding when an HTTP channel is used. So, back to the publications.xml with the HTTPTargetChannel, and then insert this line before the code that establishes the connection to the server:

    ChannelServices.RegisterChannel(new HttpChannel(null, 
      new BinaryClientFormatterSinkProvider(), null), false);
    

    Credit for this line goes to Dennis - he had found out that this makes things work, though he wasn't exactly sure why. Here's why: because the HTTP Channel is configured to use binary encoding and thereby becomes compatible with the way the Publication Service works. The XPO library doesn't register the HTTP Channel again, since it's already there, and it doesn't change the registration either. Fine.

    Now, several questions come up here. First, why are the Publication Service and the XPO defaults incompatible? I don't know. It's certainly weird and I suspect we changed something at some point, on one or both ends. It's been a long time since I wrote the Publication Service and being a pet project, it's perhaps not tested well enough - still, since we're talking about default settings here, it seems that this could have been found earlier if it had been like that forever. I don't know.

    Second, is it possible to use SOAP encoding? Yes and no, oddly enough - another point where I'm not sure where a problem comes from. It is easy to switch the Publication Service to using SOAP, like this:

    <Channels>
      <xpopub:HTTPTargetChannel>
        <Port>9999</Port>
        <UseBinaryEncoding>false</UseBinaryEncoding>
      </xpopub:HTTPTargetChannel>
    </Channels>
    

    The problem is, when I tried this, I got another weird exception coming up, saying that the SOAP formatter isn't able to deal with generic classes being serialized. Okay then... hm. The point where we changed XPO to have generic classes involved in the inheritance hierarchy is a really long time ago, so it's probably been like that since then. Interestingly, while I don't know how many people use Remoting with HTTP/SOAP, I'm pretty sure there are some people using XML Web Services, which use HTTP/SOAP as well. The answer to that is that the SOAP formatter for Remoting isn't the same as the one for XML Web Services. And they are both different from the one being used for WCF, if I'm not completely mistaken. Trust Microsoft to find ways of making complex things more confusing.

    Summary of all the odd stuff above

    If you've stopped reading my ramblings a while ago, here's the summary of important points:

    • The reason for the issue is that the XPO Publication Service and the Remoting client code in the XPO libraries use different formatter defaults for the HTTP channel. We should look into making some changes so this would be compatible automatically.
    • Using TCP instead of HTTP makes things work. This is probably the best idea for many cases, unless HTTP is absolutely required.
    • Using binary formatting with HTTP also makes things work. An additional line of code is required for this purpose, but inserting this into application initialization code shouldn't be a problem in most cases.
    • Using SOAP formatting with the Publication Service is easy, but it doesn't actually work for technical reasons in the Remoting SOAP formatter. We might look into this in a bit more detail to see if there's anything we can do about this.
    • Using the HTTP/SOAP combination is perfectly possible with XML Web Services as well as WCF, to my knowledge. XML Web Services are not self-hosting, so they can't really be supported by the XPO Publication Service. WCF can be self-hosting and it would be possible to extend the Publication Service to support it directly. The Publication Service can be extended externally, i.e. without changes to its own code base. With or without support in the Publication Service, there are plenty of articles on this blog that show how to tunnel through Web Services and WCF.

    Update:

    I've talked to the XPO team, and they have tracked down the problem with the SOAP formatter. Turns out this should actually work - so we regard it a bug and it will be fixed. Hang on... just now an IM tells me that the fix has been checked in internally. Will be in the next minor release.

    The thing with the default behavior of the XPO Remoting code is that it uses defaults the .NET Framework provides, which is a good thing of course. I guess it would make sense to change the default for the Publication Service to use SOAP instead of binary... I'll probably do that soon.

  • ANN: DXCore XPO plugin 1.2.0.1

    Version 1.2.0.1 of the XPO plugin is now available, compiled against DXCore 3.0.8. Please download it here:

    CR_XPOFieldSync-1.2.0.1.zip (20489 bytes)

    As always, if you're not familiar with the purpose of the XPO plugin, please read this description of the "Simplified Criteria Syntax" feature.

    I have recently mentioned to a few of you, through the Support Center, that I was considering retiring the plugin soon. LINQ has been mentioned several times as the query technology of choice, and I would like to make that point again. The "Simplified Criteria Syntax" feature in XPO was originally introduced as a way to write queries more easily, and specifically with the fact in mind that LINQ was at that time still about two years away from general availability. Now that LINQ is available and well supported in XPO, we do recommend you use it to write queries in code. Since LINQ is a very general-purpose technology, there will be cases where our own criteria system can do things that can't be represented in a LINQ expression. But for the vast majority of cases, there's no better way of writing queries in code today.

    Nevertheless I've changed my mind about deprecating the XPO plugin. The reason is that I've learned how many other things, apart from querying, users do with the plugin and the code it creates. Generally speaking, having a reference to a certain field in a persistent class - which is what the XPO plugin creates - is very useful in a variety of cases that don't have to do directly with querying, and so cannot be covered by LINQ. So for now, I will keep supporting the XPO plugin as well as time allows.

    For version 1.2.x, I have restructured certain things about the way the plugin works. In prior versions, the plugin was trying to be rather clever about the code generation/modification process, for instance by removing only those elements that weren't needed anymore after a code change, or inserting only those that were new. This has proved extremely tricky over time with regard to the interaction with the DXCore and the code parsing it does, since the plugin needs to know exactly what the current state of the code is while it executes several deletions and insertions, and the developer possibly keeps on typing and making more changes. The major structural change in this version is that the plugin always creates the entire code block and either inserts it or replaces the previous version with it. This does come with its own problems - see below -, but it makes things much more predictable and less dependent on details of the inner workings of the DXCore.

    Here's a summary list of changes in this version:

    • Restructured lots of the plugin to solve some long-standing problems
    • Manual execution of sync is now possible - configure a key binding for the SyncXPOClass action
    • Fixed B95475 - (a part of the) Fields class wasn't being created
    • Fixed B93229 - certain persistent property types weren't recognized correctly
    • Fixed a problem where the code region was created in the wrong place, or not at all, when using nested persistent classes
    • Fixed CB56295 - handling of NonPersistent on base classes
    • Implemented CS49600 - optional inclusion of NonPersistent members - see option dialog
    • Implemented AS13477 - toggle automatic sync using a key binding - configure a key binding to the ToggleXPOAutoSync action
    • The "Global Off" option has been replaced by the "Automatic Sync" option, which is Off by default

    Note the last entry, please. If you like to use automatic synchronization, you have to go to the options dialog after installation of the latest version and switch it on. By default, the plugin doesn't use automatic sync anymore. From my own experience during testing, the manual sync is quite comfortable, although of course there's a risk of forgetting to sync.

    There are currently three known problems with this plugin version:

    • The forever outstanding issue CB8627. Unfortunately, I still don't have a solution to this. The DXCore parser is apparently unable to tell me if there's a critical structural problem in the source code I'm looking at, and so the sync process creates code in the wrong place, or doesn't recognize that there's already a region it should replace. I'm still hoping to be able to fix this problem eventually. In this version, one possible workaround is to use manual sync - just make sure your project is valid (hit Shift-Ctrl-B to build, for instance) and if that's all fine, trigger a manual sync.
    • Due to some Visual Studio implementation oddities, (a) the newly created code region is always expanded when it is inserted and (b) it happens that it doesn't collapse automatically. There's code in place that tries its best to collapse the region and most of the time it works, but when you're tying furiously while sync kicks in - especially when you're hitting Return as well to create new lines - the region might stay open. This doesn't have any technical implications whatsoever, it just doesn't look nice. I'm afraid there will probably not be a solution to this.
    • Finally, I've seen the need to reintroduce the snag of the dead redo list in Visual Studio, as previously reported in B91445 and CB54533. Okay okay, I'm joking, I didn't do it on purpose. But I had to fiddle around with the undo handling after making those structural changes explained above, and for some reason everything now works differently... difference between using immediate and queued edits in DXCore, in case you're curious. I have now managed to make undo work really nicely - it simply skips the automatic changes and undoes (is that a word?) whatever you actually did right before - but Visual Studio still kills the redo list... opening and then discarding an undo unit apparently does that, and I doubt there's a way around it. My apologies to everybody who loves redo - personally I hardly ever use it, so I hope it's not going to be the worst kind of showstopper. Oh yeah, and I should mention that this problem applies only to automatic sync! So using manual sync is once more a workaround if you really want redo functionality.

    So that's it - please try the plugin and let me know if you find any problems. I apologize in advance for all issues I've introduced now - when making rather major changes like I did, breaking something also becomes more likely.

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