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/EF/Program.cs b/examples/src/EF/Program.cs
deleted file mode 100644
index eebbcd21..00000000
--- a/examples/src/EF/Program.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using EntityFrameworkCore.Ydb.Extensions;
-using Microsoft.EntityFrameworkCore;
-
-await using var db = new BloggingContext();
-
-await db.Database.EnsureDeletedAsync();
-await db.Database.EnsureCreatedAsync();
-
-Console.WriteLine("Inserting a new blog");
-db.Add(new Blog { Url = "http://blogs.msdn.com/adonet" });
-
-await db.SaveChangesAsync();
-
-Console.WriteLine("Querying for a blog");
-var blog = await db.Blogs
- .OrderBy(b => b.BlogId)
- .FirstAsync();
-
-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!" });
-await db.SaveChangesAsync();
-
-Console.WriteLine("Delete the blog");
-db.Remove(blog);
-await db.SaveChangesAsync();
-
-internal class BloggingContext : DbContext
-{
- 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);
-}
-
-internal class Blog
-{
- public int BlogId { get; init; }
-
- public string Url { get; set; } = string.Empty;
-
- // ReSharper disable once CollectionNeverQueried.Global
- public List Posts { get; init; } = [];
-}
-
-internal class Post
-{
- public int PostId { get; init; }
-
- public string Title { get; init; } = string.Empty;
-
- public string Content { get; init; } = string.Empty;
-
- public Blog Blog { get; init; } = null!;
-}
\ No newline at end of file
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/EntityFrameworkCore.Ydb.QuickStart/Program.cs b/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs
new file mode 100644
index 00000000..32018e4c
--- /dev/null
+++ b/examples/src/EntityFrameworkCore.Ydb.QuickStart/Program.cs
@@ -0,0 +1,88 @@
+using EntityFrameworkCore.Ydb.Extensions;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+
+await using var db = new BloggingContext();
+
+// 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!" });
+await db.SaveChangesAsync();
+
+// Delete
+Console.WriteLine("Delete the blog");
+db.Remove(blog);
+await db.SaveChangesAsync();
+
+internal class BloggingContextFactory : IDesignTimeDbContextFactory
+{
+ 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.
+ //
+ // "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(
+ 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");
+}
+
+internal class Blog
+{
+ public int BlogId { get; init; }
+
+ public string Url { get; set; } = string.Empty;
+
+ // ReSharper disable once CollectionNeverQueried.Global
+ public List Posts { get; init; } = [];
+}
+
+internal class Post
+{
+ public int PostId { get; init; }
+
+ public string Title { get; init; } = string.Empty;
+
+ public string Content { get; init; } = string.Empty;
+
+ public Blog Blog { get; init; } = null!;
+}
\ No newline at end of file
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
diff --git a/src/EFCore.Ydb/src/Infrastructure/Internal/YdbOptionsExtension.cs b/src/EFCore.Ydb/src/Infrastructure/Internal/YdbOptionsExtension.cs
index 12047dae..34c258db 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..8a35327c 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..23990cfe 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()
@@ -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 });