Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/EntityFrameworkCore.Ydb.Yandex.Cloud/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ await Parser.Default.ParseArguments<CmdOptions>(args).WithParsedAsync(async cmd

var options = new DbContextOptionsBuilder<AppDbContext>()
.UseYdb(cmd.ConnectionString, builder => builder
.WithCredentialsProvider(saProvider)
.WithServerCertificates(YcCerts.GetYcServerCertificates())
.UseCredentialsProvider(saProvider)
.UseServerCertificates(YcCerts.GetYcServerCertificates())
)
.Options;

Expand Down
3 changes: 2 additions & 1 deletion slo/src/EF/SloTableContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public class SloTableContext : SloTableContext<PooledDbContextFactory<TableDbCon
protected override string Job => "EF";

protected override PooledDbContextFactory<TableDbContext> CreateClient(Config config) =>
new(new DbContextOptionsBuilder<TableDbContext>().UseYdb(config.ConnectionString).Options);
new(new DbContextOptionsBuilder<TableDbContext>().UseYdb(config.ConnectionString,
builder => builder.EnableRetryIdempotence()).Options);

protected override async Task Create(
PooledDbContextFactory<TableDbContext> client,
Expand Down
5 changes: 5 additions & 0 deletions src/EFCore.Ydb/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
- Added support for the YDB retry policy (ADO.NET) and new configuration methods in `YdbDbContextOptionsBuilder`:
- `EnableRetryIdempotence()`: enables retries for errors classified as idempotent. You must ensure the operation itself is idempotent.
- `UseRetryPolicy(YdbRetryPolicyConfig retryPolicyConfig)`: configures custom backoff parameters and the maximum number of retry attempts.
- `UseRetryPolicy(IRetryPolicy retryPolicy)`: advanced option giving full control over retry behavior. The provider does not manage the attempt counter; your implementation must handle it.

## v0.1.0

- Fixed bug: incompatible coalesce types ([#531](https://github.com/ydb-platform/ydb-dotnet-sdk/issues/531)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ public class YdbOptionsExtension : RelationalOptionsExtension

public X509Certificate2Collection? ServerCertificates { get; private set; }

public bool DisableRetryExecutionStrategy { get; private set; }

private DbContextOptionsExtensionInfo? _info;

public YdbOptionsExtension()
Expand All @@ -25,13 +23,11 @@ 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(!DisableRetryExecutionStrategy);
public override void ApplyServices(IServiceCollection services) => services.AddEntityFrameworkYdb();

public override DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this);

Expand All @@ -53,15 +49,6 @@ 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;
Expand Down
20 changes: 16 additions & 4 deletions src/EFCore.Ydb/src/Infrastructure/YdbDbContextOptionsBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
using System.Security.Cryptography.X509Certificates;
using EntityFrameworkCore.Ydb.Infrastructure.Internal;
using EntityFrameworkCore.Ydb.Storage.Internal;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Ydb.Sdk.Ado.RetryPolicy;
using Ydb.Sdk.Auth;

namespace EntityFrameworkCore.Ydb.Infrastructure;

public class YdbDbContextOptionsBuilder(DbContextOptionsBuilder optionsBuilder)
public sealed class YdbDbContextOptionsBuilder(DbContextOptionsBuilder optionsBuilder)
: RelationalDbContextOptionsBuilder<YdbDbContextOptionsBuilder, YdbOptionsExtension>(optionsBuilder)
{
public YdbDbContextOptionsBuilder WithCredentialsProvider(ICredentialsProvider? credentialsProvider) =>
public YdbDbContextOptionsBuilder UseCredentialsProvider(ICredentialsProvider? credentialsProvider) =>
WithOption(optionsBuilder => optionsBuilder.WithCredentialsProvider(credentialsProvider));

public YdbDbContextOptionsBuilder WithServerCertificates(X509Certificate2Collection? serverCertificates) =>
public YdbDbContextOptionsBuilder UseServerCertificates(X509Certificate2Collection? serverCertificates) =>
WithOption(optionsBuilder => optionsBuilder.WithServerCertificates(serverCertificates));

public YdbDbContextOptionsBuilder EnableRetryIdempotence()
=> UseRetryPolicy(new YdbRetryPolicyConfig { EnableRetryIdempotence = true });

public YdbDbContextOptionsBuilder UseRetryPolicy(YdbRetryPolicyConfig retryPolicyConfig)
=> ExecutionStrategy(d => new YdbExecutionStrategy(d, new YdbRetryPolicy(retryPolicyConfig)));

public YdbDbContextOptionsBuilder UseRetryPolicy(IRetryPolicy retryPolicy)
=> ExecutionStrategy(d => new YdbExecutionStrategy(d, retryPolicy));

public YdbDbContextOptionsBuilder DisableRetryOnFailure() =>
WithOption(optionsBuilder => optionsBuilder.DisableRetryOnFailure());
ExecutionStrategy(d => new NonRetryingExecutionStrategy(d));
}
27 changes: 23 additions & 4 deletions src/EFCore.Ydb/src/Storage/Internal/YdbExecutionStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
using System;
using Microsoft.EntityFrameworkCore.Storage;
using Ydb.Sdk.Ado;
using Ydb.Sdk.Ado.RetryPolicy;

namespace EntityFrameworkCore.Ydb.Storage.Internal;

public class YdbExecutionStrategy(ExecutionStrategyDependencies dependencies)
: ExecutionStrategy(dependencies, maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(10)) // TODO User settings
/// <summary>
/// Retry strategy for YDB.
///
/// IMPORTANT:
/// <br/>- The maximum number of attempts and backoff logic are encapsulated in <see cref="IRetryPolicy"/>.
/// The base ExecutionStrategy parameters (maxRetryCount, maxRetryDelay) are not used.
/// <br/>- This strategy must be invoked in the correct EF Core context/connection (YDB),
/// so that exception types and ShouldRetryOn semantics match the provider.
/// <br/>- This base <see cref="ExecutionStrategy"/> is a good place to emit metrics/logs (attempt number, delay, exception type, etc.).
/// </summary>
public class YdbExecutionStrategy(ExecutionStrategyDependencies dependencies, IRetryPolicy retryPolicy)
// We pass "placeholders" to the base class:
// - int.MaxValue and TimeSpan.Zero are not used in the real retry logic.
// - Actual limits/delays are driven by IRetryPolicy.
: ExecutionStrategy(dependencies, int.MaxValue /* unused! */, TimeSpan.Zero /* unused! */)
{
protected override bool ShouldRetryOn(Exception exception)
=> exception is YdbException { IsTransient: true };
public override bool RetriesOnFailure => true;

protected override bool ShouldRetryOn(Exception exception) => exception is YdbException;

protected override TimeSpan? GetNextDelay(Exception lastException) => lastException is YdbException ydbException
? retryPolicy.GetNextDelay(ydbException, ExceptionsEncountered.Count - 1)
: null;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using Microsoft.EntityFrameworkCore.Storage;
using Ydb.Sdk.Ado.RetryPolicy;

namespace EntityFrameworkCore.Ydb.Storage.Internal;

public class YdbExecutionStrategyFactory(ExecutionStrategyDependencies dependencies)
: RelationalExecutionStrategyFactory(dependencies)
{
protected override IExecutionStrategy CreateDefaultStrategy(ExecutionStrategyDependencies dependencies)
=> new YdbExecutionStrategy(dependencies);
protected override IExecutionStrategy CreateDefaultStrategy(ExecutionStrategyDependencies dependencies) =>
new YdbExecutionStrategy(dependencies, YdbRetryPolicy.Default);
}
10 changes: 2 additions & 8 deletions src/EFCore.Ydb/src/Storage/Internal/YdbRelationalConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public class YdbRelationalConnection : RelationalConnection, IYdbRelationalConne
{
private readonly ICredentialsProvider? _credentialsProvider;
private readonly X509Certificate2Collection? _serverCertificates;
private readonly bool _disableRetryExecuteStrategy;

public YdbRelationalConnection(RelationalConnectionDependencies dependencies) : base(dependencies)
{
Expand All @@ -22,7 +21,6 @@ public YdbRelationalConnection(RelationalConnectionDependencies dependencies) :

_credentialsProvider = ydbOptionsExtension.CredentialsProvider;
_serverCertificates = ydbOptionsExtension.ServerCertificates;
_disableRetryExecuteStrategy = ydbOptionsExtension.DisableRetryExecutionStrategy;
}

protected override DbConnection CreateDbConnection()
Expand All @@ -40,12 +38,8 @@ public IYdbRelationalConnection Clone()
{
var options = new DbContextOptionsBuilder().UseYdb(GetValidatedConnectionString(), builder =>
{
builder.WithCredentialsProvider(_credentialsProvider);
builder.WithServerCertificates(_serverCertificates);
if (_disableRetryExecuteStrategy)
{
builder.DisableRetryOnFailure();
}
builder.UseCredentialsProvider(_credentialsProvider);
builder.UseServerCertificates(_serverCertificates);
}).Options;

return new YdbRelationalConnection(Dependencies with { ContextOptions = options });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Ydb.Sdk.Ado.RetryPolicy;

namespace EntityFrameworkCore.Ydb.FunctionalTests;

Expand All @@ -28,7 +29,7 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build
{
new YdbDbContextOptionsBuilder(base.AddOptions(builder))
#pragma warning disable EF1001
.ExecutionStrategy(d => new YdbExecutionStrategy(d));
.ExecutionStrategy(d => new YdbExecutionStrategy(d, YdbRetryPolicy.Default));
#pragma warning restore EF1001
return builder;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using EntityFrameworkCore.Ydb.Extensions;
using EntityFrameworkCore.Ydb.Storage.Internal;

Check warning on line 2 in src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/TestUtilities/YdbTestStoreFactory.cs

View workflow job for this annotation

GitHub Actions / Inspection (./src/YdbSdk.sln)

"[RedundantUsingDirective] Using directive is not required by the code and can be safely removed" on /home/runner/work/ydb-dotnet-sdk/ydb-dotnet-sdk/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/TestUtilities/YdbTestStoreFactory.cs(2,1)
using Microsoft.EntityFrameworkCore.Storage;

Check warning on line 3 in src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/TestUtilities/YdbTestStoreFactory.cs

View workflow job for this annotation

GitHub Actions / Inspection (./src/YdbSdk.sln)

"[RedundantUsingDirective] Using directive is not required by the code and can be safely removed" on /home/runner/work/ydb-dotnet-sdk/ydb-dotnet-sdk/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/TestUtilities/YdbTestStoreFactory.cs(3,1)
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;

Expand Down
Loading