Private/Cs/ModelProj/EfModel.ps1


Function New-EfModel([SolnInfo]$solnInfo, [ModelCsprojInfo]$modelCsprojInfo, [DbInfo]$dbInfo, [string[]]$views ) {
    # Build the scaffolding in project to "Model" folder
    # It needs to be run with the working directory of the csproj file
    Write-Host "### Create EntityFramework scaffolding for" $modelCsprojInfo.csprojName
    Invoke-Command { 
        [string]$csprojDir = $args[0]
        [string]$contextName = $args[1]
        [string]$connStr = $args[2]
        Set-Location $csprojDir
        &{dotnet.exe ef dbcontext scaffold $connStr Microsoft.EntityFrameworkCore.SqlServer -c $modelCsprojInfo.efContextName -o Model}
    } -ArgumentList $modelCsprojInfo.csprojDir, $modelCsprojInfo.efContextName, $dbInfo.connStr
    Confirm-LastExitCode

    <#
     # Ok so far, but our context .cs has code like:
     #
     # protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
     # {
     # #warning To protect potentially sensitive information in your connection string, you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings.
     # optionsBuilder.UseSqlServer(@"Server=localhost;Database=***;Trusted_Connection=True;");
     # }
     #
     # We need to replace that with
     #
     # public MyProjectContext(DbContextOptions options) :base(options)
     # {
     # }
     #
     #>

    [string] $contextCsFile = $modelCsprojInfo.csprojDir + "\Model\" + $modelCsprojInfo.efContextName + ".cs"
    Write-Host "### Fix EntityFramework" $modelCsprojInfo.efContextName "constructor to remove explicit connection string" $contextCsFile
    (Get-Content $contextCsFile) `
        -replace 'protected override void OnConfiguring\(DbContextOptionsBuilder optionsBuilder\)', "public $($modelCsprojInfo.efContextName)(DbContextOptions options) :base(options)" `
        -replace '#warning To protect.*$', '' `
        -replace 'if \(!optionsBuilder.IsConfigured\)', '' `
        -replace 'optionsBuilder.UseSqlServer.*$', '' |
        Out-FileUtf8NoBom $contextCsFile


    # Remove default "Class1.cs" file
    Write-Host "### Remove " $modelCsprojInfo.csprojName "scaffolding Class1.cs"
    if(Test-Path "$($modelCsprojInfo.csprojDir)\Class1.cs") {
        Remove-Item "$($modelCsprojInfo.csprojDir)\Class1.cs"
    }

    Write-Host "### Add view(s) $views"
    $tableInfos = $solnInfo.GetTableInfos()
    New-EfModelView $solnInfo $modelCsprojInfo $solnInfo.dbInfo $tableInfos $views

}


Function New-EfModelView([SolnInfo] $solnInfo, [ModelCsprojInfo] $modelCsprojInfo, [DbInfo] $dbInfo, $tableInfos, [string[]]$views)
{
    # Create "context with view" file if missing

    [string]$modelWithViewDir = "$($modelCsprojInfo.csprojDir)\ModelWithView"
    if(-not (Test-Path $modelWithViewDir)) {
        New-Item $modelWithViewDir -itemtype directory
    }
    $contextWithViewCsFile = "$($modelWithViewDir)\$($modelCsprojInfo.efWithViewContextName).cs"
    if(-not (Test-Path $contextWithViewCsFile)) {
        Write-Host "### Creating new context file $contextWithViewCsFile"
        New-ContextWithViewCsToString "$($modelCsprojInfo.namespace).Model" "$($modelCsprojInfo.namespace).ModelWithView" $modelCsprojInfo.efContextName $modelCsprojInfo.efWithViewContextName | Out-FileUtf8NoBom $contextWithViewCsFile
    }

    [string]$view = ""
    foreach($view in $views) {
        $viewName, $viewPrimaryKey = $view.split('.')
        [string]$contextWithViewCsFileContents = Get-Content -raw $contextWithViewCsFile
        if(-not ($contextWithViewCsFileContents -match "DbSet<$($viewName)>")) {
            # Add a pointer to the view in the context file
            [string]$newText = " public virtual DbSet<$($viewName)> $($viewName) { get; set; }"
            $contextWithViewCsFileContents = $contextWithViewCsFileContents -replace '([ \t]*// VIRTUAL_DB_SET_ABOVE_HERE)',"$($newText)`r`n`$1"

            [string]$newText = @"
            modelBuilder.Entity<$($viewName)>(entity =>
            {
                entity.HasKey(e => new { e.$($viewPrimaryKey)})
                    .HasName("PK_FAKEKEY");
            });
"@

            $contextWithViewCsFileContents = $contextWithViewCsFileContents -replace '([ \t]*// MODEL_BUILD_ENTITY_ABOVE_HERE)',"$($newText)`r`n`$1"    

            $contextWithViewCsFileContents | Out-FileUtf8NoBom $contextWithViewCsFile

            [TableInfo] $tableInfo = $tableInfos[$viewName]
            if($tableInfo -eq $null) {
                throw "Trying build C# code for view $viewName but cannot find table definition in metadata"
            }

            # Generate the view
            $modelCsFileName = "$($modelCsprojInfo.csprojDir)\ModelWithView\$($tableInfo.tableCapitalCamel).cs"
            New-EfModelViewToString $modelCsprojInfo.efWithViewNamespace $tableInfo.tableCapitalCamel $tableInfo.columnInfos | Out-FileUtf8NoBom $modelCsFileName
        }
    }
}

Function New-EfModelViewToString([string]$namespace, [string]$tableCapitalCamel, [ColumnInfo[]] $columnInfos){
    Write-Host "### Building EF model for view $tableCapitalCamel to $modelCsFileName"
    [string]$columns = ""
    foreach($columnInfo in $columnInfos) {
        $tmp = New-EfModelViewColumnToString $columnInfo
        $columns += "`r`n" + $tmp
    }


<####################################################################>
    $result = @"
using System;
using System.Collections.Generic;

namespace $namespace
{
    public partial class $($tableCapitalCamel)
    {
        public $($tableCapitalCamel)()
        {
        }

        $columns
    }
}

"@

<####################################################################>

    return $result 
}

Function New-EfModelViewColumnToString([ColumnInfo]$columnInfo) {

    # Get C# column name
    [string]$colName = $columnInfo.columnCapitalCamel

    # Get C# data type from SQL data type
    [string]$csType = "string"
    if(($columnInfo.dataType -eq "date") -or ($columnInfo.dataType -eq "datetime") -or ($columnInfo.dataType -eq "datetime2")) {
        $csType = "DateTime"
    } elseif(($columnInfo.dataType -eq "bigint")) {
        $csType = "long"
    } elseif(($columnInfo.dataType -eq "int") -or ($columnInfo.dataType -eq "smallint")) {
        $csType = "int"
    } elseif(($columnInfo.dataType -eq "bit")) {
        $csType = "bool"
    } elseif(($columnInfo.dataType -eq "numeric")) {
        $csType = "decimal"
    }

    # Add "?" syntax for nullable non-strings
    [string]$nullableMarker = ""
    if(($csType -ne "string") -and ($columnInfo.isNullable)) {
        $nullableMarker = "?"
    }

    # Build the C# expression for this column
    [string]$result = "`t`tpublic $($csType)$($nullableMarker) $colName { get; set; }"

    return $result
}