Ejemplo n.º 1
0
        /// <summary>
        /// Create the tables in each database
        /// Add some datas in each database
        /// Performs an optional DeprovisionAsync, to be sure we are starting from scratch
        /// </summary>
        private static async Task SetupDatabasesAsync(SqlSyncProvider serverProvider, SqliteSyncProvider clientProvider)
        {
            // Add some datas in both
            var serverConnection = serverProvider.CreateConnection();
            await Helper.CreateSqlServerServiceTicketsTableAsync(serverConnection);

            var clientConnection = clientProvider.CreateConnection();
            await Helper.CreateSqliteServiceTicketsTableAsync(clientConnection);

            // Creating an agent that will handle all the process
            var agent = new SyncAgent(clientProvider, serverProvider);

            // Be sure we don't have an already existing sync setup.  (from previous run)
            await agent.LocalOrchestrator.DropAllAsync();

            await agent.RemoteOrchestrator.DropAllAsync();

            // Be sure we don't have existing rows (from previous run)
            await Helper.DropRowsAsync(serverConnection);

            await Helper.DropRowsAsync(clientConnection);

            // Add rows
            await Helper.AddRowsAsync(serverConnection);

            await Helper.AddRowsAsync(clientConnection);
        }
        public virtual async Task Scenario_Adding_OneColumn_OneTable_With_TwoScopes_OneClient_Still_OnOldScope_OneClient_OnNewScope()
        {
            // create a server schema with seeding
            await this.EnsureDatabaseSchemaAndSeedAsync(this.Server, true, UseFallbackSchema);

            // create 2 client databases
            // First one will update to new scope
            // Second one will stay on last scope
            // For this purpose, using two sqlite databases

            var client1DatabaseName = HelperDatabase.GetRandomName();
            var client2DatabaseName = HelperDatabase.GetRandomName();

            // Create the two databases
            await this.CreateDatabaseAsync(ProviderType.Sqlite, client1DatabaseName, true);

            await this.CreateDatabaseAsync(ProviderType.Sqlite, client2DatabaseName, true);

            var client1provider = new SqliteSyncProvider(HelperDatabase.GetSqliteFilePath(client1DatabaseName));
            var client2provider = new SqliteSyncProvider(HelperDatabase.GetSqliteFilePath(client2DatabaseName));

            // --------------------------
            // Step 1: Create a default scope and Sync clients
            // Note we are not including the [Attribute With Space] column
            var productCategoryTableName = this.Server.ProviderType == ProviderType.Sql ? "SalesLT.ProductCategory" : "ProductCategory";
            var productTableName         = this.Server.ProviderType == ProviderType.Sql ? "SalesLT.Product" : "Product";

            var setup = new SyncSetup(new string[] { productCategoryTableName });

            setup.Tables[productCategoryTableName].Columns.AddRange(
                new string[] { "ProductCategoryId", "Name", "rowguid", "ModifiedDate" });

            // Counting product categories & products
            int productCategoryRowsCount = 0;
            int productsCount            = 0;

            using (var readCtx = new AdventureWorksContext(Server, this.UseFallbackSchema))
            {
                productCategoryRowsCount = readCtx.ProductCategory.AsNoTracking().Count();
                productsCount            = readCtx.Product.AsNoTracking().Count();
            }


            var agent1 = new SyncAgent(client1provider, Server.Provider);
            var r1     = await agent1.SynchronizeAsync(setup);

            Assert.Equal(productCategoryRowsCount, r1.TotalChangesDownloaded);

            var agent2 = new SyncAgent(client2provider, Server.Provider);
            var r2     = await agent2.SynchronizeAsync(setup);

            Assert.Equal(productCategoryRowsCount, r2.TotalChangesDownloaded);

            // From now, the client 1 will upgrade to new scope
            // the client 2 will remain on old scope

            // Adding a new scope
            var remoteOrchestrator = agent1.RemoteOrchestrator; // agent2.RemoteOrchestrator is the same, btw

            // Adding a new scope on the server with this new column and a new table
            // Creating a new scope called "V1" on server
            var setupV1 = new SyncSetup(new string[] { productCategoryTableName, productTableName });

            setupV1.Tables[productCategoryTableName].Columns.AddRange(
                new string[] { "ProductCategoryId", "Name", "rowguid", "ModifiedDate", "Attribute With Space" });

            var serverScope = await remoteOrchestrator.ProvisionAsync("v1", setupV1);


            // Create a server new ProductCategory with the new column value filled
            // and a Product related
            var productId           = Guid.NewGuid();
            var productName         = HelperDatabase.GetRandomName();
            var productNumber       = productName.ToUpperInvariant().Substring(0, 10);
            var productCategoryName = HelperDatabase.GetRandomName();
            var productCategoryId   = productCategoryName.ToUpperInvariant().Substring(0, 6);

            var newAttributeWithSpaceValue = HelperDatabase.GetRandomName();

            using (var ctx = new AdventureWorksContext(Server, this.UseFallbackSchema))
            {
                var pc = new ProductCategory
                {
                    ProductCategoryId = productCategoryId,
                    Name = productCategoryName,
                    AttributeWithSpace = newAttributeWithSpaceValue
                };
                ctx.ProductCategory.Add(pc);

                var product = new Product {
                    ProductId = productId, Name = productName, ProductNumber = productNumber, ProductCategoryId = productCategoryId
                };
                ctx.Product.Add(product);

                await ctx.SaveChangesAsync();
            }

            // Add this new column on the client 1, with default value as null
            var connection = client1provider.CreateConnection();

            connection.Open();
            var command = connection.CreateCommand();

            command.CommandText = @"ALTER TABLE ProductCategory ADD [Attribute With Space] text NULL;";
            command.Connection  = connection;
            await command.ExecuteNonQueryAsync();

            connection.Close();

            // Creating a new table is quite easier since DMS can do it for us
            // Get scope from server (v1 because it contains the new table schema)
            // we already have it, but you cand call GetServerScopInfoAsync("v1") if needed
            // var serverScope = await remoteOrchestrator.GetServerScopeInfoAsync("v1");

            var localOrchestrator = new LocalOrchestrator(client1provider);

            if (this.Server.ProviderType == ProviderType.Sql)
            {
                await localOrchestrator.CreateTableAsync(serverScope, "Product", "SalesLT");
            }
            else
            {
                await localOrchestrator.CreateTableAsync(serverScope, "Product");
            }
            // Once created we can provision the new scope, thanks to the serverScope instance we already have
            var clientScopeV1 = await localOrchestrator.ProvisionAsync(serverScope);

            // IF we launch synchronize on this new scope, it will get all the rows from the server
            // We are making a shadow copy of previous scope to get the last synchronization metadata
            var oldClientScopeInfo = await localOrchestrator.GetClientScopeInfoAsync();

            clientScopeV1.ShadowScope(oldClientScopeInfo);
            await localOrchestrator.SaveClientScopeInfoAsync(clientScopeV1);

            // We are ready to sync this new scope !
            // we still can use the old agent, since it's already configured with correct providers
            // just be sure to set the correct scope
            r1 = await agent1.SynchronizeAsync("v1");

            Assert.Equal(2, r1.TotalChangesDownloaded);

            // make a sync on old scope for client 2
            r2 = await agent2.SynchronizeAsync();

            Assert.Equal(1, r2.TotalChangesDownloaded);

            // now check values on each client
            using (var ctx1 = new AdventureWorksContext((client1DatabaseName, ProviderType.Sqlite, client1provider), false))
            {
                var producCategory1 = ctx1.ProductCategory.First(pc => pc.ProductCategoryId == productCategoryId);
                Assert.Equal(newAttributeWithSpaceValue, producCategory1.AttributeWithSpace);
            }
            using (var ctx2 = new AdventureWorksContext((client2DatabaseName, ProviderType.Sqlite, client2provider), false))
            {
                var exc = Assert.ThrowsAny <Microsoft.Data.Sqlite.SqliteException>(() => ctx2.ProductCategory.First(pc => pc.ProductCategoryId == productCategoryId));
                Assert.Contains("no such column", exc.Message);
            }

            // Assuming we want to migrate the client 2 now
            var serverScope2 = await agent2.RemoteOrchestrator.GetServerScopeInfoAsync();

            // Create the new table locally
            if (this.Server.ProviderType == ProviderType.Sql)
            {
                await localOrchestrator.CreateTableAsync(serverScope, "Product", "SalesLT");
            }
            else
            {
                await localOrchestrator.CreateTableAsync(serverScope, "Product");
            }

            // Add this new column on the client 1, with default value as null
            connection = client2provider.CreateConnection();
            connection.Open();
            command             = connection.CreateCommand();
            command.CommandText = @"ALTER TABLE ProductCategory ADD [Attribute With Space] text NULL;";
            command.Connection  = connection;
            await command.ExecuteNonQueryAsync();

            connection.Close();

            // Don't bother to ShadowCopy metadata, since we are doing a reinit
            // Just Provision
            var clientScope2 = await agent2.LocalOrchestrator.ProvisionAsync(serverScope2);

            // Sync
            r2 = await agent2.SynchronizeAsync("v1", SyncType.Reinitialize);

            using (var readCtx = new AdventureWorksContext(Server, this.UseFallbackSchema))
            {
                productCategoryRowsCount = readCtx.ProductCategory.AsNoTracking().Count();
                productsCount            = readCtx.Product.AsNoTracking().Count();
            }

            Assert.Equal((productCategoryRowsCount + productsCount), r2.TotalChangesDownloaded);
        }
