Replies: 6 comments 5 replies
-
OrchestratorsThe base code has been refactored to make things easier to handle, and more compliant with each core component role. Today a set of new public methods are accessible from both This lead to more public and independent methods you can use. You have now access to several methods for managing your sync architecture.
Here is some example using these methods (using the SQL Server AdventureWorks Database Script ) Get a table schemaThis method runs on any var provider = new SqlSyncProvider(serverConnectionString);
var options = new SyncOptions();
var setup = new SyncSetup(new string[] { "ProductCategory", "ProductModel", "Product" });
var orchestrator = new RemoteOrchestrator(provider, options, setup);
// working on the product Table
var productSetupTable = setup.Tables["Product"];
// Getting the table schema
var productTable = await orchestrator.GetTableSchemaAsync(productSetupTable);
foreach (var column in productTable.Columns)
Console.WriteLine(column);
Managing stored proceduresManaging Stored Procedures could be done using:
Creating a stored procedure could be done like this: var provider = new SqlSyncProvider(serverConnectionString);
var options = new SyncOptions();
var setup = new SyncSetup(new string[] { "ProductCategory", "ProductModel", "Product" });
var orchestrator = new RemoteOrchestrator(provider, options, setup);
// working on the product Table
var productSetupTable = setup.Tables["Product"];
var spExists = await orchestrator.ExistStoredProcedureAsync(productSetupTable, DbStoredProcedureType.SelectChanges);
if (!spExists)
await orchestrator.CreateStoredProcedureAsync(productSetupTable, DbStoredProcedureType.SelectChanges); Be careful, this stored procedure relies on a tracking table for table Creating a tracking tableContinuing on the last sample, we can create in the same way, the tracking table for table var provider = new SqlSyncProvider(serverConnectionString);
var options = new SyncOptions();
var setup = new SyncSetup(new string[] { "ProductCategory", "ProductModel", "Product" });
var orchestrator = new RemoteOrchestrator(provider, options, setup);
// working on the product Table
var productSetupTable = setup.Tables["Product"];
var spExists = await orchestrator.ExistTrackingTableAsync(productSetupTable);
if (!spExists)
await orchestrator.CreateTrackingTableAsync(productSetupTable); Droping a tracking table and a stored procedureNow we can drop this newly created stored procedure and tracking table: var trExists = await orchestrator.ExistTrackingTableAsync(productSetupTable);
if (trExists)
await orchestrator.DropTrackingTableAsync(productSetupTable);
var spExists = await orchestrator.ExistStoredProcedureAsync(productSetupTable, DbStoredProcedureType.SelectChanges);
if (spExists)
await orchestrator.DropStoredProcedureAsync(productSetupTable, DbStoredProcedureType.SelectChanges); |
Beta Was this translation helpful? Give feedback.
-
Interceptors - Change generated sync architectureAll interceptors have been refactored to be more useable and resolving some issues reported. Interceptors on
Let see a straightforward sample : Customizing a tracking table Adding a new column in a tracking tableThe idea here is to add a new column var provider = new SqlSyncProvider(serverConnectionString);
var options = new SyncOptions();
var setup = new SyncSetup(new string[] { "ProductCategory", "ProductModel", "Product" });
var orchestrator = new RemoteOrchestrator(provider, options, setup);
// working on the product Table
var productSetupTable = setup.Tables["Product"];
orchestrator.OnTrackingTableCreating(ttca =>
{
var addingID = $" ALTER TABLE {ttca.TrackingTableName.Schema().Quoted()} ADD internal_id varchar(10) null";
ttca.Command.CommandText += addingID;
});
var trExists = await orchestrator.ExistTrackingTableAsync(productSetupTable);
if (!trExists)
await orchestrator.CreateTrackingTableAsync(productSetupTable); Ok, now we need to customize the triggers to insert a correct value in the orchestrator.OnTriggerCreating(tca =>
{
string val;
if (tca.TriggerType == DbTriggerType.Insert)
val = "INS";
else if (tca.TriggerType == DbTriggerType.Delete)
val = "DEL";
else
val = "UPD";
var cmdText = $"UPDATE Product_tracking " +
$"SET Product_tracking.internal_id='{val}' " +
$"FROM Product_tracking JOIN Inserted ON Product_tracking.ProductID = Inserted.ProductID;";
tca.Command.CommandText += Environment.NewLine + cmdText;
});
var trgExists = await orchestrator.ExistTriggerAsync(productSetupTable, DbTriggerType.Insert);
if (!trgExists)
await orchestrator.CreateTriggerAsync(productSetupTable, DbTriggerType.Insert);
trgExists = await orchestrator.ExistTriggerAsync(productSetupTable, DbTriggerType.Update);
if (!trgExists)
await orchestrator.CreateTriggerAsync(productSetupTable, DbTriggerType.Update);
trgExists = await orchestrator.ExistTriggerAsync(productSetupTable, DbTriggerType.Delete);
if (!trgExists)
await orchestrator.CreateTriggerAsync(productSetupTable, DbTriggerType.Delete);
orchestrator.OnTriggerCreating(null); Here is the CREATE TRIGGER [dbo].[Product_insert_trigger] ON [dbo].[Product] FOR INSERT AS
SET NOCOUNT ON;
-- If row was deleted before, it already exists, so just make an update
UPDATE [side]
SET [sync_row_is_tombstone] = 0
,[update_scope_id] = NULL -- scope id is always NULL when update is made locally
,[last_change_datetime] = GetUtcDate()
FROM [Product_tracking] [side]
JOIN INSERTED AS [i] ON [side].[ProductID] = [i].[ProductID]
INSERT INTO [Product_tracking] (
[ProductID]
,[update_scope_id]
,[sync_row_is_tombstone]
,[last_change_datetime]
)
SELECT
[i].[ProductID]
,NULL
,0
,GetUtcDate()
FROM INSERTED [i]
LEFT JOIN [Product_tracking] [side] ON [i].[ProductID] = [side].[ProductID]
WHERE [side].[ProductID] IS NULL
UPDATE Product_tracking SET Product_tracking.internal_id='INS'
FROM Product_tracking
JOIN Inserted ON Product_tracking.ProductID = Inserted.ProductID; |
Beta Was this translation helpful? Give feedback.
-
Interceptors - Controlling data applyingYou can use Here is a quick example: First of all, making a full sync to be sure we have the same data between the server and one client var serverProvider = new SqlSyncProvider(serverConnectionString);
var clientProvider = new SqlSyncProvider(clientConnectionString);
// Tables involved in the sync process:
var tables = new string[] {"Product" };
// Creating an agent that will handle all the process
var agent = new SyncAgent(clientProvider, serverProvider, tables);
// First sync to have some rows on client
var s1 = await agent.SynchronizeAsync();
// Write results
Console.WriteLine(s1); Synchronization done.
Total changes uploaded: 0
Total changes downloaded: 295
Total changes applied: 295
Total resolved conflicts: 0
Total duration :0:0:7.324 Now let's imagine we are deleting some rows on the server var c = serverProvider.CreateConnection();
var cmd = c.CreateCommand();
cmd.Connection = c;
cmd.CommandText = "DELETE FROM Product WHERE ProductId >= 750 AND ProductId < 760";
c.Open();
cmd.ExecuteNonQuery();
c.Close(); Ok, now we are expecting to have around 10 rows to be deleted on the next sync on the client. // Do not delete product rows
agent.LocalOrchestrator.OnTableChangesApplying(args =>
{
if (args.State == DataRowState.Deleted && args.Changes.TableName == "Product")
{
Console.WriteLine($"Preventing deletion on {args.Changes.Rows.Count} rows.");
args.Cancel = true;
}
});
// Second sync
s1 = await agent.SynchronizeAsync();
// Write results
Console.WriteLine(s1); Second sync result:
As you can see, the client downloaded 10 rows to be deleted, but nothing has been applied. |
Beta Was this translation helpful? Give feedback.
-
First of all, it's not longer necessary to add Here is a sample of an output from a 1 st sync to a Web API, from the Client perspective:
This log is coming from a first sync where the BeginSession: 10:52:18.211 Session Begins [0c622a02-9c2d-4a39-bcd1-7888deb5a859].
ScopeLoading: 10:52:22.944 [localhost] Getting Schema. Scope Name:DefaultScope.
Provisioning: 10:52:23.760 [Client] Provisioned 9 Tables. Provision:Table, TrackingTable, StoredProcedures, Triggers.
ChangesSelecting: 10:52:23.817 [localhost] Sending Changes. Index:0. Rows:0
ChangesSelecting: 10:52:24.531 [localhost] Getting Changes. Index:0. Rows:658
ChangesSelecting: 10:52:24.607 [localhost] Getting Changes. Index:1. Rows:321
ChangesSelecting: 10:52:24.637 [localhost] Getting Changes. Index:2. Rows:29
ChangesSelecting: 10:52:24.654 [localhost] Getting Changes. Index:3. Rows:33
ChangesSelecting: 10:52:24.674 [localhost] Getting Changes. Index:4. Rows:39
ChangesSelecting: 10:52:24.701 [localhost] Getting Changes. Index:5. Rows:55
ChangesSelecting: 10:52:24.723 [localhost] Getting Changes. Index:6. Rows:49
ChangesSelecting: 10:52:24.745 [localhost] Getting Changes. Index:7. Rows:32
ChangesSelecting: 10:52:24.829 [localhost] Getting Changes. Index:8. Rows:758
ChangesSelecting: 10:52:24.895 [localhost] Getting Changes. Index:9. Rows:298
ChangesSelecting: 10:52:25.35 [localhost] Getting Changes. Index:10. Rows:1242
ChangesApplying: 10:52:25.491 [Client] [ProductDescription] [Modified] Applied:658. Conflicts:0.
ChangesApplying: 10:52:25.541 [Client] [ProductDescription] [Modified] Applied:762. Conflicts:0.
ChangesApplying: 10:52:25.698 [Client] [ProductCategory] [Modified] Applied:41. Conflicts:0.
ChangesApplying: 10:52:25.760 [Client] [ProductModel] [Modified] Applied:128. Conflicts:0.
ChangesApplying: 10:52:25.817 [Client] [Product] [Modified] Applied:48. Conflicts:0.
ChangesApplying: 10:52:25.833 [Client] [Product] [Modified] Applied:77. Conflicts:0.
ChangesApplying: 10:52:25.840 [Client] [Product] [Modified] Applied:110. Conflicts:0.
ChangesApplying: 10:52:25.849 [Client] [Product] [Modified] Applied:149. Conflicts:0.
ChangesApplying: 10:52:25.861 [Client] [Product] [Modified] Applied:204. Conflicts:0.
ChangesApplying: 10:52:25.893 [Client] [Product] [Modified] Applied:253. Conflicts:0.
ChangesApplying: 10:52:25.905 [Client] [Product] [Modified] Applied:285. Conflicts:0.
ChangesApplying: 10:52:25.917 [Client] [Product] [Modified] Applied:295. Conflicts:0.
ChangesApplying: 10:52:25.991 [Client] [Address] [Modified] Applied:450. Conflicts:0.
ChangesApplying: 10:52:26.62 [Client] [Customer] [Modified] Applied:298. Conflicts:0.
ChangesApplying: 10:52:26.179 [Client] [Customer] [Modified] Applied:596. Conflicts:0.
ChangesApplying: 10:52:26.215 [Client] [Customer] [Modified] Applied:847. Conflicts:0.
ChangesApplying: 10:52:26.287 [Client] [CustomerAddress] [Modified] Applied:417. Conflicts:0.
ChangesApplying: 10:52:26.346 [Client] [SalesOrderHeader] [Modified] Applied:32. Conflicts:0.
ChangesApplying: 10:52:26.417 [Client] [SalesOrderDetail] [Modified] Applied:542. Conflicts:0.
ChangesApplying: 10:52:26.430 [Client] [Total] Applied:3514. Conflicts:0.
EndSession: 10:52:26.498 Session Ended [0c622a02-9c2d-4a39-bcd1-7888deb5a859].
Synchronization done.
Total changes uploaded: 0
Total changes downloaded: 3514
Total changes applied: 3514
Total resolved conflicts: 0
Total duration :0:0:8.295 |
Beta Was this translation helpful? Give feedback.
-
New Interceptors on Web OrchestratorsNew interceptors have been added on the web orchestrators WebServerOrchestratorThe two first interceptors will intercept basically all requests and responses coming in and out:
Each of them will let you access the webServerOrchestrator.OnHttpGettingRequest(args =>
{
var httpContext = args.HttpContext;
var syncContext = args.Context;
var session = args.SessionCache;
}); The two last new web server http interceptors will let you intercept all the calls made when server receives client changes and when server sends back server changes.
Here is a quick example using all of them: var webServerOrchestrator = webServerManager.GetOrchestrator(context);
webServerOrchestrator.OnHttpGettingRequest(req =>
Console.WriteLine("Receiving Client Request:" + req.Context.SyncStage + ". " + req.HttpContext.Request.Host.Host + "."));
webServerOrchestrator.OnHttpSendingResponse(res =>
Console.WriteLine("Sending Client Response:" + res.Context.SyncStage + ". " + res.HttpContext.Request.Host.Host));
webServerOrchestrator.OnHttpGettingChanges(args => Console.WriteLine("Getting Client Changes" + args));
webServerOrchestrator.OnHttpSendingChanges(args => Console.WriteLine("Sending Server Changes" + args));
await webServerManager.HandleRequestAsync(context); Receiving Client Request:ScopeLoading. localhost.
Sending Client Response:Provisioning. localhost
Receiving Client Request:ChangesSelecting. localhost.
Sending Server Changes[localhost] Sending All Snapshot Changes. Rows:0
Sending Client Response:ChangesSelecting. localhost
Receiving Client Request:ChangesSelecting. localhost.
Getting Client Changes[localhost] Getting All Changes. Rows:0
Sending Server Changes[localhost] Sending Batch Changes. (1/11). Rows:658
Sending Client Response:ChangesSelecting. localhost
Receiving Client Request:ChangesSelecting. localhost.
Sending Server Changes[localhost] Sending Batch Changes. (2/11). Rows:321
Sending Client Response:ChangesSelecting. localhost
Receiving Client Request:ChangesSelecting. localhost.
Sending Server Changes[localhost] Sending Batch Changes. (3/11). Rows:29
Sending Client Response:ChangesSelecting. localhost
Receiving Client Request:ChangesSelecting. localhost.
Sending Server Changes[localhost] Sending Batch Changes. (4/11). Rows:33
Sending Client Response:ChangesSelecting. localhost
Receiving Client Request:ChangesSelecting. localhost.
Sending Server Changes[localhost] Sending Batch Changes. (5/11). Rows:39
Sending Client Response:ChangesSelecting. localhost
Receiving Client Request:ChangesSelecting. localhost.
Sending Server Changes[localhost] Sending Batch Changes. (6/11). Rows:55
Sending Client Response:ChangesSelecting. localhost
Receiving Client Request:ChangesSelecting. localhost.
Sending Server Changes[localhost] Sending Batch Changes. (7/11). Rows:49
Sending Client Response:ChangesSelecting. localhost
Receiving Client Request:ChangesSelecting. localhost.
Sending Server Changes[localhost] Sending Batch Changes. (8/11). Rows:32
Sending Client Response:ChangesSelecting. localhost
Receiving Client Request:ChangesSelecting. localhost.
Sending Server Changes[localhost] Sending Batch Changes. (9/11). Rows:758
Sending Client Response:ChangesSelecting. localhost
Receiving Client Request:ChangesSelecting. localhost.
Sending Server Changes[localhost] Sending Batch Changes. (10/11). Rows:298
Sending Client Response:ChangesSelecting. localhost
Receiving Client Request:ChangesSelecting. localhost.
Sending Server Changes[localhost] Sending Batch Changes. (11/11). Rows:1242
Sending Client Response:ChangesSelecting. localhost
Synchronization done. The main differences are that the two first ones will intercept ALL requests coming from the client and the two last one will intercept Only requests where data are exchanged (but you have more detailed) WebClientOrchestratorYou have pretty much the same localOrchestrator.OnHttpGettingResponse(req => Console.WriteLine("Receiving Server Response"));
localOrchestrator.OnHttpSendingRequest(res =>Console.WriteLine("Sending Client Request."));
localOrchestrator.OnHttpGettingChanges(args => Console.WriteLine("Getting Server Changes" + args));
localOrchestrator.OnHttpSendingChanges(args => Console.WriteLine("Sending Client Changes" + args)); Sending Client Request.
Receiving Server Response
Sending Client Request.
Receiving Server Response
Sending Client Changes[localhost] Sending All Changes. Rows:0
Sending Client Request.
Receiving Server Response
Getting Server Changes[localhost] Getting Batch Changes. (1/11). Rows:658
Sending Client Request.
Receiving Server Response
Getting Server Changes[localhost] Getting Batch Changes. (2/11). Rows:321
Sending Client Request.
Receiving Server Response
Getting Server Changes[localhost] Getting Batch Changes. (3/11). Rows:29
Sending Client Request.
Receiving Server Response
Getting Server Changes[localhost] Getting Batch Changes. (4/11). Rows:33
Sending Client Request.
Receiving Server Response
Getting Server Changes[localhost] Getting Batch Changes. (5/11). Rows:39
Sending Client Request.
Receiving Server Response
Getting Server Changes[localhost] Getting Batch Changes. (6/11). Rows:55
Sending Client Request.
Receiving Server Response
Getting Server Changes[localhost] Getting Batch Changes. (7/11). Rows:49
Sending Client Request.
Receiving Server Response
Getting Server Changes[localhost] Getting Batch Changes. (8/11). Rows:32
Sending Client Request.
Receiving Server Response
Getting Server Changes[localhost] Getting Batch Changes. (9/11). Rows:758
Sending Client Request.
Receiving Server Response
Getting Server Changes[localhost] Getting Batch Changes. (10/11). Rows:298
Sending Client Request.
Receiving Server Response
Getting Server Changes[localhost] Getting Batch Changes. (11/11). Rows:1242
Synchronization done. Example: Hook Bearer tokenThis sample is coming from a discussion on discussion #374 The idea is to inject the user identifier As you can see:
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class SyncController : ControllerBase
{
// The WebServerManager instance is useful to manage all the Web server orchestrators register in the Startup.cs
private WebServerManager webServerManager;
// Injected thanks to Dependency Injection
public SyncController(WebServerManager webServerManager) => this.webServerManager = webServerManager;
/// <summary>
/// This POST handler is mandatory to handle all the sync process
[HttpPost]
public async Task Post()
{
// If you are using the [Authorize] attribute you don't need to check
// the User.Identity.IsAuthenticated value
if (HttpContext.User.Identity.IsAuthenticated)
{
var orchestrator = webServerManager.GetOrchestrator(this.HttpContext);
// on each request coming from the client, just inject the User Id parameter
orchestrator.OnHttpGettingRequest(args =>
{
var pUserId = args.Context.Parameters["UserId"];
if (pUserId == null)
args.Context.Parameters.Add("UserId", this.HttpContext.User.Identity.Name);
});
// Because we don't want to send back this value, remove it from the response
orchestrator.OnHttpSendingResponse(args =>
{
if (args.Context.Parameters.Contains("UserId"))
args.Context.Parameters.Remove("UserId");
});
await webServerManager.HandleRequestAsync(this.HttpContext);
}
else
{
this.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
}
}
/// <summary>
/// This GET handler is optional. It allows you to see the configuration hosted on the server
/// The configuration is shown only if Environmenent == Development
/// </summary>
[HttpGet]
[AllowAnonymous]
public async Task Get() => await webServerManager.HandleRequestAsync(this.HttpContext);
} |
Beta Was this translation helpful? Give feedback.
-
@Mimetis Seems like I missed the discussion section. This seems good enough for the changes 😊 |
Beta Was this translation helpful? Give feedback.
-
The version V0.6 is released !
Nuget packages : https://www.nuget.org/packages?q=Dotmim.Sync
This discussion thread will summarize some of the new functionalities available:
OnSendingChanges
properties (replacing Binary with Changes table rows)Beta Was this translation helpful? Give feedback.
All reactions