Trying to wire up a basic table query, but not having much luck. The goal would be to add some initial support for relationships (I know it's coming soon), but we have folks asking for it now.
To test/develop, we downloaded the manhole inspection template from the water data model and published it to AGOL. The only change made was to link based on GlobalId, rather than FacilityId.
There is currently only one record, with a single related record. The related record was created using Collector (maybe that was the issue??). Everything works fine if you add the data to an AGOL web map, we can see the related record.
The following code returns no records, although using the same query directly on the REST API returns the record.
var destTable = new ServiceFeatureTable (new Uri (
"http://services6.arcgis.com/v1B7paWSaOfBIIVv/ArcGIS/rest/services/ManholeInspections/FeatureServer/1"
));
destTable.FeatureRequestMode = FeatureRequestMode.OnInteractionCache;
await destTable.LoadAsync ();
var query = "ManholeNumber = 'dd11f844-16b0-4e86-9fbd-ca77275dba0e'";
FeatureQueryResult queryResult = await destTable.QueryFeaturesAsync (new QueryParameters () {
WhereClause = query
}, QueryFeatureFields.LoadAll);
var ftrs = queryResult.ToList ();
if (ftrs.Any ()) {
//Handle the results
};
Solved! Go to Solution.
Related Tables are coming soon so hopefully you can take advantage of that when it comes out.
Meanwhile, you can try the following code where spatial table is added to the map and non-spatial table is populated upfront. You can use GeoViewTapped to get the feature of interest and retrieve its 'GlobalId'. Based on service metadata, the non-spatial table is related by 'ManholeNumber' field just as you had in your code. The only difference is GUIDs are stored in uppercase. This doesn't really mimic the queryRelated request in arcgis.com but should return the same feature you would expect.
xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013">
<Grid>
<esri:MapView x:Name="MyMapView" GeoViewTapped="MyMapView_GeoViewTapped"/>
</Grid>
public MainWindow()
{
InitializeComponent();
MyMapView.Map = new Map(Basemap.CreateTopographic());
MyMapView.Map.OperationalLayers.Add(new FeatureLayer(new Uri("http://services6.arcgis.com/v1B7paWSaOfBIIVv/ArcGIS/rest/services/ManholeInspections/FeatureServer/0")));
}
ServiceFeatureTable _table;
private async void MyMapView_GeoViewTapped(object sender, GeoViewInputEventArgs e)
{
var result = await MyMapView.IdentifyLayersAsync(e.Position, 1, false, 1);
foreach (var r in result)
{
var globalIdField = ((r.LayerContent as FeatureLayer)?.FeatureTable as ArcGISFeatureTable)?.GlobalIdField;
if (string.IsNullOrEmpty(globalIdField))
continue;
foreach (var g in r.GeoElements)
{
var parameters = new QueryParameters()
{
WhereClause = $"ManholeNumber='{{{g.Attributes[globalIdField]?.ToString()?.ToUpper()}}}'"
};
if (_table == null)
{
_table = new ServiceFeatureTable(new Uri("http://services6.arcgis.com/v1B7paWSaOfBIIVv/ArcGIS/rest/services/ManholeInspections/FeatureServer/1"))
{
FeatureRequestMode = FeatureRequestMode.ManualCache
};
await _table.PopulateFromServiceAsync(new QueryParameters() { WhereClause = "1=1" }, true, new string[] { "*" });
}
var relatedFeatures = await _table.QueryFeaturesAsync(parameters);
}
}
Related Tables are coming soon so hopefully you can take advantage of that when it comes out.
Meanwhile, you can try the following code where spatial table is added to the map and non-spatial table is populated upfront. You can use GeoViewTapped to get the feature of interest and retrieve its 'GlobalId'. Based on service metadata, the non-spatial table is related by 'ManholeNumber' field just as you had in your code. The only difference is GUIDs are stored in uppercase. This doesn't really mimic the queryRelated request in arcgis.com but should return the same feature you would expect.
xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013">
<Grid>
<esri:MapView x:Name="MyMapView" GeoViewTapped="MyMapView_GeoViewTapped"/>
</Grid>
public MainWindow()
{
InitializeComponent();
MyMapView.Map = new Map(Basemap.CreateTopographic());
MyMapView.Map.OperationalLayers.Add(new FeatureLayer(new Uri("http://services6.arcgis.com/v1B7paWSaOfBIIVv/ArcGIS/rest/services/ManholeInspections/FeatureServer/0")));
}
ServiceFeatureTable _table;
private async void MyMapView_GeoViewTapped(object sender, GeoViewInputEventArgs e)
{
var result = await MyMapView.IdentifyLayersAsync(e.Position, 1, false, 1);
foreach (var r in result)
{
var globalIdField = ((r.LayerContent as FeatureLayer)?.FeatureTable as ArcGISFeatureTable)?.GlobalIdField;
if (string.IsNullOrEmpty(globalIdField))
continue;
foreach (var g in r.GeoElements)
{
var parameters = new QueryParameters()
{
WhereClause = $"ManholeNumber='{{{g.Attributes[globalIdField]?.ToString()?.ToUpper()}}}'"
};
if (_table == null)
{
_table = new ServiceFeatureTable(new Uri("http://services6.arcgis.com/v1B7paWSaOfBIIVv/ArcGIS/rest/services/ManholeInspections/FeatureServer/1"))
{
FeatureRequestMode = FeatureRequestMode.ManualCache
};
await _table.PopulateFromServiceAsync(new QueryParameters() { WhereClause = "1=1" }, true, new string[] { "*" });
}
var relatedFeatures = await _table.QueryFeaturesAsync(parameters);
}
}
Jennifer, thanks again, your solution worked for me.
I was using the wrong cache mode, and didn't realize to call PopulateFromServiceAsync(). Once I updated that, things worked out.
I also did change the query to use the upper case of the GlobalId. I was wondering about this, but maybe a different thread. Using the original query (lowercase) works when executed through the REST API. And the resulting field does not seems to be stored in upper case.
You're welcome. I'm still trying to get an answer as to why we have chosen to store GUIDs in uppercase so I'll have to get back on you on that.
Another way is to compare them as Guid. Both PopulateFromServiceAsync and QueryFeatures will return IEnumerable<Features> so using Linq query you can compare the attribute value this way.
var features = await _table.PopulateFromServiceAsync(new QueryParameters() { WhereClause = "1=1" }, true, new string[] { "*" });
var relatedFeature = features.FirstOrDefault(f => (Guid)f.Attributes["ManholeNumber"] == (Guid)g.Attributes[globalIdField]);
Thanks, that would be helpful to understand.
Your help is greatly appreciated, thanks so much!!
Question about how to use PopuateFromServiceAsync(). The documentation states:
"Asynchronously performs manual query of data from the service and imports feature results into the table."
Imports? If subsequent calls are made for the same data, is it overwritten/updated in the feature table, or will duplicates be created? In the example above, I'm thinking the ideal situation would be to not use 1=1 when calling this method to maximize performance.
Offline is obviously a different pattern since all of the data is expected to be cached locally. But online, we'd want to minimize the amount of data transferred when requesting data from the service.
Thanks again for your assistance.
Hi Chad,
As for the GUIDs, it is by design that runtime APIs store them in upper-case. While this specific service may have Postgres back-end database, which would make them lower-case, the upper-case string comparison query will still work on server-side.
You're right, ManualCache imports features and will not get overwritten until you call PopulateFromServiceAsync with clearCache=true. I was thinking since this is a non-spatial table, interaction with the map which could trigger query based on current view wouldn't be helpful. But since we are doing the query with where clause here, any of these FeatureRequestMode would still work. You can choose whichever works best for you. You can use QueryFeatureFields to specify whether to return all fields, minimum set of fields or ids only. This should be equivalent to returning "*" all fields.
var parameters = new QueryParameters()
{
WhereClause = $"ManholeNumber ='{{{g.Attributes[globalIdField]?.ToString()?.ToUpper()}}}'"
};
if (_table == null)
{
_table = new ServiceFeatureTable(new Uri("http://services6.arcgis.com/v1B7paWSaOfBIIVv/ArcGIS/rest/services/ManholeInspections/FeatureServer/1"));
// Or keep default FeatureRequstMode.OnInteractionCache.
_table.FeatureRequestMode = FeatureRequestMode.OnInteractionNoCache;
await _table.LoadAsync();
}
var relatedFeatures = await _table.QueryFeaturesAsync(parameters, QueryFeatureFields.LoadAll);
Good to know. Excellent info, thanks so much for the assistance!!