data syncing to wrong users
See original GitHub issue#598 is now occurring daily for the small number of customers we have migrated to DMS 0.9.8. Our customers are companies. Each company has it’s own SQL Server database on our server accessed through our web api. Each company has multiple users. Each user syncs to a local SQLite database. Sync scopes and schema are identical across all databases. EDIT: All users in a company sync the same data. We do not use filters. EDIT: We do not use snapshots.
We have over 700 companies using our product with DMS 0.9.1. We occasionally hear that customers see data from other companies. This usually occurred when a new machine was being added to an existing company and the local SQLite database was being populated by sync from the server … lots of data being synced. Data seems to cross between companies that are syncing at the same time (ie same time-zone/business hours). Presumably these companies are syncing at the same time.
We have migrated a handful of companies to DMS 0.9.8. These companies are in regions where we do not have many customers in timezones outside our main customer concentrations, so the risk of collisions is lower. The three companies we service in one country have all reported instances of seeing another company’s data in the first couple of weeks of using our app with DMS 0.9.8.
The server is .NET 6.0. Setup is
public static void Main(string[] args)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
IServiceCollection services = builder.Services;
services.AddControllersWithViews();
services.AddRazorPages();
services.AddDistributedMemoryCache();
services.AddSession(options => options.IdleTimeout = TimeSpan.FromMinutes(30));
AddSyncServers(services, builder.Configuration);
builder.Logging.AddElmahIo(options =>
{
string elmahApiKey = builder.Configuration.GetValue<string>("ElmahIo:ApiKey");
string elmahLogId = builder.Configuration.GetValue<string>("ElmahIo:LogId");
options.ApiKey = elmahApiKey;
options.LogId = new Guid(elmahLogId);
});
WebApplication app = builder.Build();
app.UseElmahIoExtensionsLogging();
app.UseRouting();
app.UseSession();
app.UseStaticFiles();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapRazorPages();
});
app.Run();
}
static void AddSyncServers(IServiceCollection services, ConfigurationManager config)
{
SyncOptions syncOptions = new SyncOptionsShared().GetSyncOptionsShared();
string noDatabaseConnectionString = null;
Helpers.DatabaseHelper databaseHelper = new(config);
noDatabaseConnectionString = databaseHelper.DatabaseConnection(database: string.Empty);
SyncScopes syncScopes = new SyncScopes();
string scopeName = SyncScopes.All;
SyncSetup syncSetup = syncScopes.GetSyncSetup(scopeName);
services.AddSyncServer<SqlSyncChangeTrackingProvider>(connectionString: noDatabaseConnectionString, scopeName: scopeName, setup: syncSetup, options: syncOptions);
}
Could the static declaration be the issue? Note the connection string is null. This is set during sync.
[Route("api/[controller]")]
[Route("api/[controller]/[action]")]
[ApiController]
public class SyncController : ControllerBase
{
private IEnumerable<WebServerAgent> webServerAgents;
private readonly IWebHostEnvironment env;
private readonly ILogger _logger;
private readonly IConfiguration _config;
// Injected thanks to Dependency Injection
public SyncController(IEnumerable<WebServerAgent> webServerAgents,
IWebHostEnvironment env, IConfiguration config, ILogger<ElmahIoLogger> logger)
{
_config = config;
_logger = logger;
this.webServerAgents = webServerAgents;
this.env = env;
this._config = config;
}
public async Task Post([FromHeader] string databaseName, [FromHeader] string token)
{
try
{
UserAccountHelper userAccountHelper = new UserAccountHelper(_config);
bool isAuthorised = await userAccountHelper.IsAuthorisedAsync(databaseName, token);
if (!isAuthorised)
throw new UnauthorizedAccessException();
string scopeName = HttpContext.GetScopeName();
WebServerAgent webServerAgent = webServerAgents.FirstOrDefault(
c => c.ScopeName == scopeName);
DatabaseHelper databaseHelper = new(_config);
webServerAgent.Provider.ConnectionString = databaseHelper.DatabaseConnection(databaseName);
await webServerAgent.HandleRequestAsync(HttpContext).ConfigureAwait(false);
}
catch (Exception ex)
{
if (HttpContext is not null)
ex.Ship(HttpContext);
throw;
}
}
}
// the method that returns the connection string for the request database
public string DatabaseConnection(string database)
{
SqlConnectionStringBuilder sqlStrBldr = new();
sqlStrBldr.DataSource = _config.GetValue<string>("Database:DataSource");
sqlStrBldr.UserID = _config.GetValue<string>("Database:UserID");
sqlStrBldr.Password = _config.GetValue<string>("Database:Password");
sqlStrBldr.IntegratedSecurity = false;
sqlStrBldr.PersistSecurityInfo = false;
sqlStrBldr.ConnectRetryCount = 1;
sqlStrBldr.ConnectRetryInterval = 10;
sqlStrBldr.MultipleActiveResultSets = true;
sqlStrBldr.TrustServerCertificate = true;
sqlStrBldr.InitialCatalog = database;
return sqlStrBldr.ConnectionString;
}
The sync agent is configured on the client like this
static async Task<SyncAgent> ConfigureSyncAgentAsync(SyncType syncType = SyncType.Normal)
{// switch to row at a time processing on foreign key constraint error
// /https://github.com/Mimetis/Dotmim.Sync/discussions/783#discussioncomment-3379875
SyncOptions syncOptions = new SyncOptionsShared().GetSyncOptionsShared();
syncOptions.DisableConstraintsOnApplyChanges = true; // disable on client only during sync
SqliteSyncProvider clientProvider = GetSqliteSyncProvider(sync: true);
WebRemoteOrchestrator webRemoteOrchestrator = GetWebRemoteOrchestrator();
SyncAgent agent = new SyncAgent(clientProvider, webRemoteOrchestrator, syncOptions);
ConfigureErrorHanding(agent.LocalOrchestrator);
// https://github.com/Mimetis/Dotmim.Sync/issues/922#issuecomment-1369540710
await ConfigureOutdatedAsync(agent.LocalOrchestrator);
return agent;
}
The remote orchestrator for the sync agent is configured to carry credentials in the request header
static WebRemoteOrchestrator GetWebRemoteOrchestrator()
{
string serviceUri = $"{SyncApi}";
HttpClient httpClient = UserAccountService.HttpClientAuthorize();
httpClient.Timeout = TimeSpan.FromMinutes(20);
WebRemoteOrchestrator webRemoteOrchestrator = new WebRemoteOrchestrator(serviceUri, client: httpClient, maxDownladingDegreeOfParallelism: 4);
webRemoteOrchestrator.HttpClient.Timeout = TimeSpan.FromMinutes(20);
//ConfigureErrorLogging(webRemoteOrchestrator);
return webRemoteOrchestrator;
}
// the client authorised for this database
internal static HttpClient HttpClientAuthorize()
{// server to check if token authorized to access db
HttpClient httpClient = InternetHelper.HttpClientWithCertificateCustomValidationCallback();
httpClient.DefaultRequestHeaders.Add(nameof(DataService.DatabaseName), $"{DataService.DatabaseName}");
httpClient.DefaultRequestHeaders.Add(nameof(token), token);
return httpClient;
}
Please tell me I’ve done something obviously wrong.
Issue Analytics
- State:
- Created 3 months ago
- Comments:35 (35 by maintainers)

Top Related StackOverflow Question
Wow … thank you. When will this be available in the nuget?
Im working on it