Skip to main content

Overview

This guide covers the essential changes needed to migrate your Ditto C# app from v4 to v5. The main architectural shift is moving from identity-based initialization to a four-phase configuration model with async/await APIs. Also required: v5 uses DQL (Ditto Query Language) for all data operations. See the DQL Migration Guide for query migration steps.

AI Agent Prompt

Use this prompt when working with an AI coding assistant to migrate your Ditto C# app from v4 to v5.

Ditto Instance Initialization

v5 separates initialization into four distinct phases for better clarity and control:
These changes provide:
  • Clear separation of connection, initialization, and authentication concerns
  • Async initialization prevents blocking the main thread
  • No workarounds needed (transport config, DQL strict mode, v3 sync disable)
  • Type-safe configuration with compile-time validation
Initialization Migration Steps
1

Replace Initialization

Replace new Ditto() constructor with await Ditto.OpenAsync(config).
Key changes:
  • Use DittoConfig instead of DittoIdentity
  • appId β†’ databaseId
  • .OnlinePlayground β†’ new DittoConfigConnect.Server(new Uri(...))
  • .OfflinePlayground β†’ new DittoConfigConnect.SmallPeersOnly(privateKey: null)
  • Add await for async initialization
  • Remove DisableSyncWithV3(), and DQL strict mode queries
2

Update Authentication

Set authentication handler separately after initialization.
Key changes:
  • Authentication now uses DittoAuthenticationExpirationHandler async delegate
  • Delegate signature: Task DittoAuthenticationExpirationHandler(Ditto ditto, TimeSpan timeUntilExpiration)
  • First parameter is the Ditto instance β€” access auth via ditto.Auth.LoginAsync()
  • Second parameter is TimeSpan β€” check timeUntilExpiration == TimeSpan.Zero for initial auth
  • Handler called when auth required (TimeSpan.Zero) or token expiring (> TimeSpan.Zero)
  • Use DittoAuthenticationProvider.Development for playground tokens
  • Custom auth: new DittoAuthenticationProvider("your-provider")

Additional Changes

DQL Strict Mode Behavior Change

Breaking Change: v5 defaults to DQL_STRICT_MODE=false, which fundamentally changes how DQL queries behave.
  • v4 default: Objects treated as REGISTER (whole-object replacement)
  • v5 default: Objects treated as MAP (field-level merging)
This affects the behavior of all DQL SELECT, INSERT, and UPDATE operations.
Choose the appropriate migration path based on your current v4 configuration:
If you’re currently using the v4 default (DQL_STRICT_MODE=true), you must explicitly set strict mode to true in v5 before starting sync or executing any queries.
Failing to set strict mode will cause objects to merge at the field level instead of replacing entirely, which can result in unexpected data behavior and perceived data loss.
To migrate to DQL_STRICT_MODE=false (the new v5 default), contact Ditto Customer Support for guidance.
If you explicitly set DQL_STRICT_MODE=false in v4, no changes are required.v5 uses DQL_STRICT_MODE=false as the default, so your existing DQL queries will behave identically. You can upgrade freely.Optional: You can remove the explicit ALTER SYSTEM SET DQL_STRICT_MODE = false statement in v5 since this is now the default behavior.
The Legacy Query Builder has been removed in v5. All queries must be converted to DQL before upgrading.Good news: Legacy Query Builder functionality has 1:1 support with DQL_STRICT_MODE=false (the v5 default), making migrations straightforward.Migration Steps:
  1. In v4: Set DQL_STRICT_MODE=false after initialization:
  2. Convert all queries from Legacy Query Builder to DQL See the C# Legacy→DQL Migration Guide for detailed conversion examples.
  3. Upgrade to v5 No DQL configuration changes requiredβ€”v5 defaults to DQL_STRICT_MODE=false.
For additional guidance or questions, contact Ditto Customer Support.

DittoSyncPermissions (MAUI only)

Back Pressure (RegisterObserver with signalNext)

Use the Action<DittoQueryResult, Action> overload of RegisterObserver for manual back-pressure control. The signalNext Action must be called when your handler is ready for the next update. Coalescing: changes that occur while your handler executes are coalesced β€” you get the latest state.
The signalNext parameter is a plain Action (no named type).

Disk Usage

Ditto Logger Changes

In v5, you now set logging enabled or disabled using the DittoLogger.IsEnable property.

Namespace Changes

Types have moved to new namespaces in v5:

Property changes

API Renames


APIs Removed


New APIs in v5

  • Ditto.Sync.Start() / Ditto.Sync.Stop() β€” replaces Ditto.StartSync() / StopSync()
  • Ditto.Version (static) β€” replaces Ditto.SDKVersion
  • DittoAuthenticationExpirationHandler β€” async delegate replacing IDittoAuthenticationDelegate
  • DittoStore.TransactionAsync() β€” async DQL transaction using DittoTransaction
  • DittoStoreObserver β€” replaces DittoLiveQuery
  • DittoSyncSubscription β€” replaces DittoSubscription; exposes QueryString, QueryArguments, Cancel()
  • DittoQueryResultItem β€” new type for single DQL result row with .Value property
  • DittoSyncPermissions (MAUI) β€” RequestPermissionsAsync() for Bluetooth/Wi-Fi permissions
  • DittoDiskUsageItem β€” replaces DittoDiskUsageChild;
  • DittoDiskUsage.Item (property) β€” replaces Exec() method

