Skip to content

Error: "No primary key was found in the database for table Table TestEntities" when using Database Migration to Spanner with EntityFramework #636

@MarkJungeblut

Description

@MarkJungeblut

Description

When using Google.Cloud.EntityFrameworkCore.Spanner with EF Core migrations, creating a table via migrationBuilder.CreateTable(...) results in the error above:

No primary key was found in the database for table Table TestEntities.
Call SpannerModelValidationConnectionProvider.Instance.EnableDatabaseModelValidation(false)
to disable model validation if this error is a false positive.

It happens because the model validation starts before migrations are applied and therefore fails.
The only workaround suggested by the error message is disabling database model validation globally:

SpannerModelValidationConnectionProvider.Instance.EnableDatabaseModelValidation(false);

This is a significant behavioral change and not acceptable for production use.

Environment

EF Core version: 8.0.22
Google.Cloud.EntityFrameworkCore.Spanner version: 3.8.0
Database: Google Cloud Spanner
Migrations enabled ✅

Reproduction steps

Create a new C# Project with EF Core using the Spanner provider

Add the following migration:

20251216161442_InitialCreate.cs

public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "TestEntities",
            columns: table => new
            {
                Id = table.Column<Guid>(type: "STRING(36)", nullable: false),
                Name = table.Column<string>(type: "STRING", nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_TestEntities", x => x.Id);
            }
        );
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(name: "TestEntities");
    }
}

20251216161442_InitialCreate.Designer.cs

  [DbContext(typeof(TestDbContext))]
    [Migration("20251216161442_InitialCreate")]
    partial class InitialCreate
    {
        protected override void BuildTargetModel(ModelBuilder modelBuilder)
        {
#pragma warning disable 612, 618
            modelBuilder.Entity("YOUR_NAMESPACE", b =>
            {
                b.Property<Guid>("Id")
                    .ValueGeneratedOnAdd()
                    .HasColumnType("STRING(36)");

                b.Property<string>("Name")
                    .IsRequired()
                    .HasColumnType("STRING");

                b.HasKey("Id");
                
                b.ToTable("TestEntities");
            });
#pragma warning restore 612, 618
        }
    }
 [DbContext(typeof(TestDbContext))]
    partial class TestDbContextModelSnapshot : ModelSnapshot
    {
        protected override void BuildModel(ModelBuilder modelBuilder)
        {
#pragma warning disable 612, 618
            modelBuilder.HasAnnotation("ProductVersion", "6.0.16");

            modelBuilder.Entity("YOUR_NAMESPACE", b =>
            {
                b.Property<Guid>("Id")
                    .HasColumnType("STRING(36)");

                b.Property<string>("Name")
                    .IsRequired()
                    .HasColumnType("STRING");

                b.HasKey("Id");
                
                b.ToTable("TestEntities");
            });
#pragma warning restore 612, 618
        }
    }
public class TestDbContext(DbContextOptions<TestDbContext> options, string connectionString)
    : DbContext(options)
{

    public virtual DbSet<TestEntity> TestEntities { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseSpanner(connectionString, options =>
            {
                options.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
            });
        }

        base.OnConfiguring(optionsBuilder);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<TestEntity>(entity =>
        {
            entity.HasKey(it => it.Id);
            entity.ToTable("TestEntities");
        });
        base.OnModelCreating(modelBuilder);
    }
}

Important is to have this property as part of the DBContext. If it's not present, everything works as expected:

    public virtual DbSet<TestEntity> TestEntities { get; set; }

Start the application or integration test with SpannerEmulator and apply the migration to Cloud Spanner:

Expected behavior:
The generated Spanner table should have a primary key defined
EF Core model validation should succeed
No runtime error should occur

Actual behavior
On startup, EF Core throws:
Error Message Appears: No primary key was found in the database for table Table TestEntities. Call SpannerModelValidationConnectionProvider.Instance.EnableDatabaseModelValidation(false) to disable model validation if this error is a false positive.

Setting SpannerModelValidationConnectionProvider.Instance.EnableDatabaseModelValidation(false) fixes the issue but is inacceptable.

Thanks in advance!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions