question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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:closed
  • Created 3 months ago
  • Comments:35 (35 by maintainers)

github_iconTop GitHub Comments

1reaction
VagueGitcommented, Jul 10, 2023

Wow … thank you. When will this be available in the nuget?

1reaction
Mimetiscommented, Jul 8, 2023

This change in SyncController didn’t help

string tempDirectory = Path.GetTempPath();
string userBatchDirectory = Path.Combine(tempDirectory, databaseName);
webServerAgent.Options.BatchDirectory = userBatchDirectory;

Users are still seeing data from other companies.

Does DMS use Options.BatchDirectory on the server or is this option ignored on the server?

EDIT: As a workaround I am considering adding a DatabaseName column to the top level of the relational hierarchy and to lookup tables. Then apply a DMS SetupFilter to rows of data on the client and the server to filter on DatabaseName.

I’d appreciate any feedback on this workaround to prevent users from one company seeing data from another company’s server database.

Im working on it

Read more comments on GitHub >

github_iconTop Results From Across the Web

"Health" APP using wrong iPhone for input
If we understand correctly, it sounds like some Health and activity data is syncing between two iOS devices and you're sharing an Apple...
Read more >
My laptop has two users. Recently Google opens the ...
Recently Google opens the wrong account when the second user is using it ... Select 3 - Dot Menu> More tools> Clear browsing...
Read more >
What Is Data Synchronization and Why Is It Important?
Data synchronization is the ongoing process of synchronizing data between two or more devices and updating changes automatically between them to maintain ...
Read more >
OneDrive shows "You're syncing a different account" error
How to fix "You're syncing a different account" error in OneDrive for Windows · Press the Windows key + R to open the...
Read more >
Salesforce data sync error: Name or Email has already ...
Question I'm getting an error message saying Name has already been taken (account sync) or Email has already been taken (contact or lead......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found