Migration Checklist

Update NuGet Package

  • Update Ditto NuGet package to v5.x

Fix Namespace Imports

  • Add using DittoSDK.Auth; where auth types are used
  • Add using DittoSDK.Exceptions; where DittoException is caught
  • Add using DittoSDK.Logging; where DittoLogger or DittoLogLevel are used
  • Update any other namespace references per compiler errors

Initialization

  • Replace new Ditto() with await Ditto.OpenAsync(config)
  • Create DittoConfig with databaseId and connect mode
  • Add await for async initialization
  • Update .OnlinePlayground β†’ new DittoConfigConnect.Server(new Uri(...))
  • Update .OfflinePlayground β†’ new DittoConfigConnect.SmallPeersOnly(privateKey: null)
  • Remove ALTER SYSTEM SET DQL_STRICT_MODE = false queries (this is now the v5 default); if your app used the v4 default (strict mode = true), add ALTER SYSTEM SET DQL_STRICT_MODE = true BEFORE Sync.Start() to maintain v4 behavior

Update Sync API

  • Replace ditto.StartSync() β†’ ditto.Sync.Start()
  • Replace ditto.StopSync() β†’ ditto.Sync.Stop()
  • Remove DisableSyncWithV3() calls

Update Authentication

  • Replace IDittoAuthenticationDelegate implementations with DittoAuthenticationExpirationHandler async delegate
  • Replace LoginWithToken() / Login() / LoginWithCredentials() β†’ LoginAsync()
  • Set ExpirationHandler delegate after initialization
  • Use DittoAuthenticationProvider.Development for playground tokens
  • Remove authentication from identity configuration

Observers

  • Replace DittoLiveQuery / .ObserveLocal() with DittoStoreObserver / Store.RegisterObserver()
  • Observer callbacks receive DittoQueryResult with .Items property
  • Use observer.Cancel() for cleanup (replaces liveQuery.Stop()), when you no longer need to receive updates.
  • If the query result processing is dispatched and unawaited, use Action<DittoQueryResult, Action> signalNext overload for manual back-pressure control

Breaking Changes

  • Update PeerKeyString β†’ PeerKey
  • Update IsDittoCloudConnected β†’ IsConnectedToDittoServer
  • Replace DittoStore.Write() β†’ DittoStore.TransactionAsync() (callback uses DittoTransaction, not DittoWriteTransaction)
  • Add using DittoSDK.Auth;, using DittoSDK.Exceptions; as needed
  • UpdateTransportConfig() callback parameter changed from Action<DittoTransportConfig> to Action<DittoTransportConfigBuilder>. Configuration options stay the same.

Update Renamed Types

  • Ditto.SiteId β†’ Removed (use peer identity from presence graph)
  • Ditto.SDKVersion β†’ Ditto.Version (static)
  • DittoDiskUsageChild β†’ DittoDiskUsageItem
  • DittoDiskUsage.Exec() β†’ DittoDiskUsage.Item (property)
  • DittoSubscription β†’ DittoSyncSubscription
  • DittoLiveQuery β†’ DittoStoreObserver

Verification

  • Build compiles with zero errors
  • DQL strict mode set correctly for your data model
  • Auth handler fires before first sync
  • Observers update UI immediately
  • No memory leaks on navigation
  • No Ditto deprecated API warnings

Common Pitfalls

  1. DQL_STRICT_MODE silent change: Default flipped. Objects merge field-by-field instead of replacing whole. Set DQL_STRICT_MODE=true BEFORE Sync.Start() if migrating existing data.
  2. Missing await on ExecuteAsync: ExecuteAsync returns Task<DittoQueryResult>. Forgetting await causes silent no-op.
  3. DittoSortDirection removed: Sort("field", DittoSortDirection.Ascending) won’t compile. Use ORDER BY field ASC in DQL.
  4. DittoIdentityType enum: Removed entirely. Code checking identity type won’t compile.
  5. LoginWithToken() removed: Must use LoginAsync(). The sync overloads are gone.
  6. Write() vs TransactionAsync(): The callback signature changed completely β€” the callback now receives DittoTransaction (not DittoWriteTransaction) and uses async DQL, not collection-based mutations.
  7. ExecuteAsync argument type: ExecuteAsync takes Dictionary<string, object> β€” anonymous objects like new { color = "blue" } are NOT accepted. Use new Dictionary<string, object> { ["color"] = "blue" }. Note: RegisterObserver and RegisterSubscription DO have overloads accepting anonymous object arguments.
  8. ObserveLocalWithNextSignal() removed from collection API: Manual back-pressure IS supported in v5 via the RegisterObserver overload that takes Action<DittoQueryResult, Action> where the second Action is the signalNext callback.