I worked with EF Core and SQL Server using database first approach. It is Ok for me to use dotnet-ef CLI for reverse engineering both tables and views. It preserves nullability of select columns from table in view when reverse engineering.

I.E, I have the following SQL Server schema:

CREATE TABLE [dbo].[Terminal] (
    [Id] int NOT NULL IDENTITY,
    [Tid] nvarchar(max) NOT NULL,
    [ActivedAt] datetime2 NULL,
    CONSTRAINT [PK_Terminal] PRIMARY KEY ([Id])
);
CREATE OR ALTER VIEW [dbo].[TerminalView]
WITH SCHEMABINDING
AS
    SELECT [t].[Id],
        [t].[Tid],
        ISNULL(CAST(IIF([t].[ActivedAt] IS NULL, 0, 1) AS BIT), 0) AS [IsActive]
    FROM [dbo].[Terminal] AS [t];

When reverse engineering this schema, I get:


public partial class Terminal
{
    public int Id { get; set; }

    public string Tid { get; set; } = null!;

    public DateTime? ActivedAt { get; set; }
}

public partial class TerminalView
{
    public int Id { get; set; }

    public string Tid { get; set; } = null!;

    public bool IsActive { get; set; }
}

using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;

namespace Npgsql.Scaffolding.Lab.Models;

public partial class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    public virtual DbSet<Terminal> Terminals { get; set; }

    public virtual DbSet<TerminalView> TerminalViews { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Terminal>(entity =>
        {
            entity.ToTable("Terminal");

            entity.Property(e => e.Id).HasColumnName("Id");
            entity.Property(e => e.ActivedAt).HasColumnName("ActivedAt");
            entity.Property(e => e.Tid).HasColumnName("Tid");
        });

        modelBuilder.Entity<TerminalView>(entity =>
        {
            entity
                .HasNoKey()
                .ToView("TerminalView");

            entity.Property(e => e.Id)
                .ValueGeneratedOnAdd()
                .HasColumnName("Id");
            entity.Property(e => e.IsActive).HasColumnName("IsActive");
            entity.Property(e => e.Tid).HasColumnName("Tid");
        });

        OnModelCreatingPartial(modelBuilder);
    }

    partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

When I migrated to PostgreSQL schema:

CREATE TABLE IF NOT EXISTS public.terminal
(
    id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
    tid text COLLATE pg_catalog."default" NOT NULL,
    actived_at timestamp with time zone,
    CONSTRAINT "PK_terminal" PRIMARY KEY (id)
)

CREATE OR REPLACE VIEW public.terminal_view
WITH (
    check_option=cascaded
) AS
 SELECT id,
    tid,
    actived_at IS NOT NULL AS is_active
   FROM terminal;

When I reverse engineering, TerminalView nullability is not persist. All properties now become nullable.

public partial class TerminalView
{
    public int? Id { get; set; }

    public string? Tid { get; set; }

    public bool? IsActive { get; set; }
}

Did anyone meet this kind of issue? Any solution?