Where to get a delta geodatabase for ImportGeodatabaseDeltaAsync?

606
2
07-27-2017 08:54 AM
Occasional Contributor II

In the Runtime .NET API, I found a method named ImportGeodatabaseDeltaAsync.

Unfortunately, there is no documentation how to use this method in a full workflow. So I have to try many things.

The first parameter is

geodatabaseFileName

Type: System.String
The path and filename of geodatabase where delta is applied to.

It seems that it has to be a Runtime geodatabase. I could use my Runtime geodatabase anyway, which I have created with my Runtime client against an sync enabled ArcGIS Server (FeatureServer) service. So, this is no problem, as I could create it with the Runtime SDK method GenerateGeodatabase.

The second parameter is

deltaGeodatabaseFileName

Type: System.String
The path and filename of geodatabase where to import the delta from

This one could not be a normal Runtime geodatabase file, because you will get an exception in that case:

Esri.ArcGISRuntime.ArcGISRuntimeException: 'SQL error or missing database: no such table: GDB_DataChangesDatasets'

Obiviously the geodatase has a different structure.

I could not find any SDK functionality for creating a delta package, so I tried to create one directly from the REST API:

We created a replica first, added a new feature in the service and then called the synchronizeReplica operation. The result was a geodatabase file, which now has different schema as there exists some additional tables in it.

When using this geodatabase for the deltaGeodatabaseFileName Parameter, the ImportGeodatabaseDeltaAsync works.

My question now is:

  • Is there a way to create the delta geodatabase file with Runtime SDK?
  • As the synchronizeReplica REST operation is also used SyncGeodatabase SDK method, do I have to use some special parameters to generate it? But how?
  • Could I also generate the delta geodatabase with a desktop/python tool?
  • As first parameter (geodatabaseFileName), could I also use a desktop generated geodatabase (ArcGIS runtime content)?

And maybe adding a documentation for this workflow would be helpful, as I could not find anything about it.

2 Replies
New Contributor

Hi Maximilian Glas‌,

Probably too late for this, but check this out:

http://desktop.arcgis.com/en/arcmap/10.3/tools/data-management-toolbox/export-to-delta.htm

It creates a delta using a child replica geodatabase.

Reply
0 Kudos
MVP Regular Contributor

Also not sure if this is still a concern.

The way you generate the delta is with the rest API https://developers.arcgis.com/rest/services-reference/synchronize-replica.htm.  You could do through python or in C# with HttpClient.

This is some code that does in C#, most of everything is here, excluding some custom objects that define some of the basics which I think can be figured out.

//returns the Url of the delta file which is used by the calling method to download 
public async Task<string> SyncronizeReplicaAsync(ReplicaDefinition replicaDefinition)
{
try
{
ArcGISHttpClientHandler handler = new ArcGISHttpClientHandler { ArcGISCredential = await _portalManager.Credential(true) };

using ( var client = new HttpClient(handler) )
{
try
{
var featureServiceUrl = new Uri($"{FeatureServiceUrl(replicaDefinition)}?f=json");

var get = await client.GetStringAsync(featureServiceUrl);
}
catch (Exception)
{
_log.Warn($"Could not connect to Url: {FeatureServiceUrl(replicaDefinition)}");
return null;
}

//This gets the replica url wich is featureserviceUrl/replicaId
var requestUri = new Uri($"{FeatureServiceUrl(replicaDefinition)}/synchronizeReplica");

var parameters = new Dictionary<string, string>
{
{"f", "json"},
{"async", "true"},
{"dataFormat", "sqlite"},
{"replicaID", replicaDefinition.ReplicaId.ToLower()},
{"rollbackOnFailure", "false"},
{"syncLayers", await GetSyncLayersAsync(replicaDefinition)},
{"transportType", "esriTransportTypeUrl"}
};

var content = new FormUrlEncodedContent(parameters);

//This sends off the sync request to the server
var response = await client.PostAsync(requestUri, content);

if ( !(response.Content is ByteArrayContent byteArrayContent) ) return null;

var json = await byteArrayContent.ReadAsStringAsync();

var status = JsonConvert.DeserializeObject<StatusResponse>(json);
var resultUrl = await CheckJobAsync(replicaDefinition, status);

return resultUrl;
}
}
catch (Exception e)
{
_log.Error(e.Message, e);
return null;
}
}



// I have some custom objects that mimic responses and use json deserializer to convert to .net objects
private async Task<string> CheckJobAsync(ReplicaDefinition replicaDefinition, StatusResponse status)
{
JobResponse job = null;
ArcGISHttpClientHandler handler = null;

try
{
while ( true )
{
handler = new ArcGISHttpClientHandler {ArcGISCredential = await _portalManager.Credential(false)};
using (var client = new HttpClient(handler))
{
//This is a periodic check of the Job status
var requestUri = new Uri($"{status.StatusUrl}?f=json");
var json = await client.GetStringAsync(requestUri);

job = JsonConvert.DeserializeObject<JobResponse>(json);

// When job is complete the respence incluses the Url of the delta database
if ( job.JobStatus == JobStatus.Completed ) break;

if ( job.JobStatus == JobStatus.Failed )
{
_log.Warn($"Job failed: {replicaDefinition.Name}");
return null;
}

if ( job.JobStatus == JobStatus.Other )
{
_log.Warn($"Job unknown: {replicaDefinition.Name}");
return null;
}

await Task.Delay(TimeSpan.FromSeconds(5));
}
}
}
catch (ArcGISWebException e)
{
if ( handler?.ArcGISCredential is TokenCredential tokenCredential )
{
_log.Error($"{e.Message}: {tokenCredential.ExpirationDate?.LocalDateTime:hh:mm:ss}");
}
else
{
_log.Error(e.Message, e);
}

}
catch (Exception e)
{
_log.Error(e.Message, e);
}

return job?.ResultUrl;
}


// Gets the layer array required by the syncronizeReplica request
private async Task<string> GetSyncLayersAsync(ReplicaDefinition replicaDefinition)
{
try
{
var handler = new ArcGISHttpClientHandler { ArcGISCredential = await _portalManager.Credential(false) };
using ( var client = new HttpClient(handler){Timeout = TimeSpan.FromMinutes(5)} )
{
var requestUri = new Uri($"{FeatureServiceUrl(replicaDefinition)}/replicas/{replicaDefinition.ReplicaId}?f=json");
var response = await client.GetStringAsync(requestUri);

var jObject = JObject.Parse(response);
var syncLayers = JsonConvert.DeserializeObject<IEnumerable<SyncLayer>>(jObject["layerServerGens"].ToString()).ToArray();

foreach (var syncLayer in syncLayers)
{
syncLayer.SyncDirection = "download";
}

//Setting to allow to sync back beyond last sync time
// The serverGen parameter is actually the time stamp that the syncronizeReplica looks back into the past
// by default this is retrieved from the replica definition.
//This is logic to allow us to push that time back in case we need to go further to the past
if (_appSettings.DownloadSyncBack)
{
long time = (DateTimeOffset.UtcNow - TimeSpan.FromDays(_appSettings.DownloadDaysBack)).ToUnixTimeMilliseconds();

foreach (var syncLayer in syncLayers)
{
syncLayer.ServerGen = time;
}
}

var serializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};

string json = JsonConvert.SerializeObject(syncLayers, serializerSettings);

return json;
}
}
catch (Exception e)
{
_log.Error(e.Message, e);
return null;
}
}
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
Reply
0 Kudos