CoreSync is a .NET library for bidirectional data synchronization between databases. It supports SQLite, SQL Server, and PostgreSQL, letting you keep multiple database instances in sync — whether they're on the same machine or communicating over HTTP.
- Bidirectional sync between any supported database combination (e.g. SQLite ↔ SQL Server, PostgreSQL ↔ SQLite, etc.)
- Automatic change tracking — detects inserts, updates, and deletes with version-based anchors
- Conflict resolution — built-in strategies (Skip or ForceWrite) with per-item customization
- Filtered sync — synchronize a subset of data using parameterized queries
- HTTP transport — sync over the network with ASP.NET Core server endpoints and a resilient HTTP client (with Polly retries and MessagePack binary format)
- Multiple providers: SQLite, SQL Server (custom change tracking), SQL Server CT (native Change Tracking), PostgreSQL
CoreSync uses a version-based change tracking approach:
- Each database gets a unique Store ID (GUID)
- When you call
ApplyProvisionAsync(), CoreSync creates internal tracking tables that monitor inserts, updates, and deletes - SyncAgent orchestrates the sync: it pulls changes from one provider, applies them to the other, and vice versa
- Each side saves the last-known version of the other store, so the next sync only transfers what changed
┌──────────────┐ ┌──────────────┐
│ Local DB │ │ Remote DB │
│ (SQLite) │◄──── SyncAgent ──────► │ (SQL Server) │
│ │ 1. Upload changes │ │
│ │ 2. Download changes │ │
│ │ 3. Save anchors │ │
└──────────────┘ └──────────────┘
| Package | Database | Change Tracking |
|---|---|---|
CoreSync.Sqlite |
SQLite | Custom trigger-based |
CoreSync.SqlServer |
SQL Server | Custom trigger-based |
CoreSync.SqlServerCT |
SQL Server | Native Change Tracking |
CoreSync.PostgreSQL |
PostgreSQL | Custom trigger-based |
HTTP packages for remote sync over the network:
| Package | Description |
|---|---|
CoreSync.Http.Server |
ASP.NET Core endpoints for hosting a sync server |
CoreSync.Http.Client |
HTTP client with resilience (Polly) and binary format (MessagePack) |
Install the NuGet packages for your scenario. For example, to sync between SQLite (local) and SQL Server (remote):
dotnet add package CoreSync.Sqlite
dotnet add package CoreSync.SqlServerusing CoreSync.Sqlite;
using CoreSync.SqlServer;
// 1. Configure the local SQLite provider
var localConfig = new SqliteSyncConfigurationBuilder("Data Source=local.db")
.Table("Users")
.Table("Posts")
.Table("Comments")
.Build();
var localProvider = new SqliteSyncProvider(localConfig);
// 2. Configure the remote SQL Server provider
var remoteConfig = new SqlSyncConfigurationBuilder("Server=.;Database=MyApp;Trusted_Connection=true")
.Table("Users")
.Table("Posts")
.Table("Comments")
.Build();
var remoteProvider = new SqlSyncProvider(remoteConfig);
// 3. Apply provision (creates change tracking infrastructure)
await localProvider.ApplyProvisionAsync();
await remoteProvider.ApplyProvisionAsync();
// 4. Synchronize
var syncAgent = new SyncAgent(localProvider, remoteProvider);
await syncAgent.SynchronizeAsync();That's it — all changes made on either side will be transferred to the other.
When both sides modify the same record, you control what happens:
await syncAgent.SynchronizeAsync(
conflictResolutionOnRemoteStore: ConflictResolution.Skip, // ignore conflicts on remote
conflictResolutionOnLocalStore: ConflictResolution.ForceWrite // overwrite local with remote
);Or use a per-item delegate for fine-grained control:
await syncAgent.SynchronizeAsync(
conflictResolutionOnRemoteFunc: (item) =>
{
if (item.TableName == "Posts")
return ConflictResolution.ForceWrite;
return ConflictResolution.Skip;
}
);Synchronize only a subset of data using filter parameters:
var remoteConfig = new SqlSyncConfigurationBuilder(connectionString)
.Table("Users",
selectIncrementalQuery: "SELECT * FROM Users WHERE Email = @userId")
.Table("Posts",
selectIncrementalQuery: "SELECT p.* FROM Posts p JOIN Users u ON p.Author = u.Email WHERE u.Email = @userId")
.Build();
await syncAgent.SynchronizeAsync(
remoteSyncFilterParameters: [new SyncFilterParameter("@userId", "user@test.com")]
);Server (ASP.NET Core):
// Program.cs
builder.Services.AddSingleton<ISyncProvider>(sp =>
{
var config = new SqlSyncConfigurationBuilder(connectionString)
.Table("Users")
.Table("Posts")
.Build();
return new SqlSyncProvider(config);
});
var app = builder.Build();
app.UseCoreSyncHttpServer("api/sync-agent");
app.Run();Client:
var httpClient = new SyncProviderHttpClient(options =>
{
options.HttpClientName = "SyncClient";
options.SyncControllerRoute = "api/sync-agent";
options.UseBinaryFormat = true; // MessagePack for efficiency
options.BulkItemSize = 100; // pagination size
});
var localProvider = new SqliteSyncProvider(localConfig);
var syncAgent = new SyncAgent(localProvider, httpClient);
await syncAgent.SynchronizeAsync();Providers can operate in different modes depending on their role:
// Server-side: only sends data
var remoteProvider = new SqlSyncProvider(config, ProviderMode.Remote);
// Client-side: only receives data
var localProvider = new SqliteSyncProvider(config, ProviderMode.Local);
// Default: bidirectional
var provider = new SqlSyncProvider(config, ProviderMode.Bidirectional);If you prefer SQL Server's built-in Change Tracking over custom triggers:
var config = new SqlServerCTSyncConfigurationBuilder(connectionString)
.ChangeRetentionDays(7) // how long change history is kept
.AutoCleanup(true)
.Table("Users")
.Table("Posts")
.Build();
var provider = new SqlServerCTProvider(config);
await provider.ApplyProvisionAsync();// Disable tracking for a specific table
await provider.DisableChangeTrackingForTable("Users");
// Re-enable it
await provider.EnableChangeTrackingForTable("Users");
// Clean up old tracking records
var syncVersion = await provider.ApplyRetentionPolicyAsync(minVersion: 4);
// Remove all provisioning (drop tracking tables)
await provider.RemoveProvisionAsync();CoreSync (core interfaces and SyncAgent)
├── CoreSync.Sqlite (SQLite provider)
├── CoreSync.SqlServer (SQL Server provider - custom CT)
├── CoreSync.SqlServerCT (SQL Server provider - native CT)
├── CoreSync.PostgreSQL (PostgreSQL provider)
├── CoreSync.Http (shared HTTP types)
├── CoreSync.Http.Server (ASP.NET Core sync endpoints)
└── CoreSync.Http.Client (resilient HTTP sync client)
| Type | Description |
|---|---|
ISyncProvider |
Interface for database sync providers — handles provisioning, change detection, and change application |
SyncAgent |
Orchestrates bidirectional sync between two providers |
SyncChangeSet |
A set of changes (inserts/updates/deletes) with source and target anchors |
SyncItem |
A single record change with table name, change type, and column values |
SyncAnchor |
Version marker for tracking sync progress between stores |
SyncConfiguration |
Base class for provider-specific configuration (tables, connection strings) |
SyncAgent.SynchronizeAsync()
│
├── 1. Local → Remote
│ ├── localProvider.GetChangesAsync(remoteStoreId, UploadOnly)
│ ├── remoteProvider.ApplyChangesAsync(localChanges, conflictFunc)
│ └── remoteProvider.SaveVersionForStoreAsync(localStoreId, version)
│
└── 2. Remote → Local
├── remoteProvider.GetChangesAsync(localStoreId, DownloadOnly)
├── localProvider.ApplyChangesAsync(remoteChanges, conflictFunc)
└── localProvider.SaveVersionForStoreAsync(remoteStoreId, version)
- Core library and providers: .NET Standard 2.0 (compatible with .NET Framework 4.6.1+ and .NET Core 2.0+)
- HTTP packages: .NET 8.0
A full sample application using .NET MAUI with MauiReactor is available at: https://github.com/adospace/mauireactor-core-sync
- Data Synchronization Primer — introductory article explaining the concepts behind CoreSync
MIT — Copyright (c) 2018 adospace