public static async Task Run(string connectionString)
    {
        using var context = new SpannerSampleDbContext(connectionString);
        var singer = new Singer
        {
            SingerId  = Guid.NewGuid(),
            FirstName = "Yvette",
            LastName  = "Wendelson",
            // SpannerDate is specifically designed to map a DATE column in a Cloud Spanner
            // database to a property of an entity. DateTime properties will by default be
            // mapped to TIMESTAMP columns.
            BirthDate = new SpannerDate(1980, 10, 17),
        };
        await context.Singers.AddAsync(singer);

        await context.SaveChangesAsync();

        // Commonly used properties and methods of SpannerDate are mapped to the equivalent Cloud Spanner functions.
        var singersBornIn1980 = await context.Singers
                                .Where(s => s.BirthDate.GetValueOrDefault().Year == 1980)
                                .OrderBy(s => singer.LastName)
                                .ToListAsync();

        foreach (var s in singersBornIn1980)
        {
            Console.WriteLine($"Born in 1980: {s.FullName} ({s.BirthDate})");
        }
    }
예제 #2
0
    private static async Task Setup(SpannerSampleDbContext context)
    {
        await context.Singers.AddRangeAsync(
            new Singer
        {
            SingerId  = Guid.NewGuid(),
            FirstName = "Alice",
            LastName  = "Henderson",
            BirthDate = new SpannerDate(1983, 10, 19),
        },
            new Singer
        {
            SingerId  = Guid.NewGuid(),
            FirstName = "Peter",
            LastName  = "Allison",
            BirthDate = new SpannerDate(2000, 5, 2),
        },
            new Singer
        {
            SingerId  = Guid.NewGuid(),
            FirstName = "Mike",
            LastName  = "Nicholson",
            BirthDate = new SpannerDate(1976, 8, 31),
        });

        await context.SaveChangesAsync();
    }
    public static async Task Run(string connectionString)
    {
        using var context = new SpannerSampleDbContext(connectionString);

        // Create a concert.
        var(singer, venue) = await GetSingerAndVenueAsync(connectionString);

        var concert = new Concert
        {
            SingerId  = singer.SingerId,
            VenueCode = venue.Code,
            // TIMESTAMP columns are mapped to DateTime by default. Cloud Spanner stores all TIMESTAMP values in UTC.
            // If a TIMESTAMP value is set in local time, the value will be converted to UTC before it is written to
            // Cloud Spanner.
            StartTime = new DateTime(2021, 2, 1, 19, 30, 0, DateTimeKind.Utc),
            Title     = "Theodore in Concert Hall",
        };
        await context.Concerts.AddAsync(concert);

        await context.SaveChangesAsync();

        // Commonly used methods and properties of DateTime are mapped to the equivalent Cloud Spanner functions and can be used in queries.
        var concertsInFeb2021 = await context.Concerts
                                .Where(c => c.StartTime.Month == 2 && c.StartTime.Year == 2021)
                                .OrderBy(c => c.StartTime)
                                .ToListAsync();

        foreach (var c in concertsInFeb2021)
        {
            Console.WriteLine($"February concert: {c.Title}, starts at {c.StartTime}");
        }
    }
    private static async Task <(Singer, Venue)> GetSingerAndVenueAsync(string connectionString)
    {
        using var context     = new SpannerSampleDbContext(connectionString);
        using var transaction = await context.Database.BeginTransactionAsync();

        var venue = await context.Venues.FindAsync("CON");

        if (venue == null)
        {
            venue = new Venue
            {
                Code   = "CON",
                Name   = "Concert Hall",
                Active = true,
            };
            await context.Venues.AddAsync(venue);
        }
        var singer = new Singer
        {
            SingerId  = Guid.NewGuid(),
            FirstName = "Theodore",
            LastName  = "Walterson",
        };
        await context.Singers.AddAsync(singer);

        await context.SaveChangesAsync();

        await transaction.CommitAsync();

        return(singer, venue);
    }
    public static async Task Run(string connectionString)
    {
        using var context = new SpannerSampleDbContext(connectionString);
        await Setup(context);

        var singersBornBefore2000 = context.Singers
                                    .Where(s => s.BirthDate.GetValueOrDefault().Year < 2000)
                                    .OrderBy(s => s.BirthDate)
                                    .AsAsyncEnumerable();

        Console.WriteLine("Singers born before 2000:");
        await foreach (var singer in singersBornBefore2000)
        {
            Console.WriteLine($"{singer.FullName}, born at {singer.BirthDate}");
        }

        var singersStartingWithAl = context.Singers
                                    .Where(s => s.FullName.StartsWith("Al"))
                                    .OrderBy(s => s.LastName)
                                    .AsAsyncEnumerable();

        Console.WriteLine("Singers with a name starting with 'Al':");
        await foreach (var singer in singersStartingWithAl)
        {
            Console.WriteLine($"{singer.FullName}");
        }
    }
