(Core) Core (2020)

Transform Database from MsSQL to MySQL (Net Core EF DbFirst, Scaffold-DbContext, Read Db Schema, Move Users and data, EF SQL trace)

In this page I want describe a one small intermediate task to data transformation from MsSQL to MySQL, in order to move project from .Net Framework to .Net code, from Windows to Linux.

1. Source and target SQL servers.

I develop project in Windows, but deploy my project in Linux server. Therefore I install in my Windows developers computer MySQL 8.0.15 and in my server I use MariaDB 10.2.31. My Linux version is openSUSE Leap 15.1.



My big fortune that both of this servers is supported by one .Net Core data provider - Pomelo.EntityFrameworkCore.MySql (in this project I use version 3.1.1).

In other side I have old db in MsSQL 2016.



My goal in this small subtask is correct transform data from MsSQL to MySQL with manually check each mistake in datatype and fix mistakes from old project developer.

2. Database project.


Because database definition I need no use in a bunch of subprojects, I define database in separate project. With this project file. Database definition is simple class library project without any startup file. This project has some parts:



Exactly from this project I done "Update-database" command to perform migration. And to pick up this database to .Net core project (with identity, Rolebased authorization and define MySQL Options) I use:


   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Threading.Tasks;
   5:  using Microsoft.AspNetCore.Builder;
   6:  using Microsoft.AspNetCore.Components;
   7:  using Microsoft.AspNetCore.Hosting;
   8:  using Microsoft.Extensions.Configuration;
   9:  using Microsoft.Extensions.DependencyInjection;
  10:  using Microsoft.Extensions.Hosting;
  11:  using Microsoft.AspNetCore.Authentication;
  12:  using Microsoft.IdentityModel.Tokens;
  13:  using Microsoft.AspNetCore.Identity;
  14:  using Microsoft.EntityFrameworkCore;
  15:  using Microsoft.AspNetCore.Components.Authorization;
  16:  using Microsoft.AspNetCore.Http;
  17:  using Microsoft.AspNetCore.Authentication.Cookies;
  18:  using Microsoft.AspNetCore.Components.Server.Circuits;
  ...   
  63:          services.AddDbContext<ApplicationDbContext>(
  64:                  (Microsoft.EntityFrameworkCore.DbContextOptionsBuilder options) =>
  65:                  options.UseMySql(Configuration.GetConnectionString("DefaultConnection"),
  66:                  (Microsoft.EntityFrameworkCore.Infrastructure.MySqlDbContextOptionsBuilder mySqlOption) =>
  67:                  {
  68:                      mySqlOption.CommandTimeout(10);
  69:                      mySqlOption.EnableRetryOnFailure(10);
  70:                  }),
  71:                  ServiceLifetime.Transient, ServiceLifetime.Transient);
  72:   
  73:              services.AddIdentity<ApplicationUser, IdentityRole>(options =>
  74:              {
  75:                  options.Password.RequiredLength = 4;
  76:                  options.Password.RequireUppercase = false;
  77:                  options.Password.RequireLowercase = false;
  78:                  options.Password.RequireUppercase = false;
  79:                  options.Password.RequireNonAlphanumeric = false;
  80:                  options.SignIn.RequireConfirmedEmail = true;
  81:   
  82:              }).AddEntityFrameworkStores<ApplicationDbContext>()
  83:              .AddDefaultTokenProviders();
  84:   
  85:              services.AddAuthorization(options =>
  86:              {
  87:                  options.AddPolicy("RequireAdmin", c => c.RequireRole("Admin"));
  88:              });

3. Why simple SSIS-packages transformation was be impossible?


Firstly, Because old developer of this project not very accuracy, for example he define UserID with GUID datatype in one table as GUID, but in another table as string (nvarchar(max)).



Secondary, .NET type has different presentation in different SQL server, therefore automaticaly transform exaction database is impossible. For example Boolean .NET Type mapping to MsSQL as as Bool datatype, but in MySQL this is Byte (1) datetype. GUID and other .NET datatype also has different presentation in different SQL server. Therefore simple attempt of transformation raise error and no more.



Thirdly, if you do deploy ASP.NET Core identity to different SQL database (from simple EF code first command "Update-database") you will receive full different User Identity table. For example Roles identity deployed to MsSQL as two fields, but in MySQL as four fields.



And Microsoft costantly and permanently renamed all possible fields it has access - one month pass and field LockoutEndDateUtc was been renamed to LockoutEnd.



4. Console application project to move data.