Ejemplo n.º 3
0
        private static async Task MigrateClientsUsingMultiScopesAsync()
        {
            // Create the server Sync provider
            var serverProvider = new SqlSyncProvider(serverConnectionString);

            // Create 2 clients. First will migrate, 2nd will stay without new column
            var client1Provider = new SqlSyncProvider(clientConnectionString);
            var databaseName    = $"{Path.GetRandomFileName().Replace(".", "").ToLowerInvariant()}.db";
            var client2Provider = new SqliteSyncProvider(databaseName);

            // Create standard Setup
            var setup = new SyncSetup("Address", "Customer", "CustomerAddress");

            // Creating agents that will handle all the process
            var agent1 = new SyncAgent(client1Provider, serverProvider);
            var agent2 = new SyncAgent(client2Provider, serverProvider);

            // Using the Progress pattern to handle progession during the synchronization
            var progress = new SynchronousProgress <ProgressArgs>(args => Console.WriteLine($"{args.ProgressPercentage:p}:\t{args.Message}"));

            // First sync to have a starting point
            // To make a full example, we are going to use differente scope name (v0, v1)
            // v0 is the initial database
            // v1 will contains the new column in the Address table
            var s1 = await agent1.SynchronizeAsync("v0", setup, progress);

            Console.WriteLine("Initial Sync on Sql Server Client 1");
            Console.WriteLine(s1);

            var s2 = await agent2.SynchronizeAsync("v0", setup, progress);

            Console.WriteLine("Initial Sync on Sqlite Client 2");
            Console.WriteLine(s2);


            // -----------------------------------------------------------------
            // Migrating a table by adding a new column
            // -----------------------------------------------------------------

            // Adding a new column called CreatedDate to Address table, on the server
            await Helper.AddNewColumnToAddressAsync(new SqlConnection(serverConnectionString));

            Console.WriteLine("Column added on server");

            // -----------------------------------------------------------------
            // Server side
            // -----------------------------------------------------------------

            // Creating a new setup with the same tables
            // We are going to provision a new scope (v1)
            // Since this scope is not existing yet, it will force DMS to refresh the schema and
            // get the new column
            var setupAddress = new SyncSetup("Address", "Customer", "CustomerAddress");

            // Create a server orchestrator used to Deprovision and Provision only table Address
            var remoteOrchestrator = new RemoteOrchestrator(serverProvider);

            // Provision everything again for this new scope v1,
            // This provision method will fetch the address schema from the database,
            // since the new scope name is not existing yet
            // so it will contains all the columns, including the new Address column added
            await remoteOrchestrator.ProvisionAsync("v1", setupAddress, progress : progress);

            Console.WriteLine("Server migration with new column CreatedDate done.");


            // At this point, server database has two scopes:
            // v0   : first scope with Address table without the new column
            // v1   : second scope with Address table with the new column CreatedDate

            // Take a look at the database in SQL management studio and see differences in stored proc

            // Now add a row on the server (with the new column)
            var addressId = await Helper.InsertOneAddressWithNewColumnAsync(new SqlConnection(serverConnectionString));

            Console.WriteLine($"New address row added with pk {addressId}");

            // -----------------------------------------------------------------
            // SQlite Client will stay on old schema (without the new CreatedDate column)
            // -----------------------------------------------------------------

            // First of all, we are still able to sync the local database without having to migrate the client
            // allows old clients that do not have the new column, to continue sync normally
            // these old clients will continue to sync on the v0 scope

            var s3 = await agent2.SynchronizeAsync("v0", setup, progress : progress);

            Console.WriteLine($"Sqlite not migrated, doing a sync on first scope v0:");
            Console.WriteLine(s3);

            // If we get the row from the client, we have the new row inserted on server,
            // but without the new column
            var client2row = await Helper.GetLastAddressRowAsync(client2Provider.CreateConnection(), addressId);

            Console.WriteLine(client2row);

            // -----------------------------------------------------------------
            // SQL Server Client will add the column and will sync on the new scope (with the new CreatedDate column)
            // -----------------------------------------------------------------

            // Now we are going to upgrade the client 1

            // adding the column to the client
            await Helper.AddNewColumnToAddressAsync(new SqlConnection(clientConnectionString));

            Console.WriteLine("Sql Server client1 migration with new column CreatedDate done.");

            // Provision client with the new the V1 scope
            // Getting the scope from server and apply it locally
            var serverScope = await agent1.RemoteOrchestrator.GetServerScopeInfoAsync("v1", progress : progress);

            var v1clientScope = await agent1.LocalOrchestrator.ProvisionAsync(serverScope, progress : progress);

            Console.WriteLine("Sql Server client1 Provision done.");

            // if you look the stored procs on your local sql database
            // you will that you have the two scopes (v0 and v1)

            // TRICKY PART

            /*
             *  The scope v1 is new.
             *  If we sync now, since v1 is new, we are going to sync all the rows from start
             *  What we want is to sync from the last point we sync the old v0 scope
             *  That's why we are shadowing the metadata info from v0 into v1
             */
            var v0clientScope = await agent1.LocalOrchestrator.GetClientScopeInfoAsync("v0");

            v1clientScope.ShadowScope(v0clientScope);
            v1clientScope = await agent1.LocalOrchestrator.SaveClientScopeInfoAsync(v1clientScope);

            // Now test a new sync, on this new scope v1
            var s4 = await agent1.SynchronizeAsync("v1", progress : progress);

            Console.WriteLine($"Sql Server client1 migrated, doing a sync on second scope v1:");
            Console.WriteLine(s4);

            // If we get the client row from the client database, it should contains the value
            var client1row = await Helper.GetLastAddressRowAsync(new SqlConnection(clientConnectionString), addressId);

            Console.WriteLine(client1row);

            // OPTIONAL
            // -----------------------------------------------------------------

            // On this new client, migrated, we no longer need the v0 scope
            // we can deprovision it
            await agent1.LocalOrchestrator.DeprovisionAsync("v0", SyncProvision.StoredProcedures, progress : progress);

            await agent1.LocalOrchestrator.DeleteClientScopeInfoAsync(v0clientScope, progress : progress);

            Console.WriteLine($"Deprovision of old scope v0 done on Sql Server client1");

            // -----------------------------------------------------------------
            // SQLite Client will eventually migrate to v1
            // -----------------------------------------------------------------

            // It's time to migrate the sqlite client
            // Adding the column to the SQLite client
            await Helper.AddNewColumnToAddressAsync(client2Provider.CreateConnection());

            Console.WriteLine($"Column eventually added to Sqlite client2");

            // Provision SQLite client with the new the V1 scope
            var v1client2Scope = await agent2.LocalOrchestrator.ProvisionAsync(serverScope, progress : progress);

            Console.WriteLine($"Provision v1 done on SQLite client2");

            // ShadowScope old scope to new scope
            var v0client2Scope = await agent2.LocalOrchestrator.GetClientScopeInfoAsync("v0");

            v1client2Scope.ShadowScope(v0client2Scope);
            v1client2Scope = await agent2.LocalOrchestrator.SaveClientScopeInfoAsync(v1client2Scope);

            // let's try to sync firstly
            // Now test a new sync, on this new scope v1
            // Obviously, we don't have anything from the server
            var s5 = await agent2.SynchronizeAsync("v1", progress : progress);

            Console.WriteLine(s5);

            // If we get the row from client, we have the new column, but value remains null
            // since this row was synced before client migration
            client2row = await Helper.GetLastAddressRowAsync(client2Provider.CreateConnection(), addressId);

            Console.WriteLine(client2row);

            // What we can do here, is just make a sync with Renit
            var s6 = await agent2.SynchronizeAsync("v1", SyncType.ReinitializeWithUpload, progress : progress);

            Console.WriteLine($"Making a full Reinitialize sync on SQLite client2");
            Console.WriteLine(s6);

            // And now the row is correct
            // If we get the row from client, we have the new column, but value remains null
            // since this row was synced before client migration
            client2row = await Helper.GetLastAddressRowAsync(client2Provider.CreateConnection(), addressId);

            Console.WriteLine(client2row);


            // Migration done

            Console.WriteLine("End");
        }