예제 #6
0
    public static async Task Run(string connectionString)
    {
        using var context = new SpannerSampleDbContext(connectionString);

        // Singer has a generated column FullName that is the combination of the
        // FirstName and LastName. The value is automatically computed by Cloud Spanner.
        // Setting it manually client side has no effect.
        var singer = new Singer
        {
            SingerId  = Guid.NewGuid(),
            FirstName = "Alice",
            LastName  = "Jameson"
        };
        await context.Singers.AddAsync(singer);

        await context.SaveChangesAsync();

        // Entity Framework will automatically fetch the computed value for FullName
        // from Cloud Spanner after it has been written.
        Console.WriteLine($"Added singer with full name {singer.FullName}");

        // Updating the last name of the singer will also update the full name.
        singer.LastName = "Jameson - Cooper";
        await context.SaveChangesAsync();

        Console.WriteLine($"Updated singer's full name to {singer.FullName}");
    }
예제 #7
0
    private static async Task Setup(SpannerSampleDbContext context)
    {
        var singer = new Singer
        {
            SingerId  = Guid.NewGuid(),
            FirstName = "Alice",
            LastName  = "Henderson",
            BirthDate = new SpannerDate(1983, 10, 19),
        };
        await context.Singers.AddAsync(singer);

        await context.Albums.AddRangeAsync(new Album
        {
            AlbumId  = Guid.NewGuid(),
            SingerId = singer.SingerId,
            Title    = "Henderson's first",
        },
                                           new Album
        {
            AlbumId  = Guid.NewGuid(),
            SingerId = singer.SingerId,
            Title    = "Henderson's second",
        },
                                           new Album
        {
            AlbumId  = Guid.NewGuid(),
            SingerId = singer.SingerId,
            Title    = "Henderson's third",
        });

        await context.SaveChangesAsync();
    }
예제 #8
0
    public static async Task Run(string connectionString)
    {
        using var context = new SpannerSampleDbContext(connectionString);

        // Start a read-only transaction with strong timestamp bound (i.e. read everything that has been committed up until now).
        // This transaction will be assigned a read-timestamp at the first read that it executes and all
        // following read operations will also use the same read timestamp. Any changes that are made after
        // this read timestamp will not be visible to the transaction.
        // NOTE: Although read-only transaction cannot be committed or rollbacked, they still need to be disposed.
        using var transaction = await context.Database.BeginReadOnlyTransactionAsync(TimestampBound.Strong);

        // Search for a singer with a new id. This will establish a read timestamp for the read-only transaction.
        var singerId = Guid.NewGuid();
        var count    = await context.Singers
                       .Where(s => s.SingerId == singerId)
                       .CountAsync();

        Console.WriteLine($"Searching for singer with id {singerId} yielded {count} result(s)");

        // Create a new database context and insert a singer with the given id. This singer will not be visible
        // to the read-only transaction.
        using (var writeContext = new SpannerSampleDbContext(connectionString))
        {
            await writeContext.Singers.AddAsync(new Singer
            {
                SingerId  = singerId,
                FirstName = "Alice",
                LastName  = "Goldberg",
            });

            await writeContext.SaveChangesAsync();
        }

        // The count should not have changed, as the read-only transaction will continue to use
        // the read timestamp assigned during the first read.
        count = await context.Singers
                .Where(s => s.SingerId == singerId)
                .CountAsync();

        Console.WriteLine($"Searching for singer with id {singerId} yielded {count} result(s)");

        // Now 'commit' the read-only transaction. This will close the transaction and allow us to start
        // a new one on the context.
        await transaction.CommitAsync();

        // Start a new read-only transaction. TimestampBound.Strong is default so we don't have to specify it.
        using var newTransaction = await context.Database.BeginReadOnlyTransactionAsync();

        count = await context.Singers
                .Where(s => s.SingerId == singerId)
                .CountAsync();

        Console.WriteLine($"Searching for singer with id {singerId} yielded {count} result(s)");
    }
