Saturday 9 March 2013

MapGuide tidbits: UpdateFeatures

Here's a long overdue post about a part of the MapGuide API that will no doubt confuse beginners (and some experienced ones too). The UpdateFeatures method of MgFeatureService

What does this method do?

UpdateFeatures is your one stop shop for all you spatial data CRUD needs. Don't let the name fool you, this method does the full (C)reate, (U)pdate and (D)elete portions of the CRUD quartet, the (R)ead portion handled by the SelectFeatures method. If you are going to be manipulating data in your MapGuide Feature Sources, you will need to know how to use this method.

The method signature looks like this:

MgPropertyCollection UpdateFeatures(MgResourceIdentifier resource, MgFeatureCommandCollection commands, bool useTransaction); 

and a new variant that takes MgTransaction objects was introduced in MapGuide Open Source 2.2

MgPropertyCollection UpdateFeatures(MgResourceIdentifier resource, MgFeatureCommandCollection commands, MgTransaction transaction); 

The general process of using this method is:

  1. Identify the Feature Source you are going to be manipulating
  2. Load up one or more manipulation commands 
  3. Execute the UpdateFeatures method
  4. Inspect and/or process the return value
Manipulation commands

You can load up to 3 different types of commands into the MgFeatureCommandCollection
  • MgInsertFeatures: A command that inserts one or more features/records into a specific feature class. Analogous to a SQL INSERT or BULK INSERT.
  • MgUpdateFeatures: A command that updates features in a specific feature class with the given set of property values based on a filter criteria. Analogous to a SQL UPDATE.
  • MgDeleteFeatures: A command that deletes features in a specific feature class based on a filter criteria. Analogous to a SQL DELETE.
The commands are executed sequentially in the order that they were added into the collection

Transactions

If you call UpdateFeatures with useTransaction = true or call the other variant passing in a valid MgTransaction object. The method will execute atomically inside a transaction (created internally or the one you passed in). If any failures occur, the method as a whole will fail and a MgFdoException is thrown on the first failure. 

If you passed in a MgTransaction object, you will have to remember to rollback this transaction after catching the exception. If you call UpdateFeatures with useTransaction = true, a transaction is created internally, and is rollbacked internally in the event of a failure. An MgFdoException is still thrown in this case.

If you call UpdateFeatures with useTransaction = false or call the other variant with a null MgTransaction, the method will not throw any MgFdoException, which will undoubtedly be a great source of confusion.

Exceptions that occur with a non-transactional UpdateFeatures are actually serialized out as part of the return value. Read on to the next section for the nitty gritty.

The key thing to note here is that transactions are not universally supported across the different FDO providers, so it is illegal to call UpdateFeatures with useTransaction = true if the underlying FDO provider does not support transactions. To determine whether transactions can be used, you would have to inspect the FDO provider's capabilities XML (with the GetCapabilities method).

However, if you know what Feature Sources you'll be working with up-front, then you'll most likely know what its capabilities are and you can bypass this programmatic checking of capabilities and just call UpdateFeatures transactionally or non-transactionally.

The return value

The UpdateFeatures method returns a MgPropertyCollection that contains a command result corresponding with each command in the input MgFeatureCommandCollection. The first item in the MgPropertyCollection carries the result of the first command in the MgFeatureCommandCollection, and so on.

This is the mechanism by which individual command results can be inspected in a non-transactional call to UpdateFeatures, as some commands in a MgFeatureCommandCollection may succeed and some may fail.

As a general rule, never call UpdateFeatures and disregard the return value. It is important to inspect the values in this collection to determine whether your call as a whole is a success or failure.

The type of results in this collection depends on the type of command executed:
  • A MgInsertFeatures returns a MgFeatureProperty containing a MgFeatureReader of the inserted features. You should remember to always close these readers as dangling feature readers are a notorious cause of busy resource errors.
  • A MgUpdateFeatures returns a MgInt32Property containing the number of features updated.
  • A MgDeleteFeatures returns a MgInt32Property containing the number of features deleted.
  • An MgStringProperty indicates a failure with the corresponding command and contains the serialized FDO exception message.
Now until MapGuide Open Source 2.4, we actually forgot to include that final point in the API documentation which just happens to be the most important point of them all as it is the way you check if an UpdateFeatures has partially failed in a non-transactional call. Transaction calls throw exceptions, non-transactional calls serialize internally caught exceptions into MgStringProperty objects.

Does it have to be *this* complicated?

That was my initial thought when I looked at this method the first time round. Having to create all these commands, all these collections, all these objects just to insert one feature! The API just feels so clunky with all this complex setup of the UpdateFeatures method parameters.

It is this thought that drove me to provide a simplified set of APIs in mg-desktop to simplify the insert/update/delete of spatial data, by actually providing indiviudal InsertFeatures/UpdateFeatures/DeleteFeatures methods so you don't have to go through this complex monolithic method call. Bringing such convenience APIs over to MapGuide proper is under my consideration   for something after MGOS 2.5.

My thoughts on this API changed however when I looked at the WFS-T specification. I couldn't help but notice that the WFS-T inputs and outputs described in the specification conceptually map quite easily into the inputs and outputs of this "clunky" UpdateFeatures API. So I guess this API was designed this way for a reason: To lay the groundwork for adding WFS-T support in MapGuide for anyone who accepts the challenge ;-)

In closing

So what's the minimum you should get out of this?
  • Call UpdateFeatures transactionally if you can. It simplifies error handling. All you have to do is be on the lookout for MgFdoExceptions
  • Not all FDO providers support transactions. You can programmatically check its capabilities to determine if transactions are supported. But if you know what feature sources you're working with, you would generally know up front whether transactions are available to you or not.
  • Always inspect the return value. If you don't really care, then at the minimum always close any feature readers and log/aggregate any MgStringProperty values in the MgPropertyCollections as these represent the command failures in a non-transactional UpdateFeatures call
  • Yeah the API is somewhat clunky, but IMO it's clunky for a reason. We can definitely make things simpler, but it will have to be a post-2.5 release exercise

No comments: