Does difference cursor work with branch versioning?

1795
8
Jump to solution
07-21-2020 02:03 PM
RyanMuller1
New Contributor II

I'm trying to use a DifferenceCursor on a utility network feature service geodatabase.

I am following the guide here

I am able to use the VersionManager class to open the default geodatabase and the version geodatabase and then open the same feature class from both of them. However, when I try to create a DifferenceCursor I get this error

"Object has no schema locks".

I dug into this a bit further via the VersionManagerServer rest endpoints.

If you go to https://<server_url>/rest/services/<feature_server>/VersionManagementServer/versions/<version_guid>/...

and try to run it you get the same error:

This can be overcome by running the startReading command 

Then you can go back to the differences endpoint, put the same sessionId in and it works.

It doesn't look like VersionManager does this first step of starting a read session.

Here's a snippet of the code I'm using.

ServiceConnectionProperties serviceConnectionProperties = new ServiceConnectionProperties(new Uri(config.Connection.UtilityServiceUri));
using (Geodatabase gdb = new Geodatabase(serviceConnectionProperties))
using (VersionManager versionManager = gdb.GetVersionManager())
{
	//here's where I was going to try calling the startReading operation directly off the ReST endpoint
	WebClient versionManagerClient = new WebClient()
	{
		BaseAddress = config.Connection.UtilityServiceUri + "/VersionManagementServer"
	};

	Version navigateVersion = versionManager.GetVersions().First(v => v.GetName() == config.ApplicationConfiguration.NavigateVersion);

	using (Geodatabase navigateGdb = navigateVersion.Connect())
	{
		foreach (string featureClass in config.FeatureClasses)
		{
			 //service feature classes have the layer number prefix i.e L0PipelineLine
			//so we use this function to translate between human readable and the actual layer name
			FeatureClassDefinition serviceFCDefinition = GetServiceFeatureClassName(featureClass);
			string serviceFeatureClassName = serviceFCDefinition.GetName();

			FeatureClass defaultFc = gdb.OpenDataset<FeatureClass>(serviceFeatureClassName);
			FeatureClass navigateFc = navigateGdb.OpenDataset<FeatureClass>(serviceFeatureClassName);

			//this line gives me "Object has no schema locks"
			DifferenceCursor diffCursor = navigateFc.Differences(defaultFc, DifferenceType.Insert);
			while (diffCursor.MoveNext())
			{
				using (Row row = diffCursor.Current)
				{
					log.Append(String.Format("Post Change: insert into geometry_changes ({0}) values()",
						String.Join(", ", outputFields)));

				}
			}
		}
	}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
1 Solution

Accepted Solutions
RichRuh
Esri Regular Contributor

Hi Ryan,

Unfortunately, I'm still not able to reproduce this in CoreHost.  My stripped down test code (which does nothing interesting with the results) is attached.  You could change out the service name and table name and see if it works on your system.  

Let me know how it turns out,

--Rich

using ArcGIS.Core.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using ArcGIS.Core.Data;
using Version = ArcGIS.Core.Data.Version;


namespace CoreHostSandbox
{


  class Program
  {
    //[STAThread] must be present on the Application entry point
    [STAThread]
    static void Main(string[] args)
    {
      //Call Host.Initialize before constructing any objects from ArcGIS.Core
      Host.Initialize();

      Sandbox mySandbox = new Sandbox();
      mySandbox.Do();

    }
  }

  class Sandbox
  {
    const string FeatureServiceName = @"https://richruh.esri.com/server/rest/services/TableTestBranchVersioning/FeatureServer";
    const string EditVersionName = "VersionDifferencesTestSuiteVersion";
    const string CityTableName = "L0Cities";
    const DifferenceType DifferenceTypeToCheck = DifferenceType.Insert;

    private Version _defaultVersion = null;
    private Geodatabase _defaultGeodatabase = null;
    private FeatureClass _defaultCitiesFeatureClass = null;

    private Version _editVersion = null;
    private Geodatabase _editGeodatabase = null;
    private FeatureClass _editCitiesFeatureClass = null;

    public void Do()
    {
      ServiceConnectionProperties serviceConnectionProperties = new ServiceConnectionProperties(new Uri(FeatureServiceName));
      using (Geodatabase geodatabase = new Geodatabase(serviceConnectionProperties))
      {
        TestSuiteSetup(geodatabase);
        try
        {
          // Fetch all of the ObjectIDs found in the cursor
          List<long> differences = new List<long>();

          using (DifferenceCursor differenceCursor = _editCitiesFeatureClass.Differences(_defaultCitiesFeatureClass, DifferenceTypeToCheck))
          {
            while (differenceCursor.MoveNext())
            {
              differences.Add(differenceCursor.ObjectID);
              if (DifferenceTypeToCheck != DifferenceType.DeleteNoChange)
              {
                differenceCursor.Current.Dispose();
              }
            }
          }
          Console.WriteLine("Differences= " + differences.ToString());
        }
        catch (Exception e)
        {
          Console.WriteLine("FAIL: Exception thrown: " + e.Message + "\n");
        }
        TestSuiteTeardown();
      }
    }

    //TestSuiteSetup
    private void TestSuiteSetup(Geodatabase geodatabase)
    {

      using (VersionManager versionManager = geodatabase.GetVersionManager())
      using (Version currentVersion = versionManager.GetCurrentVersion())
      {
        // Connect to the default version
        _defaultVersion = GetDefaultVersion(currentVersion);
        _defaultGeodatabase = _defaultVersion.Connect();
        _defaultCitiesFeatureClass = _defaultGeodatabase.OpenDataset<FeatureClass>(CityTableName);

        // Connect to the edit version
        _editVersion = versionManager.GetVersions().First(v => v.GetName().Contains(EditVersionName));
        _editGeodatabase = _editVersion.Connect();
        _editCitiesFeatureClass = _editGeodatabase.OpenDataset<FeatureClass>(CityTableName);
      }
    }

    //TestSuiteTeardown - clean up the stuff we created in TestSuiteSetup
    private void TestSuiteTeardown()
    {
      _defaultVersion.Dispose();
      _defaultGeodatabase.Dispose();
      _defaultCitiesFeatureClass.Dispose();
      _editCitiesFeatureClass.Dispose();
      _editGeodatabase.Dispose();
      _editVersion.Dispose();
    }

    private Version GetDefaultVersion(Version version)
    {
      Version parentVersion = version.GetParent();
      if (parentVersion == null)
      {
        return version;
      }
      else
      {
        return parentVersion;
      }
    }
  }
}

View solution in original post

8 Replies
RichRuh
Esri Regular Contributor

Hi Ryan,

I don't see any problems with your code.  I set up some similar code and stepped through it with a debugger and Fiddler.  On my machine, calling versionManager.GetVersions() is calling startReading on every version on the service.

Do you have fiddler installed, and, if so, could you tell me what output is created by that call on your machine?

Some other general questions:

1. What version of Pro are you running?

2. Are all of the version names/tables names, etc. correct? (i.e., you don't have any unexpected null pointers in your code)

3. Can you do a version differences with this version in the Pro user interface?

Thanks,

--Rich

RyanMuller1
New Contributor II

1. I'm using ArcGIS Pro 2.5.2

2. All version names and tables are correct. I am able to open a RowCursor on both the versioned and default feature classes

3. I can see version changes in the Pro interface

In Fiddler, I can see the call to startReading when I open the differences tab in Pro.

I do not see any such call to startReading in Fiddler when I try my code. setting a breakpoint on the call to GetVersions shows it calling the following endpoints

VersionManagementServer/versionInfos

FeatureServer/relationships

VersionManagementServer/versions/GUID-To-Version (does this for all versions since I'm calling version.GetName() on them)

on line 26 in the code above where I create the difference cursor, I get this.

One thing different that I'm doing compared to the Pro SDK documentation, is creating the geodatabase using ServiceConnectionProperties and the url of the feature service.

 ServiceConnectionProperties serviceConnectionProperties = new ServiceConnectionProperties(
     new Uri(https://<server_url>/server/rest/services/<feature_service_name>/FeatureServer));‍‍‍‍‍
0 Kudos
RyanMuller1
New Contributor II

I was trying to do this in CoreHost. After moving my code over to a Pro addin module, it creates the difference cursor just fine. Do you know if CoreHost supports difference cursors and/or what I might need to do diffrently to get them to work in a CoreHost application?

0 Kudos
RichRuh
Esri Regular Contributor

Oh. That is a very interesting development.  While it certainly should work in CoreHost, my tests in this case also run in an add-in.  Let me try it in CoreHost and get back to you.

--Rich

RichRuh
Esri Regular Contributor

Hi Ryan,

Unfortunately, I'm still not able to reproduce this in CoreHost.  My stripped down test code (which does nothing interesting with the results) is attached.  You could change out the service name and table name and see if it works on your system.  

Let me know how it turns out,

--Rich

using ArcGIS.Core.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using ArcGIS.Core.Data;
using Version = ArcGIS.Core.Data.Version;


namespace CoreHostSandbox
{


  class Program
  {
    //[STAThread] must be present on the Application entry point
    [STAThread]
    static void Main(string[] args)
    {
      //Call Host.Initialize before constructing any objects from ArcGIS.Core
      Host.Initialize();

      Sandbox mySandbox = new Sandbox();
      mySandbox.Do();

    }
  }

  class Sandbox
  {
    const string FeatureServiceName = @"https://richruh.esri.com/server/rest/services/TableTestBranchVersioning/FeatureServer";
    const string EditVersionName = "VersionDifferencesTestSuiteVersion";
    const string CityTableName = "L0Cities";
    const DifferenceType DifferenceTypeToCheck = DifferenceType.Insert;

    private Version _defaultVersion = null;
    private Geodatabase _defaultGeodatabase = null;
    private FeatureClass _defaultCitiesFeatureClass = null;

    private Version _editVersion = null;
    private Geodatabase _editGeodatabase = null;
    private FeatureClass _editCitiesFeatureClass = null;

    public void Do()
    {
      ServiceConnectionProperties serviceConnectionProperties = new ServiceConnectionProperties(new Uri(FeatureServiceName));
      using (Geodatabase geodatabase = new Geodatabase(serviceConnectionProperties))
      {
        TestSuiteSetup(geodatabase);
        try
        {
          // Fetch all of the ObjectIDs found in the cursor
          List<long> differences = new List<long>();

          using (DifferenceCursor differenceCursor = _editCitiesFeatureClass.Differences(_defaultCitiesFeatureClass, DifferenceTypeToCheck))
          {
            while (differenceCursor.MoveNext())
            {
              differences.Add(differenceCursor.ObjectID);
              if (DifferenceTypeToCheck != DifferenceType.DeleteNoChange)
              {
                differenceCursor.Current.Dispose();
              }
            }
          }
          Console.WriteLine("Differences= " + differences.ToString());
        }
        catch (Exception e)
        {
          Console.WriteLine("FAIL: Exception thrown: " + e.Message + "\n");
        }
        TestSuiteTeardown();
      }
    }

    //TestSuiteSetup
    private void TestSuiteSetup(Geodatabase geodatabase)
    {

      using (VersionManager versionManager = geodatabase.GetVersionManager())
      using (Version currentVersion = versionManager.GetCurrentVersion())
      {
        // Connect to the default version
        _defaultVersion = GetDefaultVersion(currentVersion);
        _defaultGeodatabase = _defaultVersion.Connect();
        _defaultCitiesFeatureClass = _defaultGeodatabase.OpenDataset<FeatureClass>(CityTableName);

        // Connect to the edit version
        _editVersion = versionManager.GetVersions().First(v => v.GetName().Contains(EditVersionName));
        _editGeodatabase = _editVersion.Connect();
        _editCitiesFeatureClass = _editGeodatabase.OpenDataset<FeatureClass>(CityTableName);
      }
    }

    //TestSuiteTeardown - clean up the stuff we created in TestSuiteSetup
    private void TestSuiteTeardown()
    {
      _defaultVersion.Dispose();
      _defaultGeodatabase.Dispose();
      _defaultCitiesFeatureClass.Dispose();
      _editCitiesFeatureClass.Dispose();
      _editGeodatabase.Dispose();
      _editVersion.Dispose();
    }

    private Version GetDefaultVersion(Version version)
    {
      Version parentVersion = version.GetParent();
      if (parentVersion == null)
      {
        return version;
      }
      else
      {
        return parentVersion;
      }
    }
  }
}
RyanMuller1
New Contributor II

Thank you Rich, I was trying to do this in QueuedWorker.Run. Removing that allowed it to work. The documentation mentions that the Differences cursor must be created on the MCT so I thought the same would apply in a CoreHost application. It appears that it does not.

0 Kudos
RyanMuller1
New Contributor II

So after thinking about this a little bit more I realize I don't understand the threading model in CoreHost. Since all CoreHost applications must be in single threaded apartment (STA) mode I assumed that QueuedWorker.Run was there so you didn't block the main thread and break the STA contract which, very basically is to not block the thread and not to deadlock. Why would attempting to create a difference cursor in the MTA CLR threadpool (I'm assuming that's what QueuedWorker.Run does)  cause it to behave differently than in the STA?

If I wrap my function in QueuedWorker.Run it continues to give me the "Object has no schema locks" error. Should I just not use QueuedWorker when creating CoreHost/STAThread applications? 

0 Kudos
RichRuh
Esri Regular Contributor

Hi Ryan,

If you are creating a single-threaded application, like a command-line app, you shouldn't use QueuedWorker. QueuedWorker really only exists to allow WPF standalone apps to run operations in the background (on a COM compatible thread) whilst allowing the UI to remain responsive. 

Does this make sense?

--Rich

0 Kudos