예제 #9
0
    public static async Task Run(string connectionString)
    {
        using var context = new SpannerSampleDbContext(connectionString);
        var singer = new Singer
        {
            SingerId  = Guid.NewGuid(),
            FirstName = "Brian",
            LastName  = "Truman",
        };
        await context.Singers.AddAsync(singer);

        var album = new Album
        {
            AlbumId  = Guid.NewGuid(),
            SingerId = singer.SingerId,
            Title    = "Potatoes",
        };
        await context.Albums.AddAsync(album);

        // Tracks are interleaved in Albums. This relationship is treated the same as FOREIGN KEYS in
        // Entity Framework Core, which means that we can traverse the relationship both ways, and any
        // Track that references an Album that has is associated with the database context will also be
        // associated with the context.
        album.Tracks.Add(new Track
        {
            AlbumId = album.AlbumId,
            TrackId = 1L,
            Title   = "They are good",
        });
        album.Tracks.Add(new Track
        {
            AlbumId = album.AlbumId,
            TrackId = 2L,
            Title   = "Some find them delicious",
        });
        // This will save 1 singer, 1 album and 2 tracks.
        var updateCount = await context.SaveChangesAsync();

        Console.WriteLine($"Saved {updateCount} rows");

        // We can traverse the relationship from Track to Album.
        foreach (var track in album.Tracks)
        {
            Console.WriteLine($"'{track.Title}' is on album '{track.Album.Title}'");
        }

        // Tracks are defined as `INTERLEAVE IN PARENT Albums ON DELETE CASCADE`. Deleting an
        // album will therefore automatically also delete its tracks.
        context.Albums.Remove(album);
        var deletedCount = await context.SaveChangesAsync();

        Console.WriteLine($"Deleted {deletedCount} albums and tracks");
    }
예제 #10
0
    public static async Task Run(string connectionString)
    {
        using var context = new SpannerSampleDbContext(connectionString);

        // Start a read/write transaction that will be used with the database context.
        using var transaction = await context.Database.BeginTransactionAsync();

        // Create a new Singer, add it to the context and save the changes.
        // These changes have not yet been committed to the database and are
        // therefore not readable for other processes.
        var singerId = Guid.NewGuid();
        await context.Singers.AddAsync(new Singer
        {
            SingerId  = singerId,
            FirstName = "Bernhard",
            LastName  = "Bennet"
        });

        var count = await context.SaveChangesAsync();

        Console.WriteLine($"Added {count} singer in a transaction.");

        // Now try to read the singer in a different context which will use a different transaction.
        // This will return null, as pending changes from other transactions are not visible.
        using var contextWithoutTransaction = new SpannerSampleDbContext(connectionString);
        var exists = await contextWithoutTransaction.Singers
                     .FromSqlInterpolated($"SELECT * FROM Singers WHERE SingerId={singerId}")
                     .FirstOrDefaultAsync();

        Console.WriteLine($"Can read singer outside of transaction: {exists != null}");

        // Now try to read the same using the context with the transaction. This will return true as
        // a transaction can read its own writes. The Cloud Spanner Entity Framework Core provider
        // uses DML by default for updates that are executed in manual transactions in order to support
        // the read-your-writes feature.
        exists = await context.Singers
                 .FromSqlInterpolated($"SELECT * FROM Singers WHERE SingerId={singerId}")
                 .FirstOrDefaultAsync();

        Console.WriteLine($"Can read singer inside transaction: {exists != null}");

        // Commit the transaction. The singer is now also readable in a context without the transaction.
        await transaction.CommitAsync();

        exists = await contextWithoutTransaction.Singers
                 .FromSqlInterpolated($"SELECT * FROM Singers WHERE SingerId={singerId}")
                 .FirstOrDefaultAsync();

        Console.WriteLine($"Can read singer after commit: {exists != null}");
    }
    public static async Task Run(string connectionString)
    {
        using var context  = new SpannerSampleDbContext(connectionString);
        var(singer, album) = await GetSingerAndAlbumAsync(context);

        // A track has two array columns: Lyrics and LyricsLanguages. The length of both arrays
        // should be equal, as the LyricsLanguages indicate the language of the corresponding Lyrics.
        var track1 = new Track
        {
            AlbumId = album.AlbumId,
            TrackId = 1,
            Title   = "Whenever",
            Lyrics  = new List <string> {
                "Lyrics 1", "Lyrics 2"
            },
            LyricsLanguages = new List <string> {
                "EN", "DE"
            },
        };
        var track2 = new Track
        {
            AlbumId = album.AlbumId,
            TrackId = 2,
            Title   = "Wherever",
            // Array elements may be null, regardless whether the column itself is defined as NULL/NOT NULL.
            Lyrics = new List <string> {
                null, "Lyrics 2"
            },
            LyricsLanguages = new List <string> {
                "EN", "DE"
            },
        };
        var track3 = new Track
        {
            AlbumId = album.AlbumId,
            TrackId = 3,
            Title   = "Probably",
            // ARRAY columns may also be null.
            Lyrics          = null,
            LyricsLanguages = null,
        };
        await context.Tracks.AddRangeAsync(track1, track2, track3);

        var count = await context.SaveChangesAsync();

        Console.WriteLine($"Added {count} tracks.");

        // TODO: Add sample for querying using array functions.
    }
예제 #12
0
    public static async Task Run(string connectionString)
    {
        using var context = new SpannerSampleDbContext(connectionString);
        await Setup(context);

        var singers = context.Singers
                      .OrderBy(s => s.BirthDate)
                      .AsAsyncEnumerable();

        Console.WriteLine("Found singers:");
        await foreach (var singer in singers)
        {
            Console.WriteLine($"{singer.FullName}, born at {singer.BirthDate}");
        }
    }
