Delete Newly Created Feature

1840
9
02-15-2017 11:51 AM
ChadYoder1
Occasional Contributor II

I'm experiencing an issue trying to delete a feature which was just created.  This is all while working online using iOS (haven't tested yet on Android or UWP).

The scenario is this:

  • User is viewing a list of maintenance records and wants to add a new one.  We transition to a new page to allow the user to add the new record, click save and the feature is created and added to the model which is bound to the previous page.
  • User returns to the previous page and sees the new record, but then decides they'd rather not add, so they select the record and attempt to delete it.
  • I've tried 3 ways to get this to work, but none have worked.  It seems like the feature is "stuck" as the local copy, not the actual feature that was added to the server.
  • Feature.RetryLoadAsync() does nothing to change this behavior.

  1. Using FeatureTable.DeleteAsync() throws an error:  "Unable to complete operation".
  2. Next thought was to use the ObjectId and query to find the feature.  Well, this is a negative number (which makes sense, but I'm online, so why isn't it the real one?)
  3. Next try the GlobalId.  Well...the GlobalId is created when CreateFeature() is called.  But, if you use this to query to find the record on the server so you can delete it, it won't find it, because the server record has a new GlobalId.

If I query the table to get all of the records again, I can then find the one I want using other attributes and delete it, but this only works after using another query.  Any way around this?  Should RetryLoadAsync() rehydrate the feature as it is on the server?  Seems like there needs to be a way to get the server feature after it is created, but I don't see any way to do that.

Any ideas or thoughts would be appreciated.

9 Replies
JenniferNery
Esri Regular Contributor

Hi Chad,

Did you call AddFeatureAsync? "Unable to complete operation" sounds like a server error. I was only able to reproduce this error on DeleteFeaturesAsync if I delete a feature that is not already on the table. It looks like delete operation can trigger an incomplete query in this case, since objectId attribute is not set - which is likely the bug - but would not have happened if feature was added to the table. Also a negative objectId when you have not called ApplyEditsAsync yet to submit your edits to the service.

So in the code below if you comment out AddFeatureAsync, you'll get "Unable to complete operation".

            try
            {
                var location = await MyMapView.SketchEditor.StartAsync(SketchCreationMode.Point, false) as MapPoint;
                var layer = (FeatureLayer)MyMapView.Map.OperationalLayers[0];
                var table = (ArcGISFeatureTable)layer.FeatureTable;
                var template = table.LayerInfo.FeatureTemplates.FirstOrDefault();
                var feature = table.CreateFeature(template);
                feature.Geometry = location;
                await table.AddFeatureAsync(feature);
                await layer.FeatureTable.DeleteFeatureAsync(feature);
            }
            catch (TaskCanceledException)
            {

            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }

I don't think this is platform-specific but how are you storing the new feature?

You can use one of the following - GetAddedFeaturesAsync

 var features = await ((ArcGISFeatureTable)layer.FeatureTable).GetAddedFeaturesAsync();

or through interaction from a GeoViewTapped event

            var results = await MyMapView.IdentifyLayerAsync(layer, screenPoint, 1, false);
            if (results.GeoElements != null)
            {
                foreach (var element in results.GeoElements)
                {
                    await layer.FeatureTable.DeleteFeatureAsync((Feature)element);
                }
            }
ChadYoder1
Occasional Contributor II

Jennifer, thanks for the reply.

I am adding the feature similar to the code you show, but it is more like below since the user is adding the feature online, where apply edits is called.

try
            {
                var location = await MyMapView.SketchEditor.StartAsync(SketchCreationMode.Point, false) as MapPoint;
                var layer = (FeatureLayer)MyMapView.Map.OperationalLayers[0];
                var table = (ArcGISFeatureTable)layer.FeatureTable;
                var template = table.LayerInfo.FeatureTemplates.FirstOrDefault();
                var feature = table.CreateFeature(template);
                feature.Geometry = location;
                await table.AddFeatureAsync(feature);

                var serviceTable = table as ServiceFeatureTable;
                var results = await serviceTable.ApplyEditsAsync ();

                await layer.FeatureTable.DeleteFeatureAsync(feature);
            }
            catch (TaskCanceledException)
            {

            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }

In this code, you'll get the 'Unable to complete operation' error when DeleteFeaturesAsync() is called.

And, if you inspect the GlobalId, you'll see that it is set.  But, if you use QueryFeaturesAsync or get the feature by tapping on it, you'll see that the GlobalId is different.  Is this expected?

Is there a way to get/refresh the feature that has been added without going back to the server to get it (either by QueryFeaturesAsync or tapping the map)?

Should RetryLoadAsync() refresh the feature so it's what is on the server?

I get it if I need to go back to the server, but stumped on how to get the feature, other than use the attributes that the user entered since the ObjectId is a negative number (not refreshed from the server) and the GlobalId is different.  Hope this makes sense.  This is not an issue while offline, only when working in a connected environment.