For transform DB I have created new console application project with that library.


   1:  <Project Sdk="Microsoft.NET.Sdk">
   2:   
   3:    <PropertyGroup>
   4:      <OutputType>Exe</OutputType>
   5:      <TargetFramework>netcoreapp3.1</TargetFramework>
   6:    </PropertyGroup>
   7:   
   8:    <ItemGroup>
   9:      <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.6" />
  10:      <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.6">
  11:        <PrivateAssets>all</PrivateAssets>
  12:        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  13:      </PackageReference>
  14:      <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.6" />
  15:      <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.3" />
  16:      <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.2" />
  17:      <PackageReference Include="System.Configuration.ConfigurationManager" Version="4.7.0" />
  18:    </ItemGroup>
  19:   
  20:    <ItemGroup>
  21:      <ProjectReference Include="MyDb.csproj" />
  22:    </ItemGroup>
  23:   
  24:    <ItemGroup>
  25:      <None Update="appsettings.json">
  26:        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  27:      </None>
  28:    </ItemGroup>
  29:   
  30:  </Project>

5. Scaffold-DbContext - read real data structure from old database in SQL Server.

Scaffold-DbContext "Server=(localdb)\mssqllocaldb;Database=Docker;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -ContextDir MsSQLContext -Context MsSQLContext


Pay attention - Microsoft developers are so degenerate then not understand how to create definition of DB structure in VB.NET, their intellect low than zero, I has read MS chatting for this themes and one MS degenerate wrote to second MS degenerate - "I don't understand how is possible to write lambda expression in VB." This two dumb aborted child use this project to see how is possible to write lambda expression on VB and what difference between VB and C#.



Second points of your pay attention - MS degenerates not read DB structure even for MS SQL server, for example, in Ms Servre I have one view, based on table. This view has no have key table in result. Therefore key field was inaccessible.



However I receive main part of my MS SQL db structure:



6. Define two context - MySQL and MsSQL.