예제 #13
0
    private static async Task <(Concert, Track)> GetConcertAndTrackAsync(SpannerSampleDbContext context)
    {
        var singer = new Singer
        {
            SingerId  = Guid.NewGuid(),
            FirstName = "Alice",
            LastName  = "Jameson",
        };
        await context.Singers.AddAsync(singer);

        var album = new Album
        {
            AlbumId  = Guid.NewGuid(),
            Title    = "Rainforest",
            SingerId = singer.SingerId,
        };
        await context.Albums.AddAsync(album);

        var track = new Track
        {
            AlbumId = album.AlbumId,
            TrackId = 1,
            Title   = "Butterflies",
        };
        await context.Tracks.AddAsync(track);

        if (await context.Venues.FindAsync("CON") == null)
        {
            await context.Venues.AddAsync(new Venue
            {
                Code   = "CON",
                Name   = "Concert Hall",
                Active = true,
            });
        }
        var concert = new Concert
        {
            VenueCode = "CON",
            SingerId  = singer.SingerId,
            StartTime = new DateTime(2021, 1, 27, 18, 0, 0, DateTimeKind.Utc),
            Title     = "Alice Jameson - LIVE in Concert Hall",
        };
        await context.AddAsync(concert);

        await context.SaveChangesAsync();

        return(concert, track);
    }
예제 #14
0
    public static async Task Run(string connectionString)
    {
        using var context = new SpannerSampleDbContext(connectionString);
        await Setup(context);

        var query = from album in context.Albums
                    join singer in context.Singers
                    on album.SingerId equals singer.SingerId
                    select new { singer, album };

        Console.WriteLine("Singers and albums:");
        await foreach (var row in query.AsAsyncEnumerable())
        {
            Console.WriteLine($"Singer {row.singer.FullName} produced album {row.album.Title}");
        }
    }
    public static async Task Run(string connectionString)
    {
        using var context = new SpannerSampleDbContext(connectionString);

        // Create a new Singer, add it to the context and save the changes.
        await context.Singers.AddAsync(new Singer
        {
            // Cloud Spanner does not support server side generation of Guid values,
            // so it must always be generated by the client.
            SingerId  = Guid.NewGuid(),
            FirstName = "Jamie",
            LastName  = "Yngvason"
        });

        var count = await context.SaveChangesAsync();

        // SaveChangesAsync returns the total number of rows that was inserted/updated/deleted.
        Console.WriteLine($"Added {count} singer.");
    }
예제 #16
0
    public static async Task Run(string connectionString)
    {
        var startTime = new DateTime(2021, 1, 27, 19, 0, 0, DateTimeKind.Utc);

        using var context = new SpannerSampleDbContext(connectionString);
        (Concert concert, Track track) = await GetConcertAndTrackAsync(context);

        // Create a new performance and save it.
        // This will automatically fill the CreatedAt property with the commit timestamp of the transaction.
        var performance = new Performance
        {
            VenueCode        = concert.VenueCode,
            SingerId         = concert.SingerId,
            ConcertStartTime = concert.StartTime,
            AlbumId          = track.AlbumId,
            TrackId          = track.TrackId,
            StartTime        = startTime,
            Rating           = 7.5,
        };
        await context.Performances.AddAsync(performance);

        var count = await context.SaveChangesAsync();

        Console.WriteLine($"Saved {count} performance");
        Console.WriteLine($"Performance was created at {performance.CreatedAt}");

        // Last updated is only filled when the entity is updated (not when it is inserted).
        var lastUpdated = performance.LastUpdatedAt == null ? "<never>" : performance.LastUpdatedAt.ToString();

        Console.WriteLine($"Performance was last updated at {lastUpdated}");

        // Update the performance. This will also fill the LastUpdatedAt property.
        performance.Rating = 8.5;
        count = await context.SaveChangesAsync();

        Console.WriteLine($"Updated {count} performance");
        Console.WriteLine($"Performance was created at {performance.CreatedAt}");
        Console.WriteLine($"Performance was updated at {performance.LastUpdatedAt}");
    }
    private static async Task <(Singer, Album)> GetSingerAndAlbumAsync(SpannerSampleDbContext context)
    {
        var singer = new Singer
        {
            SingerId  = Guid.NewGuid(),
            FirstName = "Hannah",
            LastName  = "Polansky"
        };
        await context.Singers.AddAsync(singer);

        var album = new Album
        {
            AlbumId  = Guid.NewGuid(),
            SingerId = singer.SingerId,
            Title    = "Somewhere",
        };
        await context.Albums.AddAsync(album);

        await context.SaveChangesAsync();

        return(singer, album);
    }