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:
And maybe adding a documentation for this workflow would be helpful, as I could not find anything about it.
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.
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;
}
}
Hi @MaximilianGlas ,
Esri.ArcGISRuntime.ArcGISRuntimeException: 'SQL error or missing database: no such table: GDB_DataChangesDatasets'
This is the error I am getting from the xamarin forms application, when I get to use ApplyDeltaAsync(geodatabase, geodbFilename) method available in runtime.
As you mentioned, is there any way to generate the delta geodatabase using runtime sdk from the app itself or how do we solve this issue, it cloudy to understand and lack of samples also. Could you please help me move forward?