From 2d0606b3ef2e3522375c852c537a236e51b251cd Mon Sep 17 00:00:00 2001 From: Hutch79 <42042811+Hutch79@users.noreply.github.com> Date: Wed, 3 Jul 2024 21:36:23 +0200 Subject: [PATCH] Added DbContext and Migrations Management Script (#4) Fix #3 --- API/API.csproj | 10 ++ API/Program.cs | 16 ++++ Domain/Domain.csproj | 4 + Domain/Domains.cs | 1 + Domain/Subdomains.cs | 1 + Domain/User.cs | 1 + Infrastructure/Infrastructure.csproj | 30 ++++++ ...240703191648_initial-migration.Designer.cs | 96 +++++++++++++++++++ .../20240703191648_initial-migration.cs | 76 +++++++++++++++ .../YourLabDbContextModelSnapshot.cs | 93 ++++++++++++++++++ Infrastructure/YourLabDbContext.cs | 39 ++++++++ Your-Lab.sln | 10 +- scripts/dbScript.ps1 | 91 ++++++++++++++++++ 13 files changed, 466 insertions(+), 2 deletions(-) create mode 100644 Infrastructure/Infrastructure.csproj create mode 100644 Infrastructure/Migrations/20240703191648_initial-migration.Designer.cs create mode 100644 Infrastructure/Migrations/20240703191648_initial-migration.cs create mode 100644 Infrastructure/Migrations/YourLabDbContextModelSnapshot.cs create mode 100644 Infrastructure/YourLabDbContext.cs create mode 100644 scripts/dbScript.ps1 diff --git a/API/API.csproj b/API/API.csproj index 709f5d2..8e37bcc 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -6,9 +6,15 @@ enable Your_Lab Linux + 456c6c86-04fa-4b03-b088-81fcf2fb5760 + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -18,4 +24,8 @@ + + + + diff --git a/API/Program.cs b/API/Program.cs index 20cd2f5..b7e0905 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -1,3 +1,6 @@ +using Infrastructure; +using Microsoft.EntityFrameworkCore; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. @@ -7,8 +10,20 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.AddDbContext(context => +{ + context.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + var connectionString = builder.Configuration.GetConnectionString("SqlConnectionString"); + context.UseSqlServer(connectionString); +}); + var app = builder.Build(); +using var scope = app.Services.CreateScope(); + +var context = scope.ServiceProvider.GetRequiredService(); +context.Database.Migrate(); + // Configure the HTTP request pipeline. // if (app.Environment.IsDevelopment()) // { @@ -18,6 +33,7 @@ app.UseHttpsRedirection(); +// app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); diff --git a/Domain/Domain.csproj b/Domain/Domain.csproj index 3a63532..9b2515f 100644 --- a/Domain/Domain.csproj +++ b/Domain/Domain.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/Domain/Domains.cs b/Domain/Domains.cs index f41f29e..5ef0ba0 100644 --- a/Domain/Domains.cs +++ b/Domain/Domains.cs @@ -2,6 +2,7 @@ namespace Domain; public class Domains { + public int Id { get; set; } public required string Domain { get; set; } public required bool Active { get; set; } } \ No newline at end of file diff --git a/Domain/Subdomains.cs b/Domain/Subdomains.cs index 3bfcfec..9b39778 100644 --- a/Domain/Subdomains.cs +++ b/Domain/Subdomains.cs @@ -2,6 +2,7 @@ namespace Domain; public class Subdomains { + public int Id { get; set; } public required int DomainId { get; set; } public required int UserId { get; set; } public required string SubdomainName { get; set; } diff --git a/Domain/User.cs b/Domain/User.cs index c82de82..ffd1012 100644 --- a/Domain/User.cs +++ b/Domain/User.cs @@ -2,6 +2,7 @@ namespace Domain; public class User { + public int Id { get; set; } public required string Email { get; set; } public required string Username { get; set; } diff --git a/Infrastructure/Infrastructure.csproj b/Infrastructure/Infrastructure.csproj new file mode 100644 index 0000000..0110fc1 --- /dev/null +++ b/Infrastructure/Infrastructure.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/Infrastructure/Migrations/20240703191648_initial-migration.Designer.cs b/Infrastructure/Migrations/20240703191648_initial-migration.Designer.cs new file mode 100644 index 0000000..9a7d82f --- /dev/null +++ b/Infrastructure/Migrations/20240703191648_initial-migration.Designer.cs @@ -0,0 +1,96 @@ +// +using Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Infrastructure.Migrations +{ + [DbContext(typeof(YourLabDbContext))] + [Migration("20240703191648_initial-migration")] + partial class initialmigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Domain.Domains", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("Domain.Subdomains", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DomainId") + .HasColumnType("int"); + + b.Property("SubdomainName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Subdomains"); + }); + + modelBuilder.Entity("Domain.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Infrastructure/Migrations/20240703191648_initial-migration.cs b/Infrastructure/Migrations/20240703191648_initial-migration.cs new file mode 100644 index 0000000..b84005d --- /dev/null +++ b/Infrastructure/Migrations/20240703191648_initial-migration.cs @@ -0,0 +1,76 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Migrations +{ + /// + public partial class initialmigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Domains", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Domain = table.Column(type: "nvarchar(max)", nullable: false), + Active = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Domains", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Subdomains", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + DomainId = table.Column(type: "int", nullable: false), + UserId = table.Column(type: "int", nullable: false), + SubdomainName = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Subdomains", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Email = table.Column(type: "nvarchar(450)", nullable: false), + Username = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Users_Email", + table: "Users", + column: "Email", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Domains"); + + migrationBuilder.DropTable( + name: "Subdomains"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/Infrastructure/Migrations/YourLabDbContextModelSnapshot.cs b/Infrastructure/Migrations/YourLabDbContextModelSnapshot.cs new file mode 100644 index 0000000..dfcab13 --- /dev/null +++ b/Infrastructure/Migrations/YourLabDbContextModelSnapshot.cs @@ -0,0 +1,93 @@ +// +using Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Infrastructure.Migrations +{ + [DbContext(typeof(YourLabDbContext))] + partial class YourLabDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Domain.Domains", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("Domain.Subdomains", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DomainId") + .HasColumnType("int"); + + b.Property("SubdomainName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Subdomains"); + }); + + modelBuilder.Entity("Domain.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Infrastructure/YourLabDbContext.cs b/Infrastructure/YourLabDbContext.cs new file mode 100644 index 0000000..e1bd794 --- /dev/null +++ b/Infrastructure/YourLabDbContext.cs @@ -0,0 +1,39 @@ +using Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace Infrastructure +{ + public class YourLabDbContext : DbContext + { + public YourLabDbContext(DbContextOptions options) : base(options) + { + } + + + public DbSet Users { get; set; } + public DbSet Domains { get; set; } + public DbSet Subdomains { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfigurationsFromAssembly(typeof(YourLabDbContext).Assembly); + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasIndex(u => u.Email) + .IsUnique(true); + } + } + + public class YourLabDbContextFactory : IDesignTimeDbContextFactory + { + public YourLabDbContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlServer(); + + return new YourLabDbContext(optionsBuilder.Options); + } + } +} \ No newline at end of file diff --git a/Your-Lab.sln b/Your-Lab.sln index 63f6842..4b7afca 100644 --- a/Your-Lab.sln +++ b/Your-Lab.sln @@ -9,9 +9,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "Domain\Domain.csproj", "{7B6A4E3E-5108-4AE8-9E5E-45EF20D47725}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utils", "Utils", "{6919D8D5-7155-4657-9151-02FFC416C1D7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{2F42A92F-2EAE-411E-8C16-3B977BC1653C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{D02740CA-C98F-4080-8161-55D648F28D75}" ProjectSection(SolutionItems) = preProject - docker-compose.yml = docker-compose.yml + scripts\dbScript.ps1 = scripts\dbScript.ps1 EndProjectSection EndProject Global @@ -28,5 +30,9 @@ Global {7B6A4E3E-5108-4AE8-9E5E-45EF20D47725}.Debug|Any CPU.Build.0 = Debug|Any CPU {7B6A4E3E-5108-4AE8-9E5E-45EF20D47725}.Release|Any CPU.ActiveCfg = Release|Any CPU {7B6A4E3E-5108-4AE8-9E5E-45EF20D47725}.Release|Any CPU.Build.0 = Release|Any CPU + {2F42A92F-2EAE-411E-8C16-3B977BC1653C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F42A92F-2EAE-411E-8C16-3B977BC1653C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F42A92F-2EAE-411E-8C16-3B977BC1653C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F42A92F-2EAE-411E-8C16-3B977BC1653C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/scripts/dbScript.ps1 b/scripts/dbScript.ps1 new file mode 100644 index 0000000..5a85e77 --- /dev/null +++ b/scripts/dbScript.ps1 @@ -0,0 +1,91 @@ +param( + [switch]$displayHelp, + [switch]$runMigration, + [switch]$removeMigrationDirectory, + [switch]$recreateDatabase +) + +function Display-UsageInstructions { + Write-Host "" + Write-Host "Usage: .\DatabaseScript.ps1 [-runMigration] [-removeMigrationDirectory] [-recreateDatabase] [-displayHelp]" + Write-Host "Parameters:" + Write-Host " -runMigration Execute the migration script." + Write-Host " -removeMigrationDirectory Remove the migration directory before executing the script." + Write-Host " -recreateDatabase Recreate the database." + Write-Host " -displayHelp Display this help message." + Write-Host "" +} + +if (($PSBoundParameters.Count -eq 0) -or $displayHelp) { + Display-UsageInstructions + exit +} + +if ($recreateDatabase) { + + Write-Host "" + Write-Host "==================================================" -ForegroundColor Blue + Write-Host " CLOSING ALL CONNECTIONS" + Write-Host "==================================================" -ForegroundColor Blue + Write-Host "" + + sqlcmd -S "(LocalDB)\MSSQLLocalDB" -Q "ALTER DATABASE Your-Lab_DB SET SINGLE_USER WITH ROLLBACK IMMEDIATE;" + + Write-Host "" + Write-Host "==================================================" -ForegroundColor Blue + Write-Host " RECREATING DATABASE" + Write-Host "==================================================" -ForegroundColor Blue + Write-Host "" + + sqlcmd -S "(LocalDB)\MSSQLLocalDB" -Q "USE master; DROP DATABASE Your-Lab_DB;" + sqlcmd -S "(LocalDB)\MSSQLLocalDB" -Q "GO" + sqlcmd -S "(LocalDB)\MSSQLLocalDB" -Q "EXIT" + + sqlcmd -S "(LocalDB)\MSSQLLocalDB" -Q "CREATE DATABASE Your-Lab_DB;" + sqlcmd -S "(LocalDB)\MSSQLLocalDB" -Q "GO" + sqlcmd -S "(LocalDB)\MSSQLLocalDB" -Q "EXIT" +} + +if($removeMigrationDirectory) { + $directoryPath = "..\Infrastructure\Migrations" + + Write-Host "" + Write-Host "==================================================" -ForegroundColor Blue + Write-Host " DELETING MIGRATION FOLDER" + Write-Host "==================================================" -ForegroundColor Blue + + if (Test-Path -Path $directoryPath -PathType Container) { + Write-Host "" + Write-Host "Deleting directory:" + Write-Host "$directoryPath" + Remove-Item -Path $directoryPath -Recurse -Force + Write-Host "" + Write-Host "Directory deleted successfully." + } else { + Write-Host "" + Write-Host "Directory not found:" + Write-Host "$directoryPath" + } +} + +if($runMigration) { + Write-Host "" + Write-Host "==================================================" -ForegroundColor Blue + Write-Host " CREATING MIGRATION" + Write-Host "==================================================" -ForegroundColor Blue + Write-Host "" + + $MigrationName = Read-Host "Please enter the Migration name" + + Set-Location -Path "..\Infrastructure" + + dotnet ef migrations add $MigrationName + + Set-Location -Path "..\scripts" +} + +Write-Host "" +Write-Host "==================================================" -ForegroundColor Blue +Write-Host " COMPLETED" +Write-Host "==================================================" -ForegroundColor Blue +Write-Host "" \ No newline at end of file