From 6a52218e9f29f0e8a454040d2437bdc1f845f7ac Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Mon, 26 May 2025 18:24:47 +0300 Subject: [PATCH 1/5] feat: Entity Framework Core YDB Quick Start --- .github/workflows/tests.yml | 8 +++++-- examples/src/EF/EF.csproj | 14 ------------- .../EntityFrameworkCore.Ydb.QuickStart.csproj | 21 +++++++++++++++++++ .../Program.cs | 11 +++++----- .../README.md | 16 ++++++++++++++ examples/src/YdbExamples.sln | 2 +- 6 files changed, 50 insertions(+), 22 deletions(-) delete mode 100644 examples/src/EF/EF.csproj create mode 100644 examples/src/EntityFrameworkCore.Ydb.QuickStart/EntityFrameworkCore.Ydb.QuickStart.csproj rename examples/src/{EF => EntityFrameworkCore.Ydb.QuickStart}/Program.cs (88%) create mode 100644 examples/src/EntityFrameworkCore.Ydb.QuickStart/README.md diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b0de63b0..b71f4a75 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -195,7 +195,11 @@ jobs: run: | cd ./examples/src/Topic dotnet run - - name: Run EF example + - name: Run Entity Framework Core example run: | - cd ./examples/src/EF + cd ./examples/src/EntityFrameworkCore.Ydb.QuickStart + dotnet tool install --global dotnet-ef + dotnet add package Microsoft.EntityFrameworkCore.Design + dotnet ef migrations add InitialCreate + dotnet ef database update dotnet run diff --git a/examples/src/EF/EF.csproj b/examples/src/EF/EF.csproj deleted file mode 100644 index 489eb609..00000000 --- a/examples/src/EF/EF.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - Exe - net8.0 - enable - enable - EF - - - - - - diff --git a/examples/src/EntityFrameworkCore.Ydb.QuickStart/EntityFrameworkCore.Ydb.QuickStart.csproj b/examples/src/EntityFrameworkCore.Ydb.QuickStart/EntityFrameworkCore.Ydb.QuickStart.csproj new file mode 100644 index 00000000..b20b9a8f --- /dev/null +++ b/examples/src/EntityFrameworkCore.Ydb.QuickStart/EntityFrameworkCore.Ydb.QuickStart.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + enable + enable + EntityFrameworkCore.Ydb + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + diff --git a/examples/src/EF/Program.cs b/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs similarity index 88% rename from examples/src/EF/Program.cs rename to examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs index eebbcd21..66e44905 100644 --- a/examples/src/EF/Program.cs +++ b/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs @@ -3,24 +3,25 @@ await using var db = new BloggingContext(); -await db.Database.EnsureDeletedAsync(); -await db.Database.EnsureCreatedAsync(); - +// Create Console.WriteLine("Inserting a new blog"); db.Add(new Blog { Url = "http://blogs.msdn.com/adonet" }); - await db.SaveChangesAsync(); +// Read Console.WriteLine("Querying for a blog"); var blog = await db.Blogs .OrderBy(b => b.BlogId) .FirstAsync(); +// Update Console.WriteLine("Updating the blog and adding a post"); blog.Url = "https://devblogs.microsoft.com/dotnet"; -blog.Posts.Add(new Post { Title = "Hello World", Content = "I wrote an app using EF Core!" }); +blog.Posts.Add( + new Post { Title = "Hello World", Content = "I wrote an app using EF Core!" }); await db.SaveChangesAsync(); +// Delete Console.WriteLine("Delete the blog"); db.Remove(blog); await db.SaveChangesAsync(); diff --git a/examples/src/EntityFrameworkCore.Ydb.QuickStart/README.md b/examples/src/EntityFrameworkCore.Ydb.QuickStart/README.md new file mode 100644 index 00000000..0d72ab69 --- /dev/null +++ b/examples/src/EntityFrameworkCore.Ydb.QuickStart/README.md @@ -0,0 +1,16 @@ +# Entity Framework Core YDB Quick Start + +A sample application from the [official documentation](https://learn.microsoft.com/en-us/ef/core/get-started/overview/first-app?tabs=netcore-cli) +shows how to get started with EF, define a model, populate it with data and then query the database. + +## Running QuickStart + +1. Setup [YDB local](https://ydb.tech/docs/en/reference/docker/start). +2. Create the database: + ```bash + dotnet tool install --global dotnet-ef + dotnet add package Microsoft.EntityFrameworkCore.Design + dotnet ef migrations add InitialCreate + dotnet ef database update + ``` +3. Run the app: `dotnet run` diff --git a/examples/src/YdbExamples.sln b/examples/src/YdbExamples.sln index df0bcce6..3f99e3df 100644 --- a/examples/src/YdbExamples.sln +++ b/examples/src/YdbExamples.sln @@ -17,7 +17,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YC", "YC\YC.csproj", "{753E EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Topic", "Topic\Topic.csproj", "{0FB9A1C2-4B0C-4AE4-9FA2-E0ED37802A6E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EF", "EF\EF.csproj", "{0CE9DF93-1411-4E73-BA88-A66018FAB948}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkCore.Ydb.QuickStart", "EntityFrameworkCore.Ydb.QuickStart\EntityFrameworkCore.Ydb.QuickStart.csproj", "{0CE9DF93-1411-4E73-BA88-A66018FAB948}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkCore.Ydb.Yandex.Cloud", "EntityFrameworkCore.Ydb.Yandex.Cloud\EntityFrameworkCore.Ydb.Yandex.Cloud.csproj", "{8F7266C7-75BB-4753-9FB2-BDF4678AF73B}" EndProject From 6edbf4fdcb4f4ef82cafe72a6eda13870dfe1552 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Mon, 26 May 2025 19:39:44 +0300 Subject: [PATCH 2/5] fix example --- .../Program.cs | 28 +++++++++++++++++-- .../Internal/YdbOptionsExtension.cs | 15 +++++++++- .../YdbDbContextOptionsBuilder.cs | 3 ++ .../Internal/YdbRelationalConnection.cs | 8 +++++- 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs b/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs index 66e44905..d360f54e 100644 --- a/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs +++ b/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs @@ -1,5 +1,6 @@ using EntityFrameworkCore.Ydb.Extensions; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; await using var db = new BloggingContext(); @@ -26,14 +27,35 @@ db.Remove(blog); await db.SaveChangesAsync(); +internal class BloggingContextFactory : IDesignTimeDbContextFactory +{ + public BloggingContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + + return new BloggingContext( + optionsBuilder.UseYdb("Host=localhost;Port=2136;Database=/local", + builder => builder.DisableRetryOnFailure() + ).Options + ); + } +} + internal class BloggingContext : DbContext { + internal BloggingContext() + { + } + + internal BloggingContext(DbContextOptions options) : base(options) + { + } + public DbSet Blogs { get; set; } public DbSet Posts { get; set; } - protected override void OnConfiguring(DbContextOptionsBuilder options) - => options.UseYdb("Host=localhost;Port=2136;Database=/local") - .LogTo(Console.WriteLine); + protected override void OnConfiguring(DbContextOptionsBuilder options) => + options.UseYdb("Host=localhost;Port=2136;Database=/local"); } internal class Blog diff --git a/src/EFCore.Ydb/src/Infrastructure/Internal/YdbOptionsExtension.cs b/src/EFCore.Ydb/src/Infrastructure/Internal/YdbOptionsExtension.cs index 12047dae..ec0a0546 100644 --- a/src/EFCore.Ydb/src/Infrastructure/Internal/YdbOptionsExtension.cs +++ b/src/EFCore.Ydb/src/Infrastructure/Internal/YdbOptionsExtension.cs @@ -13,6 +13,8 @@ public class YdbOptionsExtension : RelationalOptionsExtension public X509Certificate2Collection? ServerCertificates { get; private set; } + public bool DisableRetryExecutionStrategy { get; private set; } + private DbContextOptionsExtensionInfo? _info; public YdbOptionsExtension() @@ -23,11 +25,13 @@ private YdbOptionsExtension(YdbOptionsExtension copyFrom) : base(copyFrom) { CredentialsProvider = copyFrom.CredentialsProvider; ServerCertificates = copyFrom.ServerCertificates; + DisableRetryExecutionStrategy = copyFrom.DisableRetryExecutionStrategy; } protected override RelationalOptionsExtension Clone() => new YdbOptionsExtension(this); - public override void ApplyServices(IServiceCollection services) => services.AddEntityFrameworkYdb(); + public override void ApplyServices(IServiceCollection services) => + services.AddEntityFrameworkYdb(!DisableRetryExecutionStrategy); public override DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this); @@ -49,6 +53,15 @@ public YdbOptionsExtension WithServerCertificates(X509Certificate2Collection? se return clone; } + public YdbOptionsExtension DisableRetryOnFailure() + { + var clone = (YdbOptionsExtension)Clone(); + + clone.DisableRetryExecutionStrategy = true; + + return clone; + } + private sealed class ExtensionInfo(YdbOptionsExtension extension) : RelationalExtensionInfo(extension) { public override bool IsDatabaseProvider => true; diff --git a/src/EFCore.Ydb/src/Infrastructure/YdbDbContextOptionsBuilder.cs b/src/EFCore.Ydb/src/Infrastructure/YdbDbContextOptionsBuilder.cs index fa64290c..c83194d0 100644 --- a/src/EFCore.Ydb/src/Infrastructure/YdbDbContextOptionsBuilder.cs +++ b/src/EFCore.Ydb/src/Infrastructure/YdbDbContextOptionsBuilder.cs @@ -14,4 +14,7 @@ public YdbDbContextOptionsBuilder WithCredentialsProvider(ICredentialsProvider? public YdbDbContextOptionsBuilder WithServerCertificates(X509Certificate2Collection? serverCertificates) => WithOption(optionsBuilder => optionsBuilder.WithServerCertificates(serverCertificates)); + + public YdbDbContextOptionsBuilder DisableRetryOnFailure() => + WithOption(optionsBuilder => optionsBuilder.DisableRetryOnFailure()); } diff --git a/src/EFCore.Ydb/src/Storage/Internal/YdbRelationalConnection.cs b/src/EFCore.Ydb/src/Storage/Internal/YdbRelationalConnection.cs index 3d5a023b..9e869b2b 100644 --- a/src/EFCore.Ydb/src/Storage/Internal/YdbRelationalConnection.cs +++ b/src/EFCore.Ydb/src/Storage/Internal/YdbRelationalConnection.cs @@ -13,6 +13,7 @@ public class YdbRelationalConnection : RelationalConnection, IYdbRelationalConne { private readonly ICredentialsProvider? _credentialsProvider; private readonly X509Certificate2Collection? _serverCertificates; + private readonly bool _disableRetryExecuteStrategy; public YdbRelationalConnection(RelationalConnectionDependencies dependencies) : base(dependencies) { @@ -21,6 +22,7 @@ public YdbRelationalConnection(RelationalConnectionDependencies dependencies) : _credentialsProvider = ydbOptionsExtension.CredentialsProvider; _serverCertificates = ydbOptionsExtension.ServerCertificates; + _disableRetryExecuteStrategy = ydbOptionsExtension.DisableRetryExecutionStrategy; } protected override DbConnection CreateDbConnection() @@ -28,7 +30,7 @@ protected override DbConnection CreateDbConnection() var ydbConnectionStringBuilder = new YdbConnectionStringBuilder(GetValidatedConnectionString()) { CredentialsProvider = _credentialsProvider, - ServerCertificates = _serverCertificates + ServerCertificates = _serverCertificates, }; return new YdbConnection(ydbConnectionStringBuilder); @@ -40,6 +42,10 @@ public IYdbRelationalConnection Clone() { builder.WithCredentialsProvider(_credentialsProvider); builder.WithServerCertificates(_serverCertificates); + if (_disableRetryExecuteStrategy) + { + builder.DisableRetryOnFailure(); + } }).Options; return new YdbRelationalConnection(Dependencies with { ContextOptions = options }); From cdac0b15a286f406ab2487b6a6794503936391f1 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Mon, 26 May 2025 19:47:35 +0300 Subject: [PATCH 3/5] fix linter --- .../src/Infrastructure/Internal/YdbOptionsExtension.cs | 4 ++-- .../src/Infrastructure/YdbDbContextOptionsBuilder.cs | 2 +- .../src/Storage/Internal/YdbRelationalConnection.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EFCore.Ydb/src/Infrastructure/Internal/YdbOptionsExtension.cs b/src/EFCore.Ydb/src/Infrastructure/Internal/YdbOptionsExtension.cs index ec0a0546..34c258db 100644 --- a/src/EFCore.Ydb/src/Infrastructure/Internal/YdbOptionsExtension.cs +++ b/src/EFCore.Ydb/src/Infrastructure/Internal/YdbOptionsExtension.cs @@ -30,7 +30,7 @@ private YdbOptionsExtension(YdbOptionsExtension copyFrom) : base(copyFrom) protected override RelationalOptionsExtension Clone() => new YdbOptionsExtension(this); - public override void ApplyServices(IServiceCollection services) => + public override void ApplyServices(IServiceCollection services) => services.AddEntityFrameworkYdb(!DisableRetryExecutionStrategy); public override DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this); @@ -56,7 +56,7 @@ public YdbOptionsExtension WithServerCertificates(X509Certificate2Collection? se public YdbOptionsExtension DisableRetryOnFailure() { var clone = (YdbOptionsExtension)Clone(); - + clone.DisableRetryExecutionStrategy = true; return clone; diff --git a/src/EFCore.Ydb/src/Infrastructure/YdbDbContextOptionsBuilder.cs b/src/EFCore.Ydb/src/Infrastructure/YdbDbContextOptionsBuilder.cs index c83194d0..8a35327c 100644 --- a/src/EFCore.Ydb/src/Infrastructure/YdbDbContextOptionsBuilder.cs +++ b/src/EFCore.Ydb/src/Infrastructure/YdbDbContextOptionsBuilder.cs @@ -14,7 +14,7 @@ public YdbDbContextOptionsBuilder WithCredentialsProvider(ICredentialsProvider? public YdbDbContextOptionsBuilder WithServerCertificates(X509Certificate2Collection? serverCertificates) => WithOption(optionsBuilder => optionsBuilder.WithServerCertificates(serverCertificates)); - + public YdbDbContextOptionsBuilder DisableRetryOnFailure() => WithOption(optionsBuilder => optionsBuilder.DisableRetryOnFailure()); } diff --git a/src/EFCore.Ydb/src/Storage/Internal/YdbRelationalConnection.cs b/src/EFCore.Ydb/src/Storage/Internal/YdbRelationalConnection.cs index 9e869b2b..23990cfe 100644 --- a/src/EFCore.Ydb/src/Storage/Internal/YdbRelationalConnection.cs +++ b/src/EFCore.Ydb/src/Storage/Internal/YdbRelationalConnection.cs @@ -30,7 +30,7 @@ protected override DbConnection CreateDbConnection() var ydbConnectionStringBuilder = new YdbConnectionStringBuilder(GetValidatedConnectionString()) { CredentialsProvider = _credentialsProvider, - ServerCertificates = _serverCertificates, + ServerCertificates = _serverCertificates }; return new YdbConnection(ydbConnectionStringBuilder); From 3131a03b21548e3f0b3e22f384dd46d5653cdd54 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Mon, 26 May 2025 20:01:38 +0300 Subject: [PATCH 4/5] added important comment --- .../src/EntityFrameworkCore.Ydb.QuickStart/Program.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs b/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs index d360f54e..d036db8f 100644 --- a/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs +++ b/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs @@ -33,6 +33,14 @@ public BloggingContext CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder(); + // IMPORTANT! + // Disables retries for the migrations context. + // Required because migration operations may use suppressed or explicit transactions, + // and enabling retries in this case leads to runtime errors with this provider. + // + // "User transaction is not supported with a TransactionSuppressed migrations or a retrying execution strategy." + // + // Bottom line: ALWAYS disable retries for design-time/migration contexts to avoid migration failures and errors. return new BloggingContext( optionsBuilder.UseYdb("Host=localhost;Port=2136;Database=/local", builder => builder.DisableRetryOnFailure() From 652ba705ec7d66f9e9b96fa66506883ea6d1a5e8 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Mon, 26 May 2025 20:02:12 +0300 Subject: [PATCH 5/5] fix --- examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs b/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs index d036db8f..32018e4c 100644 --- a/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs +++ b/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs @@ -38,7 +38,7 @@ public BloggingContext CreateDbContext(string[] args) // Required because migration operations may use suppressed or explicit transactions, // and enabling retries in this case leads to runtime errors with this provider. // - // "User transaction is not supported with a TransactionSuppressed migrations or a retrying execution strategy." + // "System.NotSupportedException: User transaction is not supported with a TransactionSuppressed migrations or a retrying execution strategy." // // Bottom line: ALWAYS disable retries for design-time/migration contexts to avoid migration failures and errors. return new BloggingContext(