Replies: 5 comments
-
Removing OnApplyChangesFailed with OnApplyChangesConflictOccured and OnApplyChangesErrorOccuredThis version introduces the management of errors when changes are applied.
OnApplyChangesConflictOccuredBasically This new feature add some new features and to improve the conflict handling, the conflict is not automatically retrieved from the underline data source. You can get it using remoteOrchestrator.OnApplyChangesConflictOccured(async acf =>
{
// Check conflict is correctly set
var conflict = await acf.GetSyncConflictAsync();
var localRow = conflict.LocalRow;
var remoteRow = conflict.RemoteRow;
// Client should wins
acf.Resolution = ConflictResolution.ClientWins;
}); You have now a new enumeration OnApplyChangesErrorOccuredEach time a row is in error (due to various reasons as Unique constraint failed or ForeignKeys constraint failed or any other failure that could happens), the row in error will be logged locally in a
But you can the way the error is handled: A new interceptor has been implemented when an error has occurred during the /// <summary>
/// Determines what kind of action should be taken when an error is raised from the datasource
/// during an insert / update or delete command
/// </summary>
public enum ErrorResolution
{
/// <summary>
/// Ignore the error and continue to sync. Error will be stored
/// locally in a separate batch info file
/// <para>
/// Row is stored locally with a state of <see cref="SyncRowState.ApplyDeletedFailed"/>
/// or <see cref="SyncRowState.ApplyModifiedFailed"/> depending on the row state.
/// </para>
/// </summary>
ContinueOnError,
/// <summary>
/// Will try one more time once after all the others rows in the table.
/// <para>
/// If the error is raised again, it will be stored locally in a
/// separate batch info file with a state of <see cref="SyncRowState.ApplyDeletedFailed"/>
/// or <see cref="SyncRowState.ApplyModifiedFailed"/> depending on the row state.
/// </para>
/// </summary>
RetryOneMoreTime,
/// <summary>
/// Row is stored locally and will be applied again on next sync.
/// Row is stored locally with a state of <see cref="SyncRowState.RetryDeletedOnNextSync"/>
/// or <see cref="SyncRowState.RetryModifiedOnNextSync"/> depending on the row state.
/// </summary>
RetryOnNextSync,
/// <summary>
/// Considers the row as applied.
/// </summary>
Resolved,
/// <summary>
/// Throw the error. Default value.
/// </summary>
Throw
} Here is some examples with a foreign key constraint failure due to Parent column not available during the insert phase: Simulating a Foreign Key failure, using this TSQL script: BEGIN TRAN
ALTER TABLE [ProductCategory] NOCHECK CONSTRAINT ALL
INSERT [ProductCategory] ([ProductCategoryID], [ParentProductCategoryId], [Name]) VALUES (N'A', 'B', N'A Sub category')
INSERT [ProductCategory] ([ProductCategoryID], [ParentProductCategoryId], [Name]) VALUES (N'B', NULL, N'B Category');
ALTER TABLE [ProductCategory] CHECK CONSTRAINT ALL
COMMIT TRAN;
By default, the sync is rollback, if we are making a sync, row by row
ErrorResolution.Throw// ErrorResolution.Throw is the default resolution. No need to explicitly set it.
// It's done here for the demo explanation.
agent.LocalOrchestrator.OnApplyChangesErrorOccured(args =>
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"ERROR: {args.ErrorRow}");
Console.ResetColor();
args.Resolution = ErrorResolution.Throw;
});
var serverProvider = new SqlSyncProvider(DBHelper.GetDatabaseConnectionString("svProduct"));
serverProvider.UseBulkOperations = false;
var clientProvider = new SqlSyncProvider(DBHelper.GetDatabaseConnectionString("CliProduct"));
clientProvider.UseBulkOperations = false;
var setup = new SyncSetup("ProductCategory");
var options = new SyncOptions();
// Creating an agent that will handle all the process
var agent = new SyncAgent(clientProvider, serverProvider, options);
var result = await agent.SynchronizeAsync(setup);
Console.WriteLine(result); As you can see, error is raised, and sync is rollback. This is the classic behavior. You can see in the This file is never deleted. Please check regularly your By the way, you can load any (or all) batch info files using // Creating an agent that will handle all the process
var agent = new SyncAgent(clientProvider, serverProvider, options);
try
{
var result = await agent.SynchronizeAsync(setup);
Console.WriteLine(result);
}
catch (Exception)
{
// Loading all batch infos from tmp dir
var batchInfos = await agent.LocalOrchestrator.LoadBatchInfosAsync();
foreach (var batchInfo in batchInfos)
{
// Load all rows from error tables specifying the specific SyncRowState states
var allTables = agent.LocalOrchestrator.LoadTablesFromBatchInfoAsync(batchInfo,
SyncRowState.ApplyDeletedFailed | SyncRowState.ApplyModifiedFailed);
// Enumerate all rows in error
await foreach (var table in allTables)
foreach (var row in table.Rows)
Console.WriteLine(row);
}
} ErrorResolution.RetryOneMoreTimeSecond scenario: We are trying to apply again one more time the error row, using agent.LocalOrchestrator.OnApplyChangesErrorOccured(args =>
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"ERROR: {args.ErrorRow}");
Console.ResetColor();
args.Resolution = ErrorResolution.RetryOneMoreTime;
}); As you can see in the previous screenshot, a Foreign key constraint prevents the rows to be inserted until the Parent category is inserted ErrorResolution.RetryOnNextSyncThe idea here is to retry to sync the failed row (once again, stored locally) on the next sync. agent.LocalOrchestrator.OnApplyChangesErrorOccured(args =>
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"ERROR: {args.ErrorRow}");
Console.ResetColor();
args.Resolution = ErrorResolution.RetryOnNextSync;
}); For the purpose of the demo, we are doing 2 sync in a row: // Creating an agent that will handle all the process
var agent = new SyncAgent(clientProvider, serverProvider, options);
Console.ResetColor();
Console.WriteLine("FIRST SYNC:");
var result = await agent.SynchronizeAsync(setup);
Console.WriteLine(result);
Console.WriteLine("SECOND SYNC:");
result = await agent.SynchronizeAsync(setup);
Console.WriteLine(result); As you can see we have a first sync where we have a failed inserted row, that is applied on the next sync, even if we did not downloaded any rows from the server ErrorResolution.ResolvedThis resolution prevents the sync from failing and is assuming you are doing the necessaries actions to insert / update the row that has failed. agent.LocalOrchestrator.OnApplyChangesErrorOccured(args =>
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"ERROR: {args.ErrorRow}");
Console.ResetColor();
args.Resolution = ErrorResolution.Resolved;
}); As you explicitly said that the error row problem has been resolved, ErrorResolution.ContinueOnErrorAlmost same as agent.LocalOrchestrator.OnApplyChangesErrorOccured(args =>
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"ERROR: {args.ErrorRow}");
Console.ResetColor();
args.Resolution = ErrorResolution.ContinueOnError;
}); |
Beta Was this translation helpful? Give feedback.
-
Upgrade of scope_info tablesThis version is a major upgrade version. If you want to read a little bit on why Once upgraded, you will have 2 scopes tables on both sides Server:
Client
Basically, the two tables are identical on both sides.
Those two tables are created on both side and are identical.
As an example, here is a sync with 3 tables, 2 of them are filtered ( Here is a
As you can see, we don't have any more the last sync timestamp information here.
Each Here is the code used to have this result: var setup = new SyncSetup("ProductCategory", "Product", "Employee");
setup.Tables[productCategoryTableName].Columns.AddRange("ProductCategoryId", "Name", "rowguid", "ModifiedDate");
setup.Filters.Add("ProductCategory", "ProductCategoryId");
setup.Filters.Add("Product", "ProductCategoryId");
var pMount = new SyncParameters(("ProductCategoryId", "MOUNTB"));
var pRoad = new SyncParameters(("ProductCategoryId", "ROADFR"));
var agent = new SyncAgent(client.Provider, Server.Provider);
var r1 = await agent.SynchronizeAsync("v1", setup, pMount);
var r2 = await agent.SynchronizeAsync("v1", setup, pRoad); It's a big step moving forward for If you want to test this version, you can grab the branch ClientHistory |
Beta Was this translation helpful? Give feedback.
-
Upgrade to v0.9.6The upgrade is really tricky, especially from the client side. remoteOrchestrator.UpgradeAsync(progress, evaluationOnly) Do not hesitate to test the upgrade on both side (client ( Server sidePretty simple, you can make a classic upgrade as usual. Here is a detailed code to do so: var progress = new SynchronousProgress<ProgressArgs>(s =>
{
Console.WriteLine($"{s.ProgressPercentage:p}:
\t[{s?.Source[..Math.Min(4, s.Source.Length)]}] {s.TypeName}: {s.Message}");
});
var serverProvider = new SqlSyncProvider(serverCstring);
var syncOptions = new SyncOptions();
var remoteOrchestrator = new RemoteOrchestrator(serverProvider, syncOptions);
var needsUpgrade = await remoteOrchestrator.NeedsToUpgradeAsync();
if (needsUpgrade)
{
Console.WriteLine("----------------------");
Console.WriteLine($"Evaluation only ? : {evaluationOnly}");
Console.WriteLine("----------------------");
var (scopeInfos, scopeInfoClients) =
await remoteOrchestrator.UpgradeAsync(progress, evaluationOnly);
Console.WriteLine("----------------------");
Console.WriteLine($"Upgrade done.");
Console.WriteLine("----------------------");
Console.WriteLine("Get Scope Infos from the new scope_info table:");
foreach (var si in scopeInfos)
Console.WriteLine(si);
Console.WriteLine("----------------------");
Console.WriteLine("----------------------");
Console.WriteLine("Get Scope Info Clients from the new scope_info_client table (SHOULD BE EMPTY):");
foreach (var sic in scopeInfoClients)
Console.WriteLine(sic);
Console.WriteLine("----------------------"); Here is the output of this code: Client sideFirst of all, the Why ? Because I need you to call it with all your The new scope_info_client table basically needs your That's why, for the client I've created a special class called As an example, imagine you are
Here is the code to call to Upgrade to var progress = new SynchronousProgress<ProgressArgs>(s =>
{
Console.WriteLine($"{s.ProgressPercentage:p}: " +
$"\t[{s?.Source[..Math.Min(4, s.Source.Length)]}] {s.TypeName}: {s.Message}");
});
var clientProvider = new SqlSyncProvider(clientCstring);
var syncOptions = new SyncOptions();
var localOrchestrator = new LocalOrchestrator(clientProvider, syncOptions);
var needsUpgrade = await localOrchestrator.NeedsToUpgradeAsync();
if (needsUpgrade)
{
var entries = new List<ScopeInfoClientUpgrade>();
var entry = new ScopeInfoClientUpgrade
{
Parameters = new SyncParameters(("ProductCategoryId", new Guid("XXXX-XX"))),
ScopeName = "v1"
};
entries.Add(entry);
Console.WriteLine("----------------------");
Console.WriteLine($"Evaluation only ? : {evaluationOnly}");
Console.WriteLine("----------------------");
var (scopeInfos, scopeInfoClients) =
await localOrchestrator.UpgradeAsync(entries, progress, evaluationOnly);
Console.WriteLine("----------------------");
Console.WriteLine($"Upgrade done.");
Console.WriteLine("----------------------");
Console.WriteLine("Get Scope Infos from the new scope_info table:");
foreach (var si in scopeInfos)
Console.WriteLine(si);
Console.WriteLine("----------------------");
Console.WriteLine("----------------------");
Console.WriteLine("Get Scope Info Clients from the new scope_info_client table:");
foreach (var sic in scopeInfoClients)
Console.WriteLine(sic);
Console.WriteLine("----------------------");
} Here is the output of this code: |
Beta Was this translation helpful? Give feedback.
-
OnGetCommand
// Eeach time a command is called on remote side,
// We are making a redirection to "default" stored procedures, except for filters specific stored proc
agent.RemoteOrchestrator.OnGetCommand(args =>
{
if (args.Command.CommandType == CommandType.StoredProcedure)
{
switch (args.CommandType)
{
case DbCommandType.SelectInitializedChangesWithFilters:
case DbCommandType.SelectChangesWithFilters:
break;
default:
args.Command.CommandText = args.Command.CommandText.Replace("_filterproducts_", "_default_");
break;
}
Console.WriteLine(args.Command.CommandText);
}
}); Take a look at the discussion #789 for a complete example |
Beta Was this translation helpful? Give feedback.
-
TransactionModeYou can specify now a transaction mode when applying changes: public enum TransactionMode
{
/// <summary>
/// Default mode for transaction, when applying changes
/// </summary>
AllOrNothing,
/// <summary>
/// Each batch file will have its own transaction
/// </summary>
PerBatch,
/// <summary>
/// No transaction during applying changes. very risky
/// </summary>
None
} You can set the |
Beta Was this translation helpful? Give feedback.
-
Version v0.9.6
TL;DR;
IS NOT yet developedworks if you are already on version >=0.9.0
This version is the LAST MAJOR upgrade of
DMS
untilV1.0.0
.It features complete, and I will now spent time to stabilize and fix errors only.
No new features will be added to the framework until
V1.0.0
And of course NEED YOUR FEEDBACKS as well :)
Beta Was this translation helpful? Give feedback.
All reactions