Than I define two context - for old database MsSQL and my new database in MySQL.


  24:          static void Main(string[] args)
  25:          {
  26:              var MySQL = new AspNetIdentity.ApplicationDbContext();
  27:              var MsSQL = new MsSQLContext.MsSQLContext();
  28:   

After scaffording (by Scaffold-DbContext) I receive configured MS provider and common structure of my MS SQL database (this is EF Database First db pattern).


But there are two big problem:


Therefore for continue this transformation project need two very important additional steps.


7. Add EF SQL tracing.

For tracing by EF SQL command I add to project package Microsoft.Extensions.Logging.Debug and define it as static in may main module.



  22:          public static readonly ILoggerFactory _loggerFactory
  23:                      = LoggerFactory.Create(builder => builder.AddDebug().AddFilter((category, level) => level == LogLevel.Information && !category.EndsWith("Connection")));

And after that I add tracing to my MySQL and MsSQL context.



   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel.DataAnnotations.Schema;
   4:  using System.IO;
   5:  using System.Linq;
   6:  using System.Threading.Tasks;
   7:  using Microsoft.AspNetCore.Identity;
   8:  using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
   9:  using Microsoft.EntityFrameworkCore;
  10:  using Microsoft.Extensions.Configuration;
  11:  using Models;
  12:   
  13:  namespace AspNetIdentity
  14:  {
  15:       public partial class ApplicationDbContext 
  16:      {
  17:          protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  18:          {
  19:              if (!optionsBuilder.IsConfigured)
  20:              {
  21:                  IConfigurationRoot configuration = new ConfigurationBuilder()
  22:                     .SetBasePath(Directory.GetCurrentDirectory())
  23:                     .AddJsonFile("appsettings.json")
  24:                     .Build();
  25:   
  26:                  var connectionString = configuration.GetConnectionString("DefaultConnection");
  27:                  optionsBuilder.UseMySql(connectionString).UseLoggerFactory(Move.Program._loggerFactory);
  28:              }
  29:          }
  30:   
  31:          protected override void OnModelCreating(ModelBuilder modelBuilder)
  32:          {
  33:              base.OnModelCreating(modelBuilder);
  34:          }
  35:   
  36:      }
  37:  }

Only when this step was done you can see SQL command in immediate window. As you can see this is different pattern than in ASP.NET Core container with dependency injection (for example see that pattern in this page).

8. Read MS SQL DB structure builded by Scaffold-DbContext.

Because EF DB working incorrectly even with MS SQL server, next mandatory step is including reading DB structure, without this step any searching mistakes is impossible.



  27:              var MsSQL = new MsSQLContext.MsSQLContext();
  28:   
  29:              var entityType = MsSQL.Model.FindEntityType("MsSQLModels.ShareOwners");
  30:   
  31:              // Table info 
  32:              var tableName = entityType.GetTableName();
  33:              var tableSchema = entityType.GetSchema();
  34:   
  35:              // Column info 
  36:              foreach (var property in entityType.GetProperties())
  37:              {
  38:                  var columnName = property.GetColumnName();
  39:                  var columnType = property.GetColumnType();
  40:                  Debug.Print($"{ tableName }, {tableSchema },{columnName},{columnType} ");
  41:              };

You will receive the same information about fields.



But pay attention you can use CLR names of EF Poco class. You can receive it bu this way.



9. Redefine ApplicationDbContext for MySQL from DbContext (not as normally need from IdentityDbContext<ApplicationUser>).

This is last key point to move data from MS SQL to My SQL by correct way. Because .NET definition of ASP.NET user is different in MySQL and MySQL, you can read and write raw data.

In my shared project with DB definition I define ASP.NET user databases as inheritance od IdentityDbContext:



But in transformation project I inherit database definition from DbContext

This pattern allow me manually changed each fields (by name and type) during a transformation ASP.NET Idntity (and other data) from MS SQL to My SQL.


  43:              MsSQL.AspNetRoles.OrderBy(row => row.Id).ForEachAsync(row =>
  44:              {
  45:                  Console.WriteLine($"{row.Id}");
  46:                  MySQL.AspNetRoles.Add(new AspNetIdentity.AspNetRoles
  47:                  {
  48:                      Id = row.Id,
  49:                      Name = row.Name,
  50:                  });
  51:                  MySQL.SaveChanges();
  52:              }).GetAwaiter().GetResult();
  53:   
  54:   
  55:              MsSQL.AspNetUsers.OrderBy(row => row.Id).ForEachAsync(row =>
  56:              {
  57:                  Console.WriteLine($"{row.Id}");
  58:                  MySQL.AspNetUsers.Add(new AspNetIdentity.AspNetUsers
  59:                  {
  60:                      Id = row.Id,
  61:                      DiscordId = row.DiscordId,
  62:                      DiscordEnabled = row.DiscordEnabled,
  63:                      DiscordServerId = row.DiscordServerId,
  64:                      IsGoogleAuthenticatorEnabled = row.IsGoogleAuthenticatorEnabled,
  65:                      GoogleAuthenticatorSecretKey = row.GoogleAuthenticatorSecretKey,
  66:                      Ipaddress = row.Ipaddress,
  67:                      UserAgent = row.UserAgent,
  68:                      ResetDone = row.ResetDone,
  69:                      Apikey = row.Apikey,
  70:                      Email = row.Email,
  71:                      EmailConfirmed = row.EmailConfirmed,
  72:                      PasswordHash = row.PasswordHash,
  73:                      SecurityStamp = row.SecurityStamp,
  74:                      PhoneNumber = row.PhoneNumber,
  75:                      PhoneNumberConfirmed = row.PhoneNumberConfirmed,
  76:                      TwoFactorEnabled = row.TwoFactorEnabled,
  77:                      LockoutEnd = row.LockoutEndDateUtc,
  78:                      LockoutEnabled = row.LockoutEnabled,
  79:                      AccessFailedCount = row.AccessFailedCount,
  80:                      UserName = row.UserName
  81:   
  82:                  });
  83:                  MySQL.SaveChanges();
  84:              }).GetAwaiter().GetResult();
  85:   
  86:              MsSQL.AspNetUserClaims.OrderBy(row => row.UserId).ForEachAsync(row =>
  87:              {
  88:                  Console.WriteLine($"{row.UserId}");
  89:                  MySQL.AspNetUserClaims.Add(new AspNetIdentity.AspNetUserClaims
  90:                  {
  91:                      Id = row.Id,
  92:                      UserId = row.UserId,
  93:                      ClaimType = row.ClaimType,
  94:                      ClaimValue = row.ClaimValue
  95:   
  96:                  });
  97:                  MySQL.SaveChanges();
  98:              }).GetAwaiter().GetResult();
  99:   

10. Moving data.

If all step was be correct, relax and enjoy. In my developer computer about 40 record per second reading from MsSQL, transform to .NET type and than write to MySQL:





Comments ( )
Link to this page: //www.vb-net.com/CoreDbFirst/Index.htm
< THANKS ME>