Skip to content

Commit b3a9570

Browse files
authored
feat: support auto-generated primary keys with IDENTITY columns (#503)
* feat: support auto-generated primary keys with IDENTITY columns Adds support for auto-generated primary keys using IDENTITY columns. Standard primary key columns in Entity Framework are translated to `GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE)` for Spanner. The methods added in SpannerModelBuilderExtensions can be used to configure the strategy for generating primary keys: `SpannerModelBuilderExtensions.DisableIdentityColumns()` disables the use of IDENTITY for primary keys. `SpannerModelBuilderExtensions.UseAutoIncrement()` configures the Entity Framework provider to generate `AUTO_INCREMENT` columns instead of a `GENERATED BY DEFAULT` clause. This can only be used if the database has a `default_sequence_kind` option set. The `SpannerModelExtensions.SetIdentityOptions` method can be used to manually configure the generation strategy. This can be used to set a custom sequence kind. * chore: cleanup
1 parent 9a4bc51 commit b3a9570

File tree

51 files changed

+709
-171
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+709
-171
lines changed

Google.Cloud.EntityFrameworkCore.Spanner.IntegrationTests/MigrationTests/MigrationModels/AllColType.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
using Google.Cloud.EntityFrameworkCore.Spanner.Storage;
1615
using Google.Cloud.Spanner.V1;
1716
using System;
1817
using System.Collections.Generic;
@@ -23,7 +22,7 @@ namespace Google.Cloud.EntityFrameworkCore.Spanner.IntegrationTests
2322
{
2423
public partial class AllColType
2524
{
26-
public int Id { get; set; }
25+
public long Id { get; set; }
2726
public short? ColShort { get; set; }
2827
public int? ColInt { get; set; }
2928
public long? ColLong { get; set; }

Google.Cloud.EntityFrameworkCore.Spanner.IntegrationTests/MigrationTests/Migrations/20210830082803_Initial.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
2929
name: "AllColTypes",
3030
columns: table => new
3131
{
32-
Id = table.Column<int>(nullable: false),
32+
Id = table.Column<int>(nullable: false).Annotation(SpannerAnnotationNames.Identity, SpannerIdentityOptionsData.Default),
3333
ColShort = table.Column<short>(nullable: true),
3434
ColInt = table.Column<int>(nullable: true),
3535
ColLong = table.Column<long>(nullable: true),

Google.Cloud.EntityFrameworkCore.Spanner.IntegrationTests/MigrationTests/SpannerMigrationTest.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,15 @@ public async Task CanInsertAndUpdateNullValues()
9090
{
9191
using var context = new TestMigrationDbContext(_fixture.DatabaseName);
9292

93-
context.AllColTypes.Add(new AllColType
93+
var entry = context.AllColTypes.Add(new AllColType
9494
{
95-
Id = 1,
9695
ColString = "Test String"
9796
});
9897

9998
var rowCount = await context.SaveChangesAsync();
10099
Assert.Equal(1, rowCount);
101-
var row = await context.AllColTypes.FindAsync(1);
100+
Assert.True(entry.Entity.Id > 0L, $"entry.Entity.Id > 0L, but was {entry.Entity.Id}");
101+
var row = await context.AllColTypes.FindAsync(entry.Entity.Id);
102102
Assert.NotNull(row);
103103
Assert.Null(row.ColTimestamp);
104104
Assert.Null(row.ColShort);
@@ -115,7 +115,7 @@ public async Task CanInsertAndUpdateNullValues()
115115
await context.SaveChangesAsync();
116116

117117
// Retrieve updated row from database
118-
row = await context.AllColTypes.FindAsync(1);
118+
row = await context.AllColTypes.FindAsync(row.Id);
119119
Assert.NotNull(row);
120120
Assert.NotNull(row.ColBool);
121121
Assert.NotNull(row.ColBoolArray);
@@ -135,7 +135,7 @@ public async Task CanInsertAndUpdateNullValues()
135135
await context.SaveChangesAsync();
136136

137137
// Retrieve updated row from database
138-
row = await context.AllColTypes.FindAsync(1);
138+
row = await context.AllColTypes.FindAsync(row.Id);
139139
Assert.NotNull(row);
140140
Assert.Null(row.ColBool);
141141
Assert.Null(row.ColBoolArray);
@@ -202,7 +202,7 @@ public async Task CanInsertAndUpdateRowWithAllDataTypes()
202202
// Get inserted Rows from database.
203203
using (var context = new TestMigrationDbContext(_fixture.DatabaseName))
204204
{
205-
var row = await context.AllColTypes.FindAsync(10);
205+
var row = await context.AllColTypes.FindAsync(10L);
206206
Assert.NotNull(row);
207207
Assert.Equal(10, row.Id);
208208
Assert.True(row.ColBool);
@@ -303,7 +303,7 @@ public async Task CanInsertAndUpdateRowWithAllDataTypes()
303303
// Retrieve Updated Rows
304304
using (var context = new TestMigrationDbContext(_fixture.DatabaseName))
305305
{
306-
var row = await context.AllColTypes.FindAsync(10);
306+
var row = await context.AllColTypes.FindAsync(10L);
307307
Assert.NotNull(row);
308308
Assert.False(row.ColBool);
309309
Assert.Equal(new [] { false, true, false }, row.ColBoolArray);
@@ -518,7 +518,7 @@ public async Task CanInsertAndDeleteInterleaveOnDeleteCascade()
518518
};
519519
context.Articles.Add(article);
520520
var rowCount = await context.SaveChangesAsync();
521-
Assert.Equal(2, rowCount);
521+
Assert.Equal(1, rowCount);
522522
}
523523

524524
using (var context = new TestMigrationDbContext(_fixture.DatabaseName))

Google.Cloud.EntityFrameworkCore.Spanner.IntegrationTests/Model/TicketSales.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 Google LLC
1+
// Copyright 2025 Google LLC
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.

Google.Cloud.EntityFrameworkCore.Spanner.IntegrationTests/SampleDataModel.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ CREATE TABLE Performances (
7777
CONSTRAINT FK_Performances_Tracks FOREIGN KEY (AlbumId, TrackId) REFERENCES Tracks (AlbumId, TrackId),
7878
) PRIMARY KEY (VenueCode, SingerId, StartTime);
7979

80+
CREATE TABLE TicketSales (
81+
Id INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
82+
CustomerName STRING(MAX),
83+
) PRIMARY KEY (Id);
84+
8085
CREATE TABLE TableWithAllColumnTypes (
8186
ColInt64 INT64 NOT NULL,
8287
ColFloat64 FLOAT64,

Google.Cloud.EntityFrameworkCore.Spanner.IntegrationTests/TransactionTests.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,13 @@ public async Task CanExecuteStaleRead()
313313
}
314314
}
315315

316-
[Fact]
316+
[SkippableFact]
317317
public async Task CanUseComputedColumnAndCommitTimestamp()
318318
{
319+
// The emulator does not allow a THEN RETURN clause in combination with
320+
// PENDING_COMMIT_TIMESTAMP(), even if the returned columns do not include
321+
// the pending commit timestamp column.
322+
Skip.If(Environment.GetEnvironmentVariable("SPANNER_EMULATOR_HOST") != null);
319323
var id1 = _fixture.RandomLong();
320324
var id2 = _fixture.RandomLong();
321325

Google.Cloud.EntityFrameworkCore.Spanner.Samples/SampleModel/Album.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public Album()
3434
/// See TicketSale for an example of using a server-side generated primary key value using a
3535
/// bit-reversed sequence.
3636
/// </summary>
37-
public Guid AlbumId { get; set; }
37+
public long AlbumId { get; set; }
3838
public string Title { get; set; }
3939

4040
/// <summary>
@@ -46,7 +46,7 @@ public Album()
4646
/// <summary>
4747
/// FOREIGN KEY value referencing a Singer record.
4848
/// </summary>
49-
public Guid SingerId { get; set; }
49+
public long SingerId { get; set; }
5050

5151
public virtual Singer Singer { get; set; }
5252

Google.Cloud.EntityFrameworkCore.Spanner.Samples/SampleModel/Concert.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public Concert()
3333
/// them from DATE columns that by default are mapped to <see cref="SpannerDate"/>.
3434
/// </summary>
3535
public DateTime StartTime { get; set; }
36-
public Guid SingerId { get; set; }
36+
public long SingerId { get; set; }
3737
public string Title { get; set; }
3838

3939
public virtual Singer Singer { get; set; }

Google.Cloud.EntityFrameworkCore.Spanner.Samples/SampleModel/Performance.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ public partial class Performance : VersionedEntity
2020
{
2121
public string VenueCode { get; set; }
2222
public DateTime ConcertStartTime { get; set; }
23-
public Guid SingerId { get; set; }
24-
public Guid AlbumId { get; set; }
23+
public long SingerId { get; set; }
24+
public long AlbumId { get; set; }
2525
public long TrackId { get; set; }
2626
public DateTime? StartTime { get; set; }
2727
public double? Rating { get; set; }

Google.Cloud.EntityFrameworkCore.Spanner.Samples/SampleModel/SampleDataModel.sql

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
*/
1515

1616
CREATE TABLE Singers (
17-
SingerId STRING(36) NOT NULL,
17+
SingerId INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
1818
FirstName STRING(200),
1919
LastName STRING(200) NOT NULL,
2020
BirthDate DATE,
@@ -26,16 +26,16 @@ CREATE TABLE Singers (
2626
CREATE INDEX Idx_Singers_FullName ON Singers (FullName);
2727

2828
CREATE TABLE Albums (
29-
AlbumId STRING(36) NOT NULL,
29+
AlbumId INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
3030
Title STRING(100) NOT NULL,
3131
ReleaseDate DATE,
32-
SingerId STRING(36) NOT NULL,
32+
SingerId INT64 NOT NULL,
3333
Version INT64 NOT NULL,
3434
CONSTRAINT FK_Albums_Singers FOREIGN KEY (SingerId) REFERENCES Singers (SingerId),
3535
) PRIMARY KEY (AlbumId);
3636

3737
CREATE TABLE Tracks (
38-
AlbumId STRING(36) NOT NULL,
38+
AlbumId INT64 NOT NULL,
3939
TrackId INT64 NOT NULL,
4040
Title STRING(200) NOT NULL,
4141
Duration NUMERIC,
@@ -58,7 +58,7 @@ CREATE TABLE Venues (
5858
CREATE TABLE Concerts (
5959
VenueCode STRING(10) NOT NULL,
6060
StartTime TIMESTAMP NOT NULL,
61-
SingerId STRING(36) NOT NULL,
61+
SingerId INT64 NOT NULL,
6262
Title STRING(200),
6363
Version INT64 NOT NULL,
6464
CONSTRAINT FK_Concerts_Venues FOREIGN KEY (VenueCode) REFERENCES Venues (Code),
@@ -68,8 +68,8 @@ CREATE TABLE Concerts (
6868
CREATE TABLE Performances (
6969
VenueCode STRING(10) NOT NULL,
7070
ConcertStartTime TIMESTAMP NOT NULL,
71-
SingerId STRING(36) NOT NULL,
72-
AlbumId STRING(36) NOT NULL,
71+
SingerId INT64 NOT NULL,
72+
AlbumId INT64 NOT NULL,
7373
TrackId INT64 NOT NULL,
7474
StartTime TIMESTAMP,
7575
Rating FLOAT64,
@@ -94,7 +94,7 @@ CREATE TABLE TicketSales (
9494
Seats ARRAY<STRING(10)> NOT NULL,
9595
VenueCode STRING(10) NOT NULL,
9696
ConcertStartTime TIMESTAMP NOT NULL,
97-
SingerId STRING(36) NOT NULL,
97+
SingerId INT64 NOT NULL,
9898
Version INT64 NOT NULL,
9999
CONSTRAINT FK_TicketSales_Concerts FOREIGN KEY (VenueCode, ConcertStartTime, SingerId) REFERENCES Concerts (VenueCode, StartTime, SingerId),
100100
) PRIMARY KEY (Id);

0 commit comments

Comments
 (0)