Select to view content in your preferred language

Is it possible to edit file gdb feature classes from Windows Service ?

1172
7
Jump to solution
06-20-2012 03:50 AM
Labels (1)
NirYoscovitz
Emerging Contributor
Hello,

I'm looking for a way to update data in feature classes located in a file geodatabse from a Windows Service.
From tests I have done so far, it seems I have no way to do so in current version of the Runtime.
Will this capability be supported in future releases of the Runtime ?

Regards,
Nir
0 Kudos
1 Solution

Accepted Solutions
MichaelBranscomb
Esri Frequent Contributor
Hi,

This should be possible. You will need to create a Map Package which contains (or references) the File GDB you wish to edit. Then create a LocalFeatureService from the map package and use the local service as the basis for an ArcGISLocalFeatureLayer. Edits are then performed against this layer. You should bear in mind that the licensing is per-application and therefore your windows service will need an ArcGIS Runtime standard license and that the license is single user only, i.e. the service can only be called by one client application (which would also need a separate ArcGIS Runtime license if it used any components of the ArcGIS Runtime SDK for WPF). The windows service could not be accessed by mutiple clients because that would constitute a server license (i.e. ArcGIS for Server).

Typically layer initialization is handled by the map, but as your layer would not be participating in a map then you would need to call the Initialize method explicitly and listen for the InitializeCompleted event. That means the layer is ready for use but will not contain any features because the loading of features from the database into the layer is controlled by queries. In a typical map-based scenario the query uses the map extent to request features but if you wanted to retrieve existing features from the database you would need to construct your own queries and use the Update / UpdateCompleted event to handle that.

Note the usage of these methods:
.Update() = Selections and unsaved edits will be lost on Update. Performs a new refreshed query against the service and refreshes the graphics layer.
.SaveEdits() = Save edits to the layer. Only required if AutoSave is false.
.Refresh() = Forces a full redraw of all graphic features but does not affect selections or unsaved edits.

Cheers

Mike

View solution in original post

0 Kudos
7 Replies
MichaelBranscomb
Esri Frequent Contributor
Hi,

This should be possible. You will need to create a Map Package which contains (or references) the File GDB you wish to edit. Then create a LocalFeatureService from the map package and use the local service as the basis for an ArcGISLocalFeatureLayer. Edits are then performed against this layer. You should bear in mind that the licensing is per-application and therefore your windows service will need an ArcGIS Runtime standard license and that the license is single user only, i.e. the service can only be called by one client application (which would also need a separate ArcGIS Runtime license if it used any components of the ArcGIS Runtime SDK for WPF). The windows service could not be accessed by mutiple clients because that would constitute a server license (i.e. ArcGIS for Server).

Typically layer initialization is handled by the map, but as your layer would not be participating in a map then you would need to call the Initialize method explicitly and listen for the InitializeCompleted event. That means the layer is ready for use but will not contain any features because the loading of features from the database into the layer is controlled by queries. In a typical map-based scenario the query uses the map extent to request features but if you wanted to retrieve existing features from the database you would need to construct your own queries and use the Update / UpdateCompleted event to handle that.

Note the usage of these methods:
.Update() = Selections and unsaved edits will be lost on Update. Performs a new refreshed query against the service and refreshes the graphics layer.
.SaveEdits() = Save edits to the layer. Only required if AutoSave is false.
.Refresh() = Forces a full redraw of all graphic features but does not affect selections or unsaved edits.

Cheers

Mike
0 Kudos
NirYoscovitz
Emerging Contributor
Hi Mike,

Thank you very much for your answer.

I have succeeded updating the feature class without GUI.
Here is the code, based on your solution :

ArcGISLocalFeatureLayer MyFeatureLayer;
MapPoint pnt;//initialize it with your favorite coordinate...
�?�
Private void EditMyFeatureLayer()
{
  
LocalFeatureService localFeatureService = new    
 LocalFeatureService(@"C:\Data\MyData.mpk");

MyFeatureLayer = new ArcGISLocalFeatureLayer(localFeatureService,0);
MyFeatureLayer.ID = "Cars";
MyFeatureLayer.Visible = false;
MyFeatureLayer.Editable = true;
MyFeatureLayer.DisableClientCaching = true;
MyFeatureLayer.ValidateEdits = true;
MyFeatureLayer.Mode = FeatureLayer.QueryMode.Snapshot;
MyFeatureLayer.AutoSave = true;
MyFeatureLayer.OutFields.Add("*");
MyFeatureLayer.Where = "Car_id = 1";

MyFeatureLayer.Initialized += new     
 EventHandler<EventArgs>(MyFeatureLayer_Initialized);
MyFeatureLayer.UpdateCompleted += new 
 EventHandler(MyFeatureLayer_UpdateCompleted);
MyFeatureLayer.Initialize();
}

void MyFeatureLayer_Initialized(object sender, EventArgs e)
{
 MyFeatureLayer.Update();
 }

void MyFeatureLayer_UpdateCompleted(object sender, EventArgs e)
{
 //update here�?�
 foreach (Graphic g in MyFeatureLayer.Graphics)
 {
  g.geometry = pnt;
 }
} 




Regards,
Nir
0 Kudos
RobertZargarian
Deactivated User
Hi,

I tried this concept too without success. Tried to edit a feature with ArcGISLocalFeatureLayer or even FeatureLayer (connected to ArcGIS Server) without GUI. If I run this from a console application it is complaining that it can�??t create layer with following error message.

�??The calling thread must be STA, because many UI components require.�?�

It works if I run as WPF application and put all code in mainwindow constructor, but then I can�??t run it as service if I have a window?

Regards,
Robert
0 Kudos
NirYoscovitz
Emerging Contributor
Hi Robert,

You may run your code in a new thread which will be defined as STA

//put your code in ThreadProc method...
Thread t = new Thread(ThreadProc);
t.SetApartmentState(ApartmentState.STA);
t.Start();


Regards,
Nir
0 Kudos
RobertZargarian
Deactivated User
Hi Nir,
Thank you for your answer. I tried to start code in new thread, but now I got another exception.
Any idias about this?

=====================
System.InvalidOperationException was unhandled
HRESULT = -2146233079
Message = The calling thread can not access this object because it belongs to another thread.