Thanks again for your help.

0 Kudos
JenniferNery
Esri Regular Contributor

Ah I see, since feature now exists on server after ApplyEditsAsync call, you can call feature.RefreshObjectId() before DeleteFeaturesAsync. DeleteFeatures failed for the same reason that it triggered a query on server with incomplete query. Note that Add/Update/DeleteFeaturesAsync or Add/Update/DeleteAttachmentsAsync are local changes until you call ApplyEditsAsync, at which time edits are sync'd to server. In the snippet above then feature is added to service but is not deleted until you call ApplyEditsAsync again.

0 Kudos
ChadYoder1
Occasional Contributor II

Jennifer, thanks again, I'll try RefreshObjectId and see if that resolves my issue.  I missed that one, sounds like it will fix my issue.

Thanks again, I'll post back my results after I get a chance to test.

0 Kudos
JenniferNery
Esri Regular Contributor

I missed to address your other question about globalid and objectid. While a negative object id is expected for new features that only exists locally (before ApplyEdits), the global id and object id from server can be retrieved from ApplyEdits result. You can query for the updated feature using the following code as well.

                var results = await table.ApplyEditsAsync();               
                if (results != null)
                {
                    var query = new QueryParameters();
                    foreach (var r in results)
                        query.ObjectIds.Add(r.ObjectId);
                    var features = await table.QueryFeaturesAsync(query);
                }

I think RefreshObjectId should have also updated the feature.Attributes - we'll try to get this fixed in future versions of the API. Adding RefreshObjectId before DeleteFeaturesAsync will delete the correct feature. But if you need the updated attributes, you can use QueryFeaturesAsync.

0 Kudos
ChadYoder1
Occasional Contributor II

Jennifer, RefreshObjectId() doesn't seem to work, it stays a negative value.  Should RetryLoadAsync() load the feature from the server?

I did confirm that getting the results from the EditResult returned from ApplyEditsAsync does in fact have the ObjectId and GlobalId values.  Thanks for that tip, much appreciated.

Just curious, why does the GlobalId get set when the feature is added, but then changes after ApplyEditsAsync() is called?

await ftrTable.AddFeatureAsync (feature);

var gidFieldName = ((ArcGISFeatureTable)feature.FeatureTable).GlobalIdField;
object gid = string.Empty;
feature.Attributes.TryGetValue (gidFieldName, out gid);
var oidFieldName = ((ArcGISFeatureTable)feature.FeatureTable).ObjectIdField;
object oid = string.Empty;
feature.Attributes.TryGetValue (oidFieldName, out oid);
ToastHelpers.ShowInfo (oid.ToString (), gid.ToString (), 2);

feature.RefreshObjectId ();
oid = string.Empty;
feature.Attributes.TryGetValue (oidFieldName, out oid);
ToastHelpers.ShowInfo (oid.ToString (), gid.ToString (), 2);

ServiceFeatureTable serviceTable = ftrTable as ServiceFeatureTable;

IReadOnlyList<EditResult> results = await serviceTable.ApplyEditsAsync ();
foreach (EditResult result in results) {
   ToastHelpers.ShowInfo (result.ObjectId.ToString (), result.GlobalId, 2);
}
0 Kudos
JenniferNery
Esri Regular Contributor

Hi Chad,

RefreshObjectId() not updating attribute value is a bug which we'll have to fix. It remains to be the local cached values. To workaround this, you can query for the feature instead. However in my test, when RefreshObjectId is called after ApplyEdits, DeleteFeature does not fail. It deletes the correct feature, but attribute values does not reflect the same values from ApplyEdits result. Can you confirm?

Based on your feedback we also have to fix the issue where an incomplete query can occur if feature being deleted is either not part of the table or had been saved but not refreshed. 

The GlobalId and ObjectId uniquely identifies the feature so they are both automatically generated as soon as feature is created and added to the table. When edits are submitted to the server, they are expected to change because say you have other clients pushing edits to the same server, each of those new features need to be given unique identifiers. ApplyEdits then would respond with the GlobalId and ObjectId assigned by the server.

0 Kudos
ChadYoder1
Occasional Contributor II

Magical....it is not refreshing, but yeah, the delete does actually work.  Thanks, better than going back to query for the feature again.

Thanks for the Id feedback.  I understand the ObjectId, just thought the GlobalId would remain the same.  Regardless, all set, thanks so much for helping out.

0 Kudos
ChadYoder1
Occasional Contributor II

Just to add to this, I encountered the same issue when using AddFeatureAsync(), then ApplyEditsAsync(), and then without going back to the server to get the feature, calling UpdateFeatureAsync().  This actually throws a different error (I can't post if it will help, just let me know), but if you call RefreshObjectId on the feature, then everything works as expected.  

Thanks again for your help in getting this figured out.

0 Kudos