Skip To Content ArcGIS for Developers Sign In Dashboard

ArcGIS Runtime SDK for .NET

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?