Source = Windows Base
Stack Trace:
at System.Windows.Threading.Dispatcher.VerifyAccess ()
at System.Windows.DependencyObject.ClearValue (DependencyProperty dp)
at ESRI.ArcGIS.Client.FeatureLayer.set_LayerInfo (Feature Layer Info value)
at ESRI.ArcGIS.Client.FeatureLayer.featureLayerInfo_OutputReady (Object sender, EventArgs e)
at ESRI.ArcGIS.Client.FeatureService.FeatureLayerInfo.OnReady (EventArgs e)
at ESRI.ArcGIS.Client.FeatureService.FeatureLayerInfo.Execute_Completed (Object sender, RequestEventArgs e)
at ESRI.ArcGIS.Client.WebRequest.OnComplete (RequestEventArgs args)
at ESRI.ArcGIS.Client.WebRequest.downloadStringCompleted (Object sender, DownloadStringCompletedEventArgs e Action retryCallback)
at ESRI.ArcGIS.Client.WebRequest. <> c__DisplayClass1c. <BuildClient> b__1a (Object s, DownloadStringCompletedEventArgs e)
at System.Net.WebClient.OnDownloadStringCompleted (DownloadStringCompletedEventArgs e)
at System.Net.WebClient.DownloadStringOperationCompleted (Object arg)
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context (Object state)
at System.Threading.ExecutionContext.RunInternal (ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run (ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem ()
at System.Threading.ThreadPoolWorkQueue.Dispatch ()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback ()
InnerException:

===========================================

I have a simple console program and this is my code:

public class Program
    {
        FeatureLayer myFeatureLayer;

        static void Main(string[] args)
        {
            Thread t = new Thread(new ThreadStart(ThreadProc));
            t.SetApartmentState(ApartmentState.STA);
            t.Start();
            Console.ReadLine();
        }

        public static void ThreadProc()
        {
            string url = "myURL";
            Program p = new Program();
            p.UpdateLayer(url);
        }


        private void UpdateLayer(string Url)
        {
            myFeatureLayer = new FeatureLayer
             {
                 Url = Url,
                 ID = "23",
                 Mode = FeatureLayer.QueryMode.Snapshot,
             };

            myFeatureLayer.Initialized += (sender, eventArgs) =>
            {
                myFeatureLayer.Update();
            };

            myFeatureLayer.InitializationFailed += (sender, e) =>
                {
                    Console.WriteLine(myFeatureLayer.InitializationFailure.Message);
                };

            myFeatureLayer.UpdateFailed += (sender, e) =>
                {
                    Console.WriteLine(e.Error);
                };

            myFeatureLayer.UpdateCompleted += (sender, e) =>
            {
                myFeatureLayer.Graphics[0].Attributes["NAMNEDIT"] = "Test";
                myFeatureLayer.SaveEditsFailed += (a, b) =>
                    {
                        Console.WriteLine(b.Error);
                    };
                myFeatureLayer.SaveEdits();
            };

            myFeatureLayer.Initialize();
        }
    }
0 Kudos
MichaelBranscomb
Esri Frequent Contributor
Hi,

I have to admit it took me a little while and a fair amount of Googling to figure this out - but the solution seems to be to set the SynchronizationContext of the main thread to a new DispatcherSynchronizationContext e.g.

var syncCtx = new DispatcherSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(syncCtx); 


The issue is that the Initialized event is firing on a different thread from the one on which the FeatureLayer is created, hence the exception you're seeing about the calling thread. As far as I know, this shouldn't happen. Working with your code in a console application I added additional code in each event (Initialized and UpdateCompleted) to get the Dispatcher of the FeatureLayer then call CheckAccess on that Dispatcher which confirms whether an Invoke is required to post back onto the calling thread. I also added code to write out the current thread id, just so I could easily check what was going on. This confirmed that the Initialized event was always firing on a different thread but I do not know why and even when I did call Dispatcher.Invoke (or .BeginInvoke) to post back on to the original thread it still failed with the same exception. A colleague suggested I should check the SynchronizationContext of the main thread and of the thread on which the event was firing. However, the SynchronizationContext of the main thread was always null and unfortunately I still do not know the reason for this - but it seems to be the reason why the event was firing on a different thread. Fortunately I found a blog post which, although addressing a different use case, showed me how to make sure there is a SynchronizationContext (http://blogs.msdn.com/b/pfxteam/archive/2012/01/21/10259307.aspx) - see code snippet above and full code below. Once I had added this code it worked perfectly. The code may not even need all the Dispatcher checks/invokes once the Sync Context is now set but I think it's good practice in this scenario.

Here's the full code:

using System;
using System.Threading;
using System.Windows.Threading;
using ESRI.ArcGIS.Client;

namespace UpdateGraphicsConsole
{
    class Program
    {
        static string _url = "http://sampleserver6.arcgisonline.com/arcgis/rest/services/Wildfire/FeatureServer/0";

        // Make sure the main thread is an STA thread
        [STAThread]
        static void Main(string[] args)
        {
            // For some reason the SynchronizationContext of the main thread is null.
            // Create and set a new DispatcherSynchronizationContext.
            var syncCtx = new DispatcherSynchronizationContext();
            SynchronizationContext.SetSynchronizationContext(syncCtx); 

            // Get the current dispatcher to check thread access.
            Dispatcher d = Dispatcher.CurrentDispatcher;
            
            // Create a FeatureLayer
            FeatureLayer fl1 = new FeatureLayer
            {
                Url = _url,
                ID = "23",
                Mode = FeatureLayer.QueryMode.Snapshot,
                AutoSave = false,
            };

            // Report which thread the FeatureLayer was created on
            Console.WriteLine("FeatureLayer created on Thread " + d.Thread.ManagedThreadId.ToString());

            // Register a handler for the Initialized event
            fl1.Initialized += (sender, eventArgs) =>
            {
                // Report which thread the event fired on (this was always a different thread until the DispatcherSynchronizationContext was set
                Console.WriteLine("Initialized event handled on Thread " + Dispatcher.CurrentDispatcher.Thread.ManagedThreadId.ToString());

                // Get the FeatureLayer
                FeatureLayer fl2 = sender as FeatureLayer;

                // Get the FeatureLayer dispatcher
                Dispatcher d1 = fl2.Dispatcher;

                // Report the thread we're invoking the Update call on (this was always the original thread)
                Console.WriteLine("Invoking Update on Thread " + d1.Thread.ManagedThreadId.ToString());

                // CheckAccess always returned false because this code was running on a different thread until the DispatcherSynchronizationContext was set
                if (fl2.Dispatcher.CheckAccess())
                {
                    fl2.Update();
                }
                else
                    d1.BeginInvoke(
                        DispatcherPriority.Normal,
                        new Action(delegate()
                            {
                                fl2.Update();
                            }));
            };

            // Register a handler for the UpdateCompleted event and call SaveEdits()
            fl1.UpdateCompleted += (sender, e) =>
            {
                // Get the FeatureLayer
                FeatureLayer fl3 = sender as FeatureLayer;

                // Get the FeatureLayer Dispatcher
                Dispatcher d2 = fl3.Dispatcher;

                // Update the graphic attribute (or perform any other edits)
                fl3.Graphics[0].Attributes["description"] = Guid.NewGuid().ToString();

                // Write out the new value 
                Console.WriteLine(
                    "Updated Graphic ObjectID " 
                    + fl3.Graphics[0].Attributes["objectid"] 
                    + " with Description: " 
                    + fl3.Graphics[0].Attributes["description"]); 
                
                // Call CheckAccess to confirm whether we need to post back to the original thread
                // Then call SaveEdits
                if (fl3.Dispatcher.CheckAccess())
                {
                    fl3.SaveEdits();
                }
                else
                    d2.BeginInvoke(
                    DispatcherPriority.Normal,
                    new Action(() => fl3.SaveEdits()));
            };

            // Register a handler for the EndSaveEdits event and write out the status
            fl1.EndSaveEdits += (sender, e) =>
            {
                Console.WriteLine(e.Success ? "Success" : "Fail");
                // Can check results online at query endpoint (by description value, objectid, etc)
                // http://sampleserver6.arcgisonline.com/arcgis/rest/services/Wildfire/FeatureServer/0/query
            };

            // Register a handler for the InitializationFailed event
            fl1.InitializationFailed += (sender, e) =>
            {
                // Report error
                FeatureLayer fl4 = sender as FeatureLayer;
                Console.WriteLine(fl4.InitializationFailure.Message);
            };

            // Register a handler for the UpdateFailed event
            fl1.UpdateFailed += (sender, e) =>
            {
                // Report failure to update layer
                Console.WriteLine(e.Error);
            };

            // Register a handler for the SaveEditsFailed event
            fl1.SaveEditsFailed += (sender, e) =>
            {
                // Report failure
                Console.WriteLine(e.Error);
            };


            Console.WriteLine("Calling Initialize on Thread " + d.Thread.ManagedThreadId.ToString());

            // Call Initialize method (in a map/UI scenario this call would be handled by the Map).
            fl1.Initialize();

            // Need to call Dispatcher.Run 
            Dispatcher.Run();
            
        }
    }
}


Cheers

Mike
0 Kudos
RobertZargarian
Deactivated User
Hi,
It works fine. Thank you for taking time to make this work.
I am sure code sample will be grate help for developers who want to implement this concept.

Regards
Robert
0 Kudos