Edit Related Data

Download Samples Repository

Description

Demonstrates how to query and edit related records.

"Desktop" "Store" "Phone" Available for Desktop, Store, Phone

Sample Code

<UserControl x:Class="ArcGISRuntime.Samples.Desktop.EditRelatedData"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013">
    <UserControl.Resources>
        <!-- Transparent symbol with outline highlights feature. -->
        <esri:SimpleLineSymbol x:Key="HighlightOutlineSymbol"
                               Color="Cyan"
                               Width="4.5" />
        <esri:SimpleMarkerSymbol x:Key="HighlightMarkerSymbol"
                                 Color="Transparent"
                                 Outline="{StaticResource HighlightOutlineSymbol}"
                                 Size="21.5" />
        <esri:SimpleRenderer x:Key="HighlightRenderer"
                             Symbol="{StaticResource HighlightMarkerSymbol}" />
    </UserControl.Resources>
    <Grid>
        <esri:MapView x:Name="MyMapView"
                      MapViewTapped="MyMapView_MapViewTapped">
            <esri:Map InitialViewpoint="-9813341.31730507,5126248.61666055, -9810989.33812169, 5127472.38082941, 102100">
                <esri:ArcGISTiledMapServiceLayer ServiceUri="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer" />
                <!-- Renders related features at current extent -->
                <esri:ArcGISDynamicMapServiceLayer ID="ServiceRequests"
                                                   VisibleLayers="0"
                                                   ServiceUri="http://sampleserver6.arcgisonline.com/arcgis/rest/services/ServiceRequest/MapServer" />
            </esri:Map>
            <esri:MapView.GraphicsOverlays>
                <!-- Highlights one feature at a time -->
                <esri:GraphicsOverlay ID="Highlighter"
                                      Renderer="{StaticResource HighlightRenderer}" />
            </esri:MapView.GraphicsOverlays>
        </esri:MapView>
        <!-- Used for editing related records. -->
        <Border Background="White"
                BorderBrush="Black"
                BorderThickness="1"
                HorizontalAlignment="Right"
                VerticalAlignment="Top"
                Width="250"
                Margin="30"
                Padding="20">
            <Border.Effect>
                <DropShadowEffect />
            </Border.Effect>
            <StackPanel>
                <Button x:Name="AddButton"
                        IsEnabled="False"
                        Content="Add"
                        Margin="2"
                        Click="AddButton_Click"
                        HorizontalAlignment="Left" />
                <ComboBox x:Name="RelatedRecords"
                          SelectionChanged="RelatedRecords_SelectionChanged"
                          Margin="2">
                    <ComboBox.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Attributes[objectid]}" />
                        </DataTemplate>
                    </ComboBox.ItemTemplate>
                </ComboBox>
                <Grid x:Name="AttributeEditor"
                      Visibility="Collapsed">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition />
                        <RowDefinition />
                    </Grid.RowDefinitions>
                    <TextBlock Text="Importance"
                               Margin="2" />
                    <ComboBox x:Name="ChoiceList"
                              Margin="2"
                              Grid.Column="1">
                        <ComboBox.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding}" />
                            </DataTemplate>
                        </ComboBox.ItemTemplate>
                    </ComboBox>
                        <TextBlock Text="Comments"
                               Margin="2"
                               Grid.Row="1" />
                    <TextBox x:Name="Comments"
                             TextWrapping="Wrap"
                             Margin="2"
                             Grid.Row="1"
                             Grid.Column="1" />
                    <StackPanel Orientation="Horizontal"
                                Grid.Row="2"
                                Grid.ColumnSpan="2"
                                HorizontalAlignment="Right">
                        <Button Content="Apply"
                                Margin="2"
                                Click="EditButton_Click" />
                        <Button Content="Delete"
                                Margin="2"
                                Click="DeleteButton_Click" />
                    </StackPanel>
                </Grid>
            </StackPanel>
        </Border>
        <Border Background="White"
                BorderBrush="Black"
                BorderThickness="1"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Margin="30"
                Padding="20">
            <Border.Effect>
                <DropShadowEffect />
            </Border.Effect>
            <TextBlock Text="Click on a feature to select. 'Add' new related record or update/delete any of its existing records."
                       Width="200"
                       TextAlignment="Left"
                       Margin="30,20,20,30"
                       TextWrapping="Wrap" />
        </Border>
    </Grid>
</UserControl>
using Esri.ArcGISRuntime.Controls;
using Esri.ArcGISRuntime.Data;
using Esri.ArcGISRuntime.Layers;
using Esri.ArcGISRuntime.Tasks.Query;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace ArcGISRuntime.Samples.Desktop
{
    /// <summary>
    /// Demonstrates how to query and edit related records.
    /// </summary>
    /// <title>Edit Related Data</title>
    /// <category>Editing</category>
    public partial class EditRelatedData : UserControl
    {
        // Query is done through this relationship.
        Esri.ArcGISRuntime.ArcGISServices.Relationship relationship;
        // Editing is done through this table.
        ServiceFeatureTable table;

        public EditRelatedData()
        {
            InitializeComponent();
        }
        
        /// <summary>
        /// Identifies graphic to highlight and query its related records.
        /// </summary>
        private async void MyMapView_MapViewTapped(object sender, MapViewInputEventArgs e)
        {
            var layer = MyMapView.Map.Layers["ServiceRequests"] as ArcGISDynamicMapServiceLayer;
            var task = new IdentifyTask(new Uri(layer.ServiceUri));
            var mapPoint = MyMapView.ScreenToLocation(e.Position);

            // Get current viewpoints extent from the MapView
            var currentViewpoint = MyMapView.GetCurrentViewpoint(ViewpointType.BoundingGeometry);
            var viewpointExtent = currentViewpoint.TargetGeometry.Extent;

            var parameter = new IdentifyParameters(mapPoint, viewpointExtent, 5, (int)MyMapView.ActualHeight, (int)MyMapView.ActualWidth);

            // Clears map of any highlights.
            var overlay = MyMapView.GraphicsOverlays["Highlighter"] as GraphicsOverlay;
            overlay.Graphics.Clear();

            SetRelatedRecordEditor();

            string message = null;
            try
            {
                // Performs an identify and adds graphic result into overlay.
                var result = await task.ExecuteAsync(parameter);
                if (result == null || result.Results == null || result.Results.Count < 1)
                    return;
                var graphic = (Graphic)result.Results[0].Feature;
                overlay.Graphics.Add(graphic);

                // Prepares related records editor.
                var featureID = Convert.ToInt64(graphic.Attributes["OBJECTID"], CultureInfo.InvariantCulture);
                string requestID = null;
                if(graphic.Attributes["Service Request ID"] != null)
                     requestID = Convert.ToString(graphic.Attributes["Service Request ID"], CultureInfo.InvariantCulture);
                SetRelatedRecordEditor(featureID, requestID);

                await QueryRelatedRecordsAsync();
            }
            catch (Exception ex)
            {
                message = ex.Message;
            }
            if (!string.IsNullOrWhiteSpace(message))
                MessageBox.Show(message);
        }

        /// <summary>
        /// Prepares related record editor for add and query.
        /// </summary>
        private void SetRelatedRecordEditor(long featureID = 0, string requestID = null)
        {
            AddButton.Tag = featureID;
            AddButton.IsEnabled = featureID > 0 && !string.IsNullOrWhiteSpace(requestID);
            RelatedRecords.Tag = requestID;
            if (featureID == 0)
            {
                RelatedRecords.ItemsSource = null;
                RelatedRecords.SelectedItem = null;
            }
            SetAttributeEditor();
        }

        /// <summary>
        /// Prepares attribute editor for update or delete of existing related record.
        /// </summary>
        private void SetAttributeEditor(Feature feature = null)
        {
            if (ChoiceList.ItemsSource == null && table != null && table.ServiceInfo != null && table.ServiceInfo.Fields != null)
            {
                var rangeDomain = (RangeDomain<IComparable>)table.ServiceInfo.Fields.FirstOrDefault(f => f.Name == "rank").Domain;
                ChoiceList.ItemsSource = GetRangeValues((int)rangeDomain.MinValue, (int)rangeDomain.MaxValue);
            }
            AttributeEditor.Tag = feature;
            if (feature != null)
            {
                if (feature.Attributes.ContainsKey("rank") && feature.Attributes["rank"] != null)
                    ChoiceList.SelectedItem = Convert.ToInt32(feature.Attributes["rank"], CultureInfo.InvariantCulture);
                if (feature.Attributes.ContainsKey("comments") && feature.Attributes["comments"] != null)
                Comments.Text = Convert.ToString(feature.Attributes["comments"], CultureInfo.InvariantCulture);
                AttributeEditor.Visibility = Visibility.Visible;
            }
            else
            {
                ChoiceList.SelectedItem = null;
                Comments.Text = string.Empty;
                AttributeEditor.Visibility = Visibility.Collapsed;
            }
        }

        /// <summary>
        /// Gets exclusive values between minimum and maximum values.
        /// </summary>
        private IEnumerable<int> GetRangeValues(int min, int max)
        {
            for (int i = min; i <= max; i++)
                yield return i;
        }

        /// <summary>
        /// Gets relationship information using service metadata.
        /// </summary>
        private async Task<Esri.ArcGISRuntime.ArcGISServices.Relationship> GetRelationshipAsync()
        {
            var layer = MyMapView.Map.Layers["ServiceRequests"] as ArcGISDynamicMapServiceLayer;
            if (layer == null || layer.VisibleLayers == null)
                return null;
            var id = layer.VisibleLayers.FirstOrDefault();
            var details = await layer.GetDetailsAsync(id);
            if (details != null && details.Relationships != null)
                return details.Relationships.FirstOrDefault();
            return null;
        }

        /// <summary>
        /// Queries related records for specified feature ID.
        /// </summary>
        private async Task QueryRelatedRecordsAsync()
        {
            var featureID = (Int64)AddButton.Tag;
            var layer = MyMapView.Map.Layers["ServiceRequests"] as ArcGISDynamicMapServiceLayer;
            var id = layer.VisibleLayers.FirstOrDefault();
            var task = new QueryTask(new Uri(string.Format("{0}/{1}", layer.ServiceUri, id)));
            if (relationship == null)
                relationship = await GetRelationshipAsync();
            var parameters = new RelationshipParameters(new List<long>(new long[] { featureID }), relationship.ID);
            parameters.OutFields = new OutFields(new string[] { "objectid" });
            var result = await task.ExecuteRelationshipQueryAsync(parameters);
            if (result != null && result.RelatedRecordGroups != null && result.RelatedRecordGroups.ContainsKey(featureID))
                RelatedRecords.ItemsSource = result.RelatedRecordGroups[featureID];
        }

        /// <summary>
        /// Submits related record edits back to server.
        /// </summary>
        private async Task SaveEditsAsync()
        {
            if (table == null || !table.HasEdits)
                return;
            if (table is ServiceFeatureTable)
            {
                var serviceTable = (ServiceFeatureTable)table;
                // Pushes new graphic back to the server.
                var result = await serviceTable.ApplyEditsAsync();
                if (result.AddResults == null || result.AddResults.Count < 1)
                    return;
                var addResult = result.AddResults[0];
                if (addResult.Error != null)
                    throw addResult.Error;
            }
        }

        /// <summary>
        /// Gets related table for editing.
        /// </summary>
        private async Task<ServiceFeatureTable> GetRelatedTableAsync()
        {
            var layer = MyMapView.Map.Layers["ServiceRequests"] as ArcGISDynamicMapServiceLayer;
            // Creates table based on related table ID of the visible layer in dynamic layer 
            // using FeatureServer specifying rank and comments fields to enable editing.
            var id = relationship.RelatedTableID.Value;
            var url = layer.ServiceUri.Replace("MapServer", "FeatureServer");
            url = string.Format("{0}/{1}", url, id);
            var table = await ServiceFeatureTable.OpenAsync(new Uri(url), null, MyMapView.SpatialReference);
            table.OutFields = new OutFields(new string[] {relationship.KeyField, "rank", "comments", "submitdt" });
            return table;
        }

        /// <summary>
        /// Displays current attribute values of related record.
        /// </summary>
        private async void RelatedRecords_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (RelatedRecords.SelectedItem == null)
                return;
            var graphic = (Graphic)RelatedRecords.SelectedItem;
            SetAttributeEditor();
            string message = null;
            try
            {
                if (table == null)
                    table = await GetRelatedTableAsync();
                var featureID = Convert.ToInt64(graphic.Attributes[table.ObjectIDField], CultureInfo.InvariantCulture);
                var feature = await table.QueryAsync(featureID);
                SetAttributeEditor(feature);
            }
            catch (Exception ex)
            {
                message = ex.Message;
            }
            if (!string.IsNullOrWhiteSpace(message))
                MessageBox.Show(message);
        }
        
        /// <summary>
        /// Adds a new related record to highlighted feature.
        /// </summary>
        private async void AddButton_Click(object sender, RoutedEventArgs e)
        {
            SetAttributeEditor();
            var featureID = (Int64)AddButton.Tag;
            var requestID = (string)RelatedRecords.Tag;
            string message = null;
            try
            {
                if (table == null)
                    table = await GetRelatedTableAsync();
                var feature = new GeodatabaseFeature(table.Schema);                
                feature.Attributes[relationship.KeyField] = requestID;
                feature.Attributes["rank"] = 5;
                feature.Attributes["comments"] = "Describe service requirement here.";
                feature.Attributes["submitdt"] = DateTime.UtcNow;
                var relatedFeatureID = await table.AddAsync(feature);
                await SaveEditsAsync();
                await QueryRelatedRecordsAsync();
            }
            catch (Exception ex)
            {
                message = ex.Message;
            }
            if (!string.IsNullOrWhiteSpace(message))
                MessageBox.Show(message);
        }
        
        /// <summary>
        /// Updates attributes of related record.
        /// </summary>
        private async void EditButton_Click(object sender, RoutedEventArgs e)
        {
            if (AttributeEditor.Tag == null) return;
            var feature = (GeodatabaseFeature)AttributeEditor.Tag;
            string message = null;
            try
            {
                if(ChoiceList.SelectedItem != null)
                    feature.Attributes["rank"] = (int)ChoiceList.SelectedItem;
                feature.Attributes["comments"] = Comments.Text.Trim();
                await table.UpdateAsync(feature);
                await SaveEditsAsync();
            }
            catch (Exception ex)
            {
                message = ex.Message;
            }
            if (!string.IsNullOrWhiteSpace(message))
                MessageBox.Show(message);
        }
        
        /// <summary>
        /// Deletes related record.
        /// </summary>
        private async void DeleteButton_Click(object sender, RoutedEventArgs e)
        {
            if (RelatedRecords.SelectedItem == null) return;
            var graphic = (Graphic)RelatedRecords.SelectedItem;
            string message = null;
            try
            {
                if (table == null)
                    table = await GetRelatedTableAsync();
                var featureID = Convert.ToInt64(graphic.Attributes[table.ObjectIDField], CultureInfo.InvariantCulture);
                await table.DeleteAsync(featureID);
                await SaveEditsAsync();
                await QueryRelatedRecordsAsync();
                SetAttributeEditor();
            }
            catch (Exception ex)
            {
                message = ex.Message;
            }
            if (!string.IsNullOrWhiteSpace(message))
                MessageBox.Show(message);
        }
    }
}
Feedback on this topic?