diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 686b5829..c0dddfb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -241,6 +241,16 @@ jobs: "samples/connection-string-mssql/ConnectionStringMssql.sln" "samples/aspnet-core-appsettings/AspNetCoreAppSettings.sln" "samples/split-data-and-models-between-multiple-projects/SampleApp.slnx" + # Per-provider reference samples (#191): minimal EF Core samples with committed + # generated code for the providers that don't have DACPAC support, so they build + # with -p:EfcptEnabled=false just like the rest of this list. provider-snowflake is + # entities-only (no first-party EF Core runtime provider exists for Snowflake) - see + # samples/provider-snowflake/README.md. + "samples/provider-postgres/ProviderPostgres.sln" + "samples/provider-mysql/ProviderMySql.sln" + "samples/provider-oracle/ProviderOracle.sln" + "samples/provider-firebird/ProviderFirebird.sln" + "samples/provider-snowflake/ProviderSnowflake.sln" ) for sample in "${SAMPLES[@]}"; do echo "::group::Build $sample" diff --git a/docs/user-guide/provider-support.md b/docs/user-guide/provider-support.md index aabde702..741d4317 100644 --- a/docs/user-guide/provider-support.md +++ b/docs/user-guide/provider-support.md @@ -253,6 +253,26 @@ satellite-package resolution machinery. This is disabled by default (custom prov third-party code at build time) and requires an explicit opt-in. See [Custom Providers](custom-providers.md) for the full authoring and registration guide. +## Per-Provider Reference Samples + +Minimal, buildable reference samples exist under `samples/provider-/` for the five +providers that have no DACPAC support and therefore need Connection String Mode. Each one ships a +committed, compilable copy of the generated `AppDbContext` + entity classes under +`EntityFrameworkCoreProject/Generated/`, so the sample builds green in CI with `-p:EfcptEnabled=false` +and **no live database** - see each sample's README for how to point it at a real database and +regenerate locally. + +| Sample | EF Core Runtime Provider | Notes | +|--------|--------------------------|-------| +| [provider-postgres](../../samples/provider-postgres/README.md) | `Npgsql.EntityFrameworkCore.PostgreSQL` | | +| [provider-mysql](../../samples/provider-mysql/README.md) | `Pomelo.EntityFrameworkCore.MySql` | Pinned to EF Core 9.x - Pomelo has no EF Core 10 release yet | +| [provider-oracle](../../samples/provider-oracle/README.md) | `Oracle.EntityFrameworkCore` | | +| [provider-firebird](../../samples/provider-firebird/README.md) | `FirebirdSql.EntityFrameworkCore.Firebird` | | +| [provider-snowflake](../../samples/provider-snowflake/README.md) | *(none - entities-only)* | No first-party EF Core runtime provider exists for Snowflake | + +(SQL Server and SQLite already have full samples under `samples/dacpac-zero-config/` and +`samples/connection-string-sqlite/` respectively - see [samples/README.md](../../samples/README.md).) + ## See Also - [Configuration Reference](configuration.md) - All EfcptProvider values and properties diff --git a/samples/README.md b/samples/README.md index df875b12..91fd317b 100644 --- a/samples/README.md +++ b/samples/README.md @@ -30,6 +30,21 @@ This directory contains sample projects demonstrating various usage patterns of | [connection-string-mssql](#connection-string-mssql) | SQL Server + Aspire | SQL Server container with .NET Aspire | | [aspnet-core-appsettings](#aspnet-core-appsettings) | SQL Server + Aspire | appsettings.json + Aspire container | +### Per-Provider Reference Samples + +Minimal reference samples for the providers that have no DACPAC support and only support +Connection String Mode. Each commits a reference copy of the generated code so it builds in CI +with `-p:EfcptEnabled=false` and no live database - see [Per-Provider Reference +Samples](#per-provider-reference-samples-1) below for details. + +| Sample | Database Provider | EF Core Runtime Provider | +|--------|------------------|---------------------------| +| [provider-postgres](#provider-postgres) | PostgreSQL | `Npgsql.EntityFrameworkCore.PostgreSQL` | +| [provider-mysql](#provider-mysql) | MySQL | `Pomelo.EntityFrameworkCore.MySql` (EF Core 9.x pin) | +| [provider-oracle](#provider-oracle) | Oracle | `Oracle.EntityFrameworkCore` | +| [provider-firebird](#provider-firebird) | Firebird | `FirebirdSql.EntityFrameworkCore.Firebird` | +| [provider-snowflake](#provider-snowflake) | Snowflake | *(none - entities-only, see sample README)* | + ## Input Modes JD.Efcpt.Build supports two primary input modes: @@ -426,6 +441,81 @@ dotnet build MyApp.Api --- +## Per-Provider Reference Samples + +These five samples cover every JD.Efcpt.Build-supported provider that has no DACPAC support and +therefore relies entirely on Connection String Mode. Rather than requiring a live database in CI, +each one commits a reference copy of the code `efcpt` would generate under +`EntityFrameworkCoreProject/Generated/`, so `dotnet build .sln -p:EfcptEnabled=false` +succeeds with zero database dependency (see the `samples-build` job in +`.github/workflows/ci.yml`). Set `EfcptConnectionString` locally to regenerate against a real +database instead - see each sample's README for details. + +### provider-postgres + +**Location:** `provider-postgres/` + +PostgreSQL via Connection String Mode, using the `Npgsql.EntityFrameworkCore.PostgreSQL` runtime +provider. See [provider-postgres/README.md](provider-postgres/README.md). + +**Build:** +```bash +dotnet build provider-postgres/ProviderPostgres.sln +``` + +### provider-mysql + +**Location:** `provider-mysql/` + +MySQL via Connection String Mode, using the `Pomelo.EntityFrameworkCore.MySql` runtime provider. +Pinned to the EF Core 9.x line since Pomelo has no EF Core 10 release yet - see +[provider-mysql/README.md](provider-mysql/README.md). + +**Build:** +```bash +dotnet build provider-mysql/ProviderMySql.sln +``` + +### provider-oracle + +**Location:** `provider-oracle/` + +Oracle via Connection String Mode, using the `Oracle.EntityFrameworkCore` runtime provider. See +[provider-oracle/README.md](provider-oracle/README.md). + +**Build:** +```bash +dotnet build provider-oracle/ProviderOracle.sln +``` + +### provider-firebird + +**Location:** `provider-firebird/` + +Firebird via Connection String Mode, using the `FirebirdSql.EntityFrameworkCore.Firebird` runtime +provider. See [provider-firebird/README.md](provider-firebird/README.md). + +**Build:** +```bash +dotnet build provider-firebird/ProviderFirebird.sln +``` + +### provider-snowflake + +**Location:** `provider-snowflake/` + +Snowflake via Connection String Mode - **entities-only**. No first-party EF Core runtime provider +exists for Snowflake, so `AppDbContext` has entity classes and `DbSet` properties but no +`OnConfiguring`/`UseX` call. See [provider-snowflake/README.md](provider-snowflake/README.md) for +why and how a host application would wire it up. + +**Build:** +```bash +dotnet build provider-snowflake/ProviderSnowflake.sln +``` + +--- + ## Common Configuration All samples use: diff --git a/samples/provider-firebird/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj b/samples/provider-firebird/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj new file mode 100644 index 00000000..dcd51c52 --- /dev/null +++ b/samples/provider-firebird/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj @@ -0,0 +1,52 @@ + + + + net10.0 + enable + enable + + $(NoWarn);CS8669 + + + + + + firebird + + false + + + + + + + + + + + + + + + + + + + + diff --git a/samples/provider-firebird/EntityFrameworkCoreProject/Generated/AppDbContext.cs b/samples/provider-firebird/EntityFrameworkCoreProject/Generated/AppDbContext.cs new file mode 100644 index 00000000..858572a5 --- /dev/null +++ b/samples/provider-firebird/EntityFrameworkCoreProject/Generated/AppDbContext.cs @@ -0,0 +1,63 @@ +// +// Reference copy of what `efcpt` would emit for this schema against Firebird. +// Regenerate for real by setting EfcptConnectionString and building with a live +// Firebird database - see ../README.md. +// +using EntityFrameworkCoreProject.Generated.Entities; +using Microsoft.EntityFrameworkCore; + +namespace EntityFrameworkCoreProject.Generated; + +public partial class AppDbContext : DbContext +{ + public AppDbContext() + { + } + + public AppDbContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet Customers { get; set; } = null!; + + public virtual DbSet Orders { get; set; } = null!; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (!optionsBuilder.IsConfigured) + { + // Illustrative only - real efcpt output (enable-on-configuring=false in + // efcpt-config.json) omits this and expects the host app to configure the + // provider itself. Shown here so this sample demonstrates + // FirebirdSql.EntityFrameworkCore.Firebird end-to-end. + optionsBuilder.UseFirebird("Database=localhost:sample.fdb;User=SYSDBA;Password=masterkey"); + } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.HasIndex(e => e.Email).IsUnique(); + entity.Property(e => e.Name).HasMaxLength(200).IsRequired(); + entity.Property(e => e.Email).HasMaxLength(320).IsRequired(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.TotalAmount).HasColumnType("DECIMAL(18,2)"); + + entity.HasOne(d => d.Customer) + .WithMany(p => p.Orders) + .HasForeignKey(d => d.CustomerId) + .OnDelete(DeleteBehavior.Cascade); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); +} diff --git a/samples/provider-firebird/EntityFrameworkCoreProject/Generated/Entities/Customer.cs b/samples/provider-firebird/EntityFrameworkCoreProject/Generated/Entities/Customer.cs new file mode 100644 index 00000000..722eb9e7 --- /dev/null +++ b/samples/provider-firebird/EntityFrameworkCoreProject/Generated/Entities/Customer.cs @@ -0,0 +1,22 @@ +// +// Reference copy of what `efcpt` would emit for the CUSTOMERS table. +// Regenerate for real by setting EfcptConnectionString and building with a live +// Firebird database - see ../../README.md. +// +using System; +using System.Collections.Generic; + +namespace EntityFrameworkCoreProject.Generated.Entities; + +public partial class Customer +{ + public int Id { get; set; } + + public string Name { get; set; } = null!; + + public string Email { get; set; } = null!; + + public DateTime CreatedAtUtc { get; set; } + + public virtual ICollection Orders { get; set; } = new List(); +} diff --git a/samples/provider-firebird/EntityFrameworkCoreProject/Generated/Entities/Order.cs b/samples/provider-firebird/EntityFrameworkCoreProject/Generated/Entities/Order.cs new file mode 100644 index 00000000..9b1b0239 --- /dev/null +++ b/samples/provider-firebird/EntityFrameworkCoreProject/Generated/Entities/Order.cs @@ -0,0 +1,21 @@ +// +// Reference copy of what `efcpt` would emit for the ORDERS table. +// Regenerate for real by setting EfcptConnectionString and building with a live +// Firebird database - see ../../README.md. +// +using System; + +namespace EntityFrameworkCoreProject.Generated.Entities; + +public partial class Order +{ + public int Id { get; set; } + + public int CustomerId { get; set; } + + public decimal TotalAmount { get; set; } + + public DateTime OrderedAtUtc { get; set; } + + public virtual Customer Customer { get; set; } = null!; +} diff --git a/samples/provider-firebird/EntityFrameworkCoreProject/efcpt-config.json b/samples/provider-firebird/EntityFrameworkCoreProject/efcpt-config.json new file mode 100644 index 00000000..434b292b --- /dev/null +++ b/samples/provider-firebird/EntityFrameworkCoreProject/efcpt-config.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://raw.githubusercontent.com/ErikEJ/EFCorePowerTools/master/samples/efcpt-config.schema.json", + "names": { + "root-namespace": "EntityFrameworkCoreProject", + "dbcontext-name": "AppDbContext", + "dbcontext-namespace": "EntityFrameworkCoreProject.Generated", + "model-namespace": "EntityFrameworkCoreProject.Generated.Entities" + }, + "code-generation": { + "use-t4": false, + "enable-on-configuring": false, + "use-nullable-reference-types": true, + "use-inflector": true + }, + "file-layout": { + "output-path": "Models", + "output-dbcontext-path": "." + }, + "tables": [ + { "name": "[CUSTOMERS]" }, + { "name": "[ORDERS]" } + ] +} diff --git a/samples/provider-firebird/ProviderFirebird.sln b/samples/provider-firebird/ProviderFirebird.sln new file mode 100644 index 00000000..354d2301 --- /dev/null +++ b/samples/provider-firebird/ProviderFirebird.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A2B3C4D-0004-4000-8000-000000000001}" + ProjectSection(SolutionItems) = preProject + nuget.config = nuget.config + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkCoreProject", "EntityFrameworkCoreProject\EntityFrameworkCoreProject.csproj", "{1A2B3C4D-0004-4000-8000-000000000002}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A2B3C4D-0004-4000-8000-000000000002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A2B3C4D-0004-4000-8000-000000000002}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A2B3C4D-0004-4000-8000-000000000002}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A2B3C4D-0004-4000-8000-000000000002}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/samples/provider-firebird/README.md b/samples/provider-firebird/README.md new file mode 100644 index 00000000..41475f07 --- /dev/null +++ b/samples/provider-firebird/README.md @@ -0,0 +1,69 @@ +# Firebird Reference Sample + +This sample demonstrates configuring **JD.Efcpt.Build** for **Firebird** using Connection String +Mode, and shows the shape of the EF Core models `efcpt` generates for a Firebird schema. + +## What This Demonstrates + +- `EfcptProvider=firebird` configuration +- The `FirebirdSql.EntityFrameworkCore.Firebird` runtime provider wired up in `AppDbContext` +- A minimal `efcpt-config.json` for connection-string-mode generation +- How to keep a sample buildable in CI **without a live database** by committing a reference + copy of the generated code (see [Committed Generated Code](#committed-generated-code) below) + +## Project Structure + +``` +provider-firebird/ +├── ProviderFirebird.sln +├── nuget.config +└── EntityFrameworkCoreProject/ + ├── EntityFrameworkCoreProject.csproj + ├── efcpt-config.json + └── Generated/ # Committed reference copy (see below) + ├── AppDbContext.cs + └── Entities/ + ├── Customer.cs + └── Order.cs +``` + +## Connection String Format + +``` +Database=localhost:mydb.fdb;User=SYSDBA;Password=masterkey +``` + +## Regenerating Locally + +Firebird has no DACPAC equivalent, so real generation requires a live database: + +1. Install the Firebird driver satellite package (only needed while regenerating, + JD.Efcpt.Build itself is already referenced): + ```bash + dotnet add EntityFrameworkCoreProject package JD.Efcpt.Build.Firebird + ``` +2. Point at a real database and build: + ```bash + set EfcptConnectionString=Database=localhost:mydb.fdb;User=SYSDBA;Password=masterkey + dotnet build EntityFrameworkCoreProject + ``` + (`export EfcptConnectionString=...` on macOS/Linux.) + +With `EfcptConnectionString` set, `EfcptEnabled` turns on automatically (see the csproj), `efcpt` +regenerates real models into `obj/efcpt/Generated`, and the committed `Generated/` reference copy +in the project directory is excluded from compilation for that build so the two don't collide. + +## Committed Generated Code + +Without a connection string (as in CI), `EfcptEnabled` is forced to `false`, which fully +short-circuits generation - no DB connection, DACPAC query, or `efcpt` invocation happens. Instead +the project compiles the **committed** `Generated/AppDbContext.cs` and `Generated/Entities/*.cs` +files, which are hand-written to mirror realistic `efcpt` output for a `CUSTOMERS`/`ORDERS` +schema. This is what lets `dotnet build EntityFrameworkCoreProject -p:EfcptEnabled=false` succeed +in CI with zero database dependency - see the `samples-build` job in `.github/workflows/ci.yml`. + +## Building + +```bash +dotnet build ProviderFirebird.sln +``` diff --git a/samples/provider-firebird/nuget.config b/samples/provider-firebird/nuget.config new file mode 100644 index 00000000..38998b07 --- /dev/null +++ b/samples/provider-firebird/nuget.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/samples/provider-mysql/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj b/samples/provider-mysql/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj new file mode 100644 index 00000000..c3ae0cb7 --- /dev/null +++ b/samples/provider-mysql/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj @@ -0,0 +1,61 @@ + + + + net10.0 + enable + enable + + $(NoWarn);CS8669 + + + + + + mysql + + false + + + + + + + + + + + + + + + + + + + + diff --git a/samples/provider-mysql/EntityFrameworkCoreProject/Generated/AppDbContext.cs b/samples/provider-mysql/EntityFrameworkCoreProject/Generated/AppDbContext.cs new file mode 100644 index 00000000..c6a6bdb8 --- /dev/null +++ b/samples/provider-mysql/EntityFrameworkCoreProject/Generated/AppDbContext.cs @@ -0,0 +1,67 @@ +// +// Reference copy of what `efcpt` would emit for this schema against MySQL. +// Regenerate for real by setting EfcptConnectionString and building with a live +// MySQL database - see ../README.md. +// +using System; +using EntityFrameworkCoreProject.Generated.Entities; +using Microsoft.EntityFrameworkCore; + +namespace EntityFrameworkCoreProject.Generated; + +public partial class AppDbContext : DbContext +{ + public AppDbContext() + { + } + + public AppDbContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet Customers { get; set; } = null!; + + public virtual DbSet Orders { get; set; } = null!; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (!optionsBuilder.IsConfigured) + { + // Illustrative only - real efcpt output (enable-on-configuring=false in + // efcpt-config.json) omits this and expects the host app to configure the + // provider itself. Shown here so this sample demonstrates + // Pomelo.EntityFrameworkCore.MySql end-to-end. An explicit ServerVersion is used + // instead of ServerVersion.AutoDetect(...) so this never requires a live connection. + optionsBuilder.UseMySql( + "Server=localhost;Database=sample;Uid=root;Pwd=root;Port=3306", + new MySqlServerVersion(new Version(8, 0, 34))); + } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.HasIndex(e => e.Email).IsUnique(); + entity.Property(e => e.Name).HasMaxLength(200).IsRequired(); + entity.Property(e => e.Email).HasMaxLength(320).IsRequired(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.TotalAmount).HasColumnType("decimal(18,2)"); + + entity.HasOne(d => d.Customer) + .WithMany(p => p.Orders) + .HasForeignKey(d => d.CustomerId) + .OnDelete(DeleteBehavior.Cascade); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); +} diff --git a/samples/provider-mysql/EntityFrameworkCoreProject/Generated/Entities/Customer.cs b/samples/provider-mysql/EntityFrameworkCoreProject/Generated/Entities/Customer.cs new file mode 100644 index 00000000..6d45f757 --- /dev/null +++ b/samples/provider-mysql/EntityFrameworkCoreProject/Generated/Entities/Customer.cs @@ -0,0 +1,22 @@ +// +// Reference copy of what `efcpt` would emit for the `customers` table. +// Regenerate for real by setting EfcptConnectionString and building with a live +// MySQL database - see ../../README.md. +// +using System; +using System.Collections.Generic; + +namespace EntityFrameworkCoreProject.Generated.Entities; + +public partial class Customer +{ + public int Id { get; set; } + + public string Name { get; set; } = null!; + + public string Email { get; set; } = null!; + + public DateTime CreatedAtUtc { get; set; } + + public virtual ICollection Orders { get; set; } = new List(); +} diff --git a/samples/provider-mysql/EntityFrameworkCoreProject/Generated/Entities/Order.cs b/samples/provider-mysql/EntityFrameworkCoreProject/Generated/Entities/Order.cs new file mode 100644 index 00000000..92e81a15 --- /dev/null +++ b/samples/provider-mysql/EntityFrameworkCoreProject/Generated/Entities/Order.cs @@ -0,0 +1,21 @@ +// +// Reference copy of what `efcpt` would emit for the `orders` table. +// Regenerate for real by setting EfcptConnectionString and building with a live +// MySQL database - see ../../README.md. +// +using System; + +namespace EntityFrameworkCoreProject.Generated.Entities; + +public partial class Order +{ + public int Id { get; set; } + + public int CustomerId { get; set; } + + public decimal TotalAmount { get; set; } + + public DateTime OrderedAtUtc { get; set; } + + public virtual Customer Customer { get; set; } = null!; +} diff --git a/samples/provider-mysql/EntityFrameworkCoreProject/efcpt-config.json b/samples/provider-mysql/EntityFrameworkCoreProject/efcpt-config.json new file mode 100644 index 00000000..7784ccab --- /dev/null +++ b/samples/provider-mysql/EntityFrameworkCoreProject/efcpt-config.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://raw.githubusercontent.com/ErikEJ/EFCorePowerTools/master/samples/efcpt-config.schema.json", + "names": { + "root-namespace": "EntityFrameworkCoreProject", + "dbcontext-name": "AppDbContext", + "dbcontext-namespace": "EntityFrameworkCoreProject.Generated", + "model-namespace": "EntityFrameworkCoreProject.Generated.Entities" + }, + "code-generation": { + "use-t4": false, + "enable-on-configuring": false, + "use-nullable-reference-types": true, + "use-inflector": true + }, + "file-layout": { + "output-path": "Models", + "output-dbcontext-path": "." + }, + "tables": [ + { "name": "[sample].[customers]" }, + { "name": "[sample].[orders]" } + ] +} diff --git a/samples/provider-mysql/ProviderMySql.sln b/samples/provider-mysql/ProviderMySql.sln new file mode 100644 index 00000000..7d423154 --- /dev/null +++ b/samples/provider-mysql/ProviderMySql.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A2B3C4D-0002-4000-8000-000000000001}" + ProjectSection(SolutionItems) = preProject + nuget.config = nuget.config + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkCoreProject", "EntityFrameworkCoreProject\EntityFrameworkCoreProject.csproj", "{1A2B3C4D-0002-4000-8000-000000000002}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A2B3C4D-0002-4000-8000-000000000002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A2B3C4D-0002-4000-8000-000000000002}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A2B3C4D-0002-4000-8000-000000000002}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A2B3C4D-0002-4000-8000-000000000002}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/samples/provider-mysql/README.md b/samples/provider-mysql/README.md new file mode 100644 index 00000000..3a30cbdc --- /dev/null +++ b/samples/provider-mysql/README.md @@ -0,0 +1,78 @@ +# MySQL Reference Sample + +This sample demonstrates configuring **JD.Efcpt.Build** for **MySQL** using Connection String +Mode, and shows the shape of the EF Core models `efcpt` generates for a MySQL schema. + +## What This Demonstrates + +- `EfcptProvider=mysql` configuration +- The `Pomelo.EntityFrameworkCore.MySql` runtime provider wired up in `AppDbContext` +- A minimal `efcpt-config.json` for connection-string-mode generation +- How to keep a sample buildable in CI **without a live database** by committing a reference + copy of the generated code (see [Committed Generated Code](#committed-generated-code) below) + +## Project Structure + +``` +provider-mysql/ +├── ProviderMySql.sln +├── nuget.config +└── EntityFrameworkCoreProject/ + ├── EntityFrameworkCoreProject.csproj + ├── efcpt-config.json + └── Generated/ # Committed reference copy (see below) + ├── AppDbContext.cs + └── Entities/ + ├── Customer.cs + └── Order.cs +``` + +## Connection String Format + +``` +Server=localhost;Database=mydb;Uid=user;Pwd=pwd;Port=3306 +``` + +## A Note on Package Versions + +`Pomelo.EntityFrameworkCore.MySql`'s latest stable release at the time this sample was written +(9.0.0) only targets **EF Core 9.x** - there is no EF Core 10 build of Pomelo yet. The project +still targets `net10.0` (NuGet resolves Pomelo's `net8.0` asset group fine for a `net10.0` app), +but `Microsoft.EntityFrameworkCore` is deliberately pinned to the `9.0.x` line here instead of the +`10.0.x` line used by the other provider samples, to satisfy Pomelo's dependency range. Revisit +this pin once Pomelo ships an EF Core 10 compatible release. + +## Regenerating Locally + +MySQL has no DACPAC equivalent, so real generation requires a live database: + +1. Install the MySQL driver satellite package (only needed while regenerating, JD.Efcpt.Build + itself is already referenced): + ```bash + dotnet add EntityFrameworkCoreProject package JD.Efcpt.Build.MySqlConnector + ``` +2. Point at a real database and build: + ```bash + set EfcptConnectionString=Server=localhost;Database=mydb;Uid=user;Pwd=pwd + dotnet build EntityFrameworkCoreProject + ``` + (`export EfcptConnectionString=...` on macOS/Linux.) + +With `EfcptConnectionString` set, `EfcptEnabled` turns on automatically (see the csproj), `efcpt` +regenerates real models into `obj/efcpt/Generated`, and the committed `Generated/` reference copy +in the project directory is excluded from compilation for that build so the two don't collide. + +## Committed Generated Code + +Without a connection string (as in CI), `EfcptEnabled` is forced to `false`, which fully +short-circuits generation - no DB connection, DACPAC query, or `efcpt` invocation happens. Instead +the project compiles the **committed** `Generated/AppDbContext.cs` and `Generated/Entities/*.cs` +files, which are hand-written to mirror realistic `efcpt` output for a `customers`/`orders` +schema. This is what lets `dotnet build EntityFrameworkCoreProject -p:EfcptEnabled=false` succeed +in CI with zero database dependency - see the `samples-build` job in `.github/workflows/ci.yml`. + +## Building + +```bash +dotnet build ProviderMySql.sln +``` diff --git a/samples/provider-mysql/nuget.config b/samples/provider-mysql/nuget.config new file mode 100644 index 00000000..38998b07 --- /dev/null +++ b/samples/provider-mysql/nuget.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/samples/provider-oracle/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj b/samples/provider-oracle/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj new file mode 100644 index 00000000..4beac2bf --- /dev/null +++ b/samples/provider-oracle/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj @@ -0,0 +1,52 @@ + + + + net10.0 + enable + enable + + $(NoWarn);CS8669 + + + + + + oracle + + false + + + + + + + + + + + + + + + + + + + + diff --git a/samples/provider-oracle/EntityFrameworkCoreProject/Generated/AppDbContext.cs b/samples/provider-oracle/EntityFrameworkCoreProject/Generated/AppDbContext.cs new file mode 100644 index 00000000..5a7e0eed --- /dev/null +++ b/samples/provider-oracle/EntityFrameworkCoreProject/Generated/AppDbContext.cs @@ -0,0 +1,64 @@ +// +// Reference copy of what `efcpt` would emit for this schema against Oracle. +// Regenerate for real by setting EfcptConnectionString and building with a live +// Oracle database - see ../README.md. +// +using EntityFrameworkCoreProject.Generated.Entities; +using Microsoft.EntityFrameworkCore; + +namespace EntityFrameworkCoreProject.Generated; + +public partial class AppDbContext : DbContext +{ + public AppDbContext() + { + } + + public AppDbContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet Customers { get; set; } = null!; + + public virtual DbSet Orders { get; set; } = null!; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (!optionsBuilder.IsConfigured) + { + // Illustrative only - real efcpt output (enable-on-configuring=false in + // efcpt-config.json) omits this and expects the host app to configure the + // provider itself. Shown here so this sample demonstrates + // Oracle.EntityFrameworkCore end-to-end. + optionsBuilder.UseOracle( + "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=sample)));User Id=sample;Password=sample"); + } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.HasIndex(e => e.Email).IsUnique(); + entity.Property(e => e.Name).HasMaxLength(200).IsRequired(); + entity.Property(e => e.Email).HasMaxLength(320).IsRequired(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.TotalAmount).HasColumnType("NUMBER(18,2)"); + + entity.HasOne(d => d.Customer) + .WithMany(p => p.Orders) + .HasForeignKey(d => d.CustomerId) + .OnDelete(DeleteBehavior.Cascade); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); +} diff --git a/samples/provider-oracle/EntityFrameworkCoreProject/Generated/Entities/Customer.cs b/samples/provider-oracle/EntityFrameworkCoreProject/Generated/Entities/Customer.cs new file mode 100644 index 00000000..1e882019 --- /dev/null +++ b/samples/provider-oracle/EntityFrameworkCoreProject/Generated/Entities/Customer.cs @@ -0,0 +1,22 @@ +// +// Reference copy of what `efcpt` would emit for the [SAMPLE].[CUSTOMERS] table. +// Regenerate for real by setting EfcptConnectionString and building with a live +// Oracle database - see ../../README.md. +// +using System; +using System.Collections.Generic; + +namespace EntityFrameworkCoreProject.Generated.Entities; + +public partial class Customer +{ + public int Id { get; set; } + + public string Name { get; set; } = null!; + + public string Email { get; set; } = null!; + + public DateTime CreatedAtUtc { get; set; } + + public virtual ICollection Orders { get; set; } = new List(); +} diff --git a/samples/provider-oracle/EntityFrameworkCoreProject/Generated/Entities/Order.cs b/samples/provider-oracle/EntityFrameworkCoreProject/Generated/Entities/Order.cs new file mode 100644 index 00000000..3f5c2de2 --- /dev/null +++ b/samples/provider-oracle/EntityFrameworkCoreProject/Generated/Entities/Order.cs @@ -0,0 +1,21 @@ +// +// Reference copy of what `efcpt` would emit for the [SAMPLE].[ORDERS] table. +// Regenerate for real by setting EfcptConnectionString and building with a live +// Oracle database - see ../../README.md. +// +using System; + +namespace EntityFrameworkCoreProject.Generated.Entities; + +public partial class Order +{ + public int Id { get; set; } + + public int CustomerId { get; set; } + + public decimal TotalAmount { get; set; } + + public DateTime OrderedAtUtc { get; set; } + + public virtual Customer Customer { get; set; } = null!; +} diff --git a/samples/provider-oracle/EntityFrameworkCoreProject/efcpt-config.json b/samples/provider-oracle/EntityFrameworkCoreProject/efcpt-config.json new file mode 100644 index 00000000..538e0783 --- /dev/null +++ b/samples/provider-oracle/EntityFrameworkCoreProject/efcpt-config.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://raw.githubusercontent.com/ErikEJ/EFCorePowerTools/master/samples/efcpt-config.schema.json", + "names": { + "root-namespace": "EntityFrameworkCoreProject", + "dbcontext-name": "AppDbContext", + "dbcontext-namespace": "EntityFrameworkCoreProject.Generated", + "model-namespace": "EntityFrameworkCoreProject.Generated.Entities" + }, + "code-generation": { + "use-t4": false, + "enable-on-configuring": false, + "use-nullable-reference-types": true, + "use-inflector": true + }, + "file-layout": { + "output-path": "Models", + "output-dbcontext-path": "." + }, + "tables": [ + { "name": "[SAMPLE].[CUSTOMERS]" }, + { "name": "[SAMPLE].[ORDERS]" } + ] +} diff --git a/samples/provider-oracle/ProviderOracle.sln b/samples/provider-oracle/ProviderOracle.sln new file mode 100644 index 00000000..381cbc37 --- /dev/null +++ b/samples/provider-oracle/ProviderOracle.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A2B3C4D-0003-4000-8000-000000000001}" + ProjectSection(SolutionItems) = preProject + nuget.config = nuget.config + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkCoreProject", "EntityFrameworkCoreProject\EntityFrameworkCoreProject.csproj", "{1A2B3C4D-0003-4000-8000-000000000002}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A2B3C4D-0003-4000-8000-000000000002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A2B3C4D-0003-4000-8000-000000000002}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A2B3C4D-0003-4000-8000-000000000002}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A2B3C4D-0003-4000-8000-000000000002}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/samples/provider-oracle/README.md b/samples/provider-oracle/README.md new file mode 100644 index 00000000..862d651d --- /dev/null +++ b/samples/provider-oracle/README.md @@ -0,0 +1,69 @@ +# Oracle Reference Sample + +This sample demonstrates configuring **JD.Efcpt.Build** for **Oracle** using Connection String +Mode, and shows the shape of the EF Core models `efcpt` generates for an Oracle schema. + +## What This Demonstrates + +- `EfcptProvider=oracle` configuration +- The `Oracle.EntityFrameworkCore` runtime provider wired up in `AppDbContext` +- A minimal `efcpt-config.json` for connection-string-mode generation +- How to keep a sample buildable in CI **without a live database** by committing a reference + copy of the generated code (see [Committed Generated Code](#committed-generated-code) below) + +## Project Structure + +``` +provider-oracle/ +├── ProviderOracle.sln +├── nuget.config +└── EntityFrameworkCoreProject/ + ├── EntityFrameworkCoreProject.csproj + ├── efcpt-config.json + └── Generated/ # Committed reference copy (see below) + ├── AppDbContext.cs + └── Entities/ + ├── Customer.cs + └── Order.cs +``` + +## Connection String Format + +``` +Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=mydb)));User Id=user;Password=pwd +``` + +## Regenerating Locally + +Oracle has no DACPAC equivalent, so real generation requires a live database: + +1. Install the Oracle driver satellite package (only needed while regenerating, JD.Efcpt.Build + itself is already referenced): + ```bash + dotnet add EntityFrameworkCoreProject package JD.Efcpt.Build.Oracle + ``` +2. Point at a real database and build: + ```bash + set EfcptConnectionString=Data Source=(DESCRIPTION=...);User Id=user;Password=pwd + dotnet build EntityFrameworkCoreProject + ``` + (`export EfcptConnectionString=...` on macOS/Linux.) + +With `EfcptConnectionString` set, `EfcptEnabled` turns on automatically (see the csproj), `efcpt` +regenerates real models into `obj/efcpt/Generated`, and the committed `Generated/` reference copy +in the project directory is excluded from compilation for that build so the two don't collide. + +## Committed Generated Code + +Without a connection string (as in CI), `EfcptEnabled` is forced to `false`, which fully +short-circuits generation - no DB connection, DACPAC query, or `efcpt` invocation happens. Instead +the project compiles the **committed** `Generated/AppDbContext.cs` and `Generated/Entities/*.cs` +files, which are hand-written to mirror realistic `efcpt` output for a `CUSTOMERS`/`ORDERS` +schema. This is what lets `dotnet build EntityFrameworkCoreProject -p:EfcptEnabled=false` succeed +in CI with zero database dependency - see the `samples-build` job in `.github/workflows/ci.yml`. + +## Building + +```bash +dotnet build ProviderOracle.sln +``` diff --git a/samples/provider-oracle/nuget.config b/samples/provider-oracle/nuget.config new file mode 100644 index 00000000..38998b07 --- /dev/null +++ b/samples/provider-oracle/nuget.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/samples/provider-postgres/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj b/samples/provider-postgres/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj new file mode 100644 index 00000000..4dfa67de --- /dev/null +++ b/samples/provider-postgres/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj @@ -0,0 +1,53 @@ + + + + net10.0 + enable + enable + + $(NoWarn);CS8669 + + + + + + postgres + + false + + + + + + + + + + + + + + + + + + + + diff --git a/samples/provider-postgres/EntityFrameworkCoreProject/Generated/AppDbContext.cs b/samples/provider-postgres/EntityFrameworkCoreProject/Generated/AppDbContext.cs new file mode 100644 index 00000000..d418355f --- /dev/null +++ b/samples/provider-postgres/EntityFrameworkCoreProject/Generated/AppDbContext.cs @@ -0,0 +1,63 @@ +// +// Reference copy of what `efcpt` would emit for this schema against PostgreSQL. +// Regenerate for real by setting EfcptConnectionString and building with a live +// PostgreSQL database - see ../README.md. +// +using EntityFrameworkCoreProject.Generated.Entities; +using Microsoft.EntityFrameworkCore; + +namespace EntityFrameworkCoreProject.Generated; + +public partial class AppDbContext : DbContext +{ + public AppDbContext() + { + } + + public AppDbContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet Customers { get; set; } = null!; + + public virtual DbSet Orders { get; set; } = null!; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (!optionsBuilder.IsConfigured) + { + // Illustrative only - real efcpt output (enable-on-configuring=false in + // efcpt-config.json) omits this and expects the host app to configure the + // provider itself. Shown here so this sample demonstrates + // Npgsql.EntityFrameworkCore.PostgreSQL end-to-end. + optionsBuilder.UseNpgsql("Host=localhost;Database=sample;Username=postgres;Password=postgres"); + } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.HasIndex(e => e.Email).IsUnique(); + entity.Property(e => e.Name).HasMaxLength(200).IsRequired(); + entity.Property(e => e.Email).HasMaxLength(320).IsRequired(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.TotalAmount).HasColumnType("numeric(18,2)"); + + entity.HasOne(d => d.Customer) + .WithMany(p => p.Orders) + .HasForeignKey(d => d.CustomerId) + .OnDelete(DeleteBehavior.Cascade); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); +} diff --git a/samples/provider-postgres/EntityFrameworkCoreProject/Generated/Entities/Customer.cs b/samples/provider-postgres/EntityFrameworkCoreProject/Generated/Entities/Customer.cs new file mode 100644 index 00000000..22b4cdcd --- /dev/null +++ b/samples/provider-postgres/EntityFrameworkCoreProject/Generated/Entities/Customer.cs @@ -0,0 +1,22 @@ +// +// Reference copy of what `efcpt` would emit for the [public].[customers] table. +// Regenerate for real by setting EfcptConnectionString and building with a live +// PostgreSQL database - see ../../README.md. +// +using System; +using System.Collections.Generic; + +namespace EntityFrameworkCoreProject.Generated.Entities; + +public partial class Customer +{ + public int Id { get; set; } + + public string Name { get; set; } = null!; + + public string Email { get; set; } = null!; + + public DateTime CreatedAtUtc { get; set; } + + public virtual ICollection Orders { get; set; } = new List(); +} diff --git a/samples/provider-postgres/EntityFrameworkCoreProject/Generated/Entities/Order.cs b/samples/provider-postgres/EntityFrameworkCoreProject/Generated/Entities/Order.cs new file mode 100644 index 00000000..cacfd138 --- /dev/null +++ b/samples/provider-postgres/EntityFrameworkCoreProject/Generated/Entities/Order.cs @@ -0,0 +1,21 @@ +// +// Reference copy of what `efcpt` would emit for the [public].[orders] table. +// Regenerate for real by setting EfcptConnectionString and building with a live +// PostgreSQL database - see ../../README.md. +// +using System; + +namespace EntityFrameworkCoreProject.Generated.Entities; + +public partial class Order +{ + public int Id { get; set; } + + public int CustomerId { get; set; } + + public decimal TotalAmount { get; set; } + + public DateTime OrderedAtUtc { get; set; } + + public virtual Customer Customer { get; set; } = null!; +} diff --git a/samples/provider-postgres/EntityFrameworkCoreProject/efcpt-config.json b/samples/provider-postgres/EntityFrameworkCoreProject/efcpt-config.json new file mode 100644 index 00000000..bfde64bd --- /dev/null +++ b/samples/provider-postgres/EntityFrameworkCoreProject/efcpt-config.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://raw.githubusercontent.com/ErikEJ/EFCorePowerTools/master/samples/efcpt-config.schema.json", + "names": { + "root-namespace": "EntityFrameworkCoreProject", + "dbcontext-name": "AppDbContext", + "dbcontext-namespace": "EntityFrameworkCoreProject.Generated", + "model-namespace": "EntityFrameworkCoreProject.Generated.Entities" + }, + "code-generation": { + "use-t4": false, + "enable-on-configuring": false, + "use-nullable-reference-types": true, + "use-inflector": true + }, + "file-layout": { + "output-path": "Models", + "output-dbcontext-path": "." + }, + "tables": [ + { "name": "[public].[customers]" }, + { "name": "[public].[orders]" } + ] +} diff --git a/samples/provider-postgres/ProviderPostgres.sln b/samples/provider-postgres/ProviderPostgres.sln new file mode 100644 index 00000000..d6c653b7 --- /dev/null +++ b/samples/provider-postgres/ProviderPostgres.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A2B3C4D-0001-4000-8000-000000000001}" + ProjectSection(SolutionItems) = preProject + nuget.config = nuget.config + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkCoreProject", "EntityFrameworkCoreProject\EntityFrameworkCoreProject.csproj", "{1A2B3C4D-0001-4000-8000-000000000002}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A2B3C4D-0001-4000-8000-000000000002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A2B3C4D-0001-4000-8000-000000000002}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A2B3C4D-0001-4000-8000-000000000002}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A2B3C4D-0001-4000-8000-000000000002}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/samples/provider-postgres/README.md b/samples/provider-postgres/README.md new file mode 100644 index 00000000..b44b5dd2 --- /dev/null +++ b/samples/provider-postgres/README.md @@ -0,0 +1,69 @@ +# PostgreSQL Reference Sample + +This sample demonstrates configuring **JD.Efcpt.Build** for **PostgreSQL** using Connection +String Mode, and shows the shape of the EF Core models `efcpt` generates for a Postgres schema. + +## What This Demonstrates + +- `EfcptProvider=postgres` configuration +- The `Npgsql.EntityFrameworkCore.PostgreSQL` runtime provider wired up in `AppDbContext` +- A minimal `efcpt-config.json` for connection-string-mode generation +- How to keep a sample buildable in CI **without a live database** by committing a reference + copy of the generated code (see [Committed Generated Code](#committed-generated-code) below) + +## Project Structure + +``` +provider-postgres/ +├── ProviderPostgres.sln +├── nuget.config +└── EntityFrameworkCoreProject/ + ├── EntityFrameworkCoreProject.csproj + ├── efcpt-config.json + └── Generated/ # Committed reference copy (see below) + ├── AppDbContext.cs + └── Entities/ + ├── Customer.cs + └── Order.cs +``` + +## Connection String Format + +``` +Host=localhost;Database=mydb;Username=user;Password=pwd;Port=5432 +``` + +## Regenerating Locally + +PostgreSQL has no DACPAC equivalent, so real generation requires a live database: + +1. Install the PostgreSQL driver satellite package (only needed while regenerating, JD.Efcpt.Build + itself is already referenced): + ```bash + dotnet add EntityFrameworkCoreProject package JD.Efcpt.Build.PostgreSQL + ``` +2. Point at a real database and build: + ```bash + set EfcptConnectionString=Host=localhost;Database=mydb;Username=user;Password=pwd + dotnet build EntityFrameworkCoreProject + ``` + (`export EfcptConnectionString=...` on macOS/Linux.) + +With `EfcptConnectionString` set, `EfcptEnabled` turns on automatically (see the csproj), `efcpt` +regenerates real models into `obj/efcpt/Generated`, and the committed `Generated/` reference copy +in the project directory is excluded from compilation for that build so the two don't collide. + +## Committed Generated Code + +Without a connection string (as in CI), `EfcptEnabled` is forced to `false`, which fully +short-circuits generation - no DB connection, DACPAC query, or `efcpt` invocation happens. Instead +the project compiles the **committed** `Generated/AppDbContext.cs` and `Generated/Entities/*.cs` +files, which are hand-written to mirror realistic `efcpt` output for a `customers`/`orders` +schema. This is what lets `dotnet build EntityFrameworkCoreProject -p:EfcptEnabled=false` succeed +in CI with zero database dependency - see the `samples-build` job in `.github/workflows/ci.yml`. + +## Building + +```bash +dotnet build ProviderPostgres.sln +``` diff --git a/samples/provider-postgres/nuget.config b/samples/provider-postgres/nuget.config new file mode 100644 index 00000000..38998b07 --- /dev/null +++ b/samples/provider-postgres/nuget.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/samples/provider-snowflake/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj b/samples/provider-snowflake/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj new file mode 100644 index 00000000..b87840ef --- /dev/null +++ b/samples/provider-snowflake/EntityFrameworkCoreProject/EntityFrameworkCoreProject.csproj @@ -0,0 +1,56 @@ + + + + net10.0 + enable + enable + + $(NoWarn);CS8669 + + + + + + snowflake + + false + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/provider-snowflake/EntityFrameworkCoreProject/Generated/AppDbContext.cs b/samples/provider-snowflake/EntityFrameworkCoreProject/Generated/AppDbContext.cs new file mode 100644 index 00000000..42b363cb --- /dev/null +++ b/samples/provider-snowflake/EntityFrameworkCoreProject/Generated/AppDbContext.cs @@ -0,0 +1,57 @@ +// +// Reference copy of what `efcpt` would emit for this schema against Snowflake. +// ENTITIES-ONLY: Snowflake has no first-party EF Core runtime provider, so +// there is deliberately no OnConfiguring/UseX call here - the host application +// must supply its own DbContextOptions (e.g. a community/third-party Snowflake +// EF Core provider, or a different data-access approach entirely) to actually +// connect. See ../README.md. +// +using EntityFrameworkCoreProject.Generated.Entities; +using Microsoft.EntityFrameworkCore; + +namespace EntityFrameworkCoreProject.Generated; + +public partial class AppDbContext : DbContext +{ + public AppDbContext() + { + } + + public AppDbContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet Customers { get; set; } = null!; + + public virtual DbSet Orders { get; set; } = null!; + + // No OnConfiguring override - see the file header. Callers must pass configured + // DbContextOptions to the constructor above. + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.HasIndex(e => e.Email).IsUnique(); + entity.Property(e => e.Name).HasMaxLength(200).IsRequired(); + entity.Property(e => e.Email).HasMaxLength(320).IsRequired(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.TotalAmount).HasColumnType("NUMBER(18,2)"); + + entity.HasOne(d => d.Customer) + .WithMany(p => p.Orders) + .HasForeignKey(d => d.CustomerId) + .OnDelete(DeleteBehavior.Cascade); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); +} diff --git a/samples/provider-snowflake/EntityFrameworkCoreProject/Generated/Entities/Customer.cs b/samples/provider-snowflake/EntityFrameworkCoreProject/Generated/Entities/Customer.cs new file mode 100644 index 00000000..cec6d829 --- /dev/null +++ b/samples/provider-snowflake/EntityFrameworkCoreProject/Generated/Entities/Customer.cs @@ -0,0 +1,22 @@ +// +// Reference copy of what `efcpt` would emit for the CUSTOMERS table. +// Snowflake has no first-party EF Core runtime provider, so this sample is +// ENTITIES-ONLY - see ../../README.md. +// +using System; +using System.Collections.Generic; + +namespace EntityFrameworkCoreProject.Generated.Entities; + +public partial class Customer +{ + public int Id { get; set; } + + public string Name { get; set; } = null!; + + public string Email { get; set; } = null!; + + public DateTime CreatedAtUtc { get; set; } + + public virtual ICollection Orders { get; set; } = new List(); +} diff --git a/samples/provider-snowflake/EntityFrameworkCoreProject/Generated/Entities/Order.cs b/samples/provider-snowflake/EntityFrameworkCoreProject/Generated/Entities/Order.cs new file mode 100644 index 00000000..e8e7ee6f --- /dev/null +++ b/samples/provider-snowflake/EntityFrameworkCoreProject/Generated/Entities/Order.cs @@ -0,0 +1,21 @@ +// +// Reference copy of what `efcpt` would emit for the ORDERS table. +// Snowflake has no first-party EF Core runtime provider, so this sample is +// ENTITIES-ONLY - see ../../README.md. +// +using System; + +namespace EntityFrameworkCoreProject.Generated.Entities; + +public partial class Order +{ + public int Id { get; set; } + + public int CustomerId { get; set; } + + public decimal TotalAmount { get; set; } + + public DateTime OrderedAtUtc { get; set; } + + public virtual Customer Customer { get; set; } = null!; +} diff --git a/samples/provider-snowflake/EntityFrameworkCoreProject/efcpt-config.json b/samples/provider-snowflake/EntityFrameworkCoreProject/efcpt-config.json new file mode 100644 index 00000000..e4a7357c --- /dev/null +++ b/samples/provider-snowflake/EntityFrameworkCoreProject/efcpt-config.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://raw.githubusercontent.com/ErikEJ/EFCorePowerTools/master/samples/efcpt-config.schema.json", + "names": { + "root-namespace": "EntityFrameworkCoreProject", + "dbcontext-name": "AppDbContext", + "dbcontext-namespace": "EntityFrameworkCoreProject.Generated", + "model-namespace": "EntityFrameworkCoreProject.Generated.Entities" + }, + "code-generation": { + "use-t4": false, + "enable-on-configuring": false, + "use-nullable-reference-types": true, + "use-inflector": true + }, + "file-layout": { + "output-path": "Models", + "output-dbcontext-path": "." + }, + "tables": [ + { "name": "[SAMPLE].[PUBLIC].[CUSTOMERS]" }, + { "name": "[SAMPLE].[PUBLIC].[ORDERS]" } + ] +} diff --git a/samples/provider-snowflake/ProviderSnowflake.sln b/samples/provider-snowflake/ProviderSnowflake.sln new file mode 100644 index 00000000..20cd55bd --- /dev/null +++ b/samples/provider-snowflake/ProviderSnowflake.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A2B3C4D-0005-4000-8000-000000000001}" + ProjectSection(SolutionItems) = preProject + nuget.config = nuget.config + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkCoreProject", "EntityFrameworkCoreProject\EntityFrameworkCoreProject.csproj", "{1A2B3C4D-0005-4000-8000-000000000002}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A2B3C4D-0005-4000-8000-000000000002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A2B3C4D-0005-4000-8000-000000000002}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A2B3C4D-0005-4000-8000-000000000002}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A2B3C4D-0005-4000-8000-000000000002}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/samples/provider-snowflake/README.md b/samples/provider-snowflake/README.md new file mode 100644 index 00000000..f6c50590 --- /dev/null +++ b/samples/provider-snowflake/README.md @@ -0,0 +1,85 @@ +# Snowflake Reference Sample (Entities-Only) + +This sample demonstrates configuring **JD.Efcpt.Build** for **Snowflake** using Connection String +Mode. Unlike the other four provider samples, this one is **entities-only**. + +## Why Entities-Only + +JD.Efcpt.Build can read Snowflake schema at *generation* time via the +`JD.Efcpt.Build.Snowflake` satellite driver package (it bundles `Snowflake.Data` purely for +schema reading). However, there is no first-party (or well-established community) **EF Core +runtime provider** for Snowflake - i.e. no `UseSnowflake(...)` extension method exists anywhere to +actually connect a `DbContext` to a Snowflake warehouse at runtime. + +So this sample's `AppDbContext` has entity classes and `DbSet` properties, generated exactly +like the other providers, but **no `OnConfiguring` override / no `UseX` call**. A host application +that wants to actually query Snowflake through this `DbContext` needs to supply its own configured +`DbContextOptions` - for example by hand-rolling ADO.NET queries against +`Snowflake.Data` and mapping results into these entity types, or wiring up a third-party/community +EF Core provider if one exists at the time you read this. + +## What This Demonstrates + +- `EfcptProvider=snowflake` configuration +- Entity classes (`Customer`, `Order`) and a `DbSet`-based `AppDbContext` with **no** runtime + provider wiring (only `Microsoft.EntityFrameworkCore` + `Microsoft.EntityFrameworkCore.Relational` + are referenced, the latter purely so `OnModelCreating` can use relational fluent-API members like + `HasColumnType`) +- A minimal `efcpt-config.json` for connection-string-mode generation +- How to keep a sample buildable in CI **without a live database** by committing a reference + copy of the generated code (see [Committed Generated Code](#committed-generated-code) below) + +## Project Structure + +``` +provider-snowflake/ +├── ProviderSnowflake.sln +├── nuget.config +└── EntityFrameworkCoreProject/ + ├── EntityFrameworkCoreProject.csproj + ├── efcpt-config.json + └── Generated/ # Committed reference copy (see below) + ├── AppDbContext.cs # Entities + DbSets only, no OnConfiguring/UseX + └── Entities/ + ├── Customer.cs + └── Order.cs +``` + +## Connection String Format + +``` +Account=myaccount;User=user;Password=pwd;Warehouse=mywarehouse;Database=mydb;Schema=myschema +``` + +## Regenerating Locally + +1. Install the Snowflake driver satellite package (only needed while regenerating, + JD.Efcpt.Build itself is already referenced): + ```bash + dotnet add EntityFrameworkCoreProject package JD.Efcpt.Build.Snowflake + ``` +2. Point at a real warehouse and build: + ```bash + set EfcptConnectionString=Account=myaccount;User=user;Password=pwd;Warehouse=mywarehouse;Database=mydb;Schema=myschema + dotnet build EntityFrameworkCoreProject + ``` + (`export EfcptConnectionString=...` on macOS/Linux.) + +With `EfcptConnectionString` set, `EfcptEnabled` turns on automatically (see the csproj), `efcpt` +regenerates real models into `obj/efcpt/Generated`, and the committed `Generated/` reference copy +in the project directory is excluded from compilation for that build so the two don't collide. The +regenerated `AppDbContext` will still have no `OnConfiguring`/`UseX` call, for the reasons above. + +## Committed Generated Code + +Without a connection string (as in CI), `EfcptEnabled` is forced to `false`, which fully +short-circuits generation - no DB connection, DACPAC query, or `efcpt` invocation happens. Instead +the project compiles the **committed** `Generated/AppDbContext.cs` and `Generated/Entities/*.cs` +files. This is what lets `dotnet build EntityFrameworkCoreProject -p:EfcptEnabled=false` succeed +in CI with zero database dependency - see the `samples-build` job in `.github/workflows/ci.yml`. + +## Building + +```bash +dotnet build ProviderSnowflake.sln +``` diff --git a/samples/provider-snowflake/nuget.config b/samples/provider-snowflake/nuget.config new file mode 100644 index 00000000..38998b07 --- /dev/null +++ b/samples/provider-snowflake/nuget.config @@ -0,0 +1,8 @@ + + + + + + + +