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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions