Skip to content

Commit 1209f9b

Browse files
feat: Entity Framework Core YDB Quick Start (#429)
1 parent 30af596 commit 1209f9b

File tree

10 files changed

+155
-75
lines changed

10 files changed

+155
-75
lines changed

.github/workflows/tests.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,11 @@ jobs:
195195
run: |
196196
cd ./examples/src/Topic
197197
dotnet run
198-
- name: Run EF example
198+
- name: Run Entity Framework Core example
199199
run: |
200-
cd ./examples/src/EF
200+
cd ./examples/src/EntityFrameworkCore.Ydb.QuickStart
201+
dotnet tool install --global dotnet-ef
202+
dotnet add package Microsoft.EntityFrameworkCore.Design
203+
dotnet ef migrations add InitialCreate
204+
dotnet ef database update
201205
dotnet run

examples/src/EF/EF.csproj

Lines changed: 0 additions & 14 deletions
This file was deleted.

examples/src/EF/Program.cs

Lines changed: 0 additions & 57 deletions
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<RootNamespace>EntityFrameworkCore.Ydb</RootNamespace>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\..\..\src\EFCore.Ydb\src\EntityFrameworkCore.Ydb.csproj" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5">
17+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
18+
<PrivateAssets>all</PrivateAssets>
19+
</PackageReference>
20+
</ItemGroup>
21+
</Project>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using EntityFrameworkCore.Ydb.Extensions;
2+
using Microsoft.EntityFrameworkCore;
3+
using Microsoft.EntityFrameworkCore.Design;
4+
5+
await using var db = new BloggingContext();
6+
7+
// Create
8+
Console.WriteLine("Inserting a new blog");
9+
db.Add(new Blog { Url = "http://blogs.msdn.com/adonet" });
10+
await db.SaveChangesAsync();
11+
12+
// Read
13+
Console.WriteLine("Querying for a blog");
14+
var blog = await db.Blogs
15+
.OrderBy(b => b.BlogId)
16+
.FirstAsync();
17+
18+
// Update
19+
Console.WriteLine("Updating the blog and adding a post");
20+
blog.Url = "https://devblogs.microsoft.com/dotnet";
21+
blog.Posts.Add(
22+
new Post { Title = "Hello World", Content = "I wrote an app using EF Core!" });
23+
await db.SaveChangesAsync();
24+
25+
// Delete
26+
Console.WriteLine("Delete the blog");
27+
db.Remove(blog);
28+
await db.SaveChangesAsync();
29+
30+
internal class BloggingContextFactory : IDesignTimeDbContextFactory<BloggingContext>
31+
{
32+
public BloggingContext CreateDbContext(string[] args)
33+
{
34+
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
35+
36+
// IMPORTANT!
37+
// Disables retries for the migrations context.
38+
// Required because migration operations may use suppressed or explicit transactions,
39+
// and enabling retries in this case leads to runtime errors with this provider.
40+
//
41+
// "System.NotSupportedException: User transaction is not supported with a TransactionSuppressed migrations or a retrying execution strategy."
42+
//
43+
// Bottom line: ALWAYS disable retries for design-time/migration contexts to avoid migration failures and errors.
44+
return new BloggingContext(
45+
optionsBuilder.UseYdb("Host=localhost;Port=2136;Database=/local",
46+
builder => builder.DisableRetryOnFailure()
47+
).Options
48+
);
49+
}
50+
}
51+
52+
internal class BloggingContext : DbContext
53+
{
54+
internal BloggingContext()
55+
{
56+
}
57+
58+
internal BloggingContext(DbContextOptions<BloggingContext> options) : base(options)
59+
{
60+
}
61+
62+
public DbSet<Blog> Blogs { get; set; }
63+
public DbSet<Post> Posts { get; set; }
64+
65+
protected override void OnConfiguring(DbContextOptionsBuilder options) =>
66+
options.UseYdb("Host=localhost;Port=2136;Database=/local");
67+
}
68+
69+
internal class Blog
70+
{
71+
public int BlogId { get; init; }
72+
73+
public string Url { get; set; } = string.Empty;
74+
75+
// ReSharper disable once CollectionNeverQueried.Global
76+
public List<Post> Posts { get; init; } = [];
77+
}
78+
79+
internal class Post
80+
{
81+
public int PostId { get; init; }
82+
83+
public string Title { get; init; } = string.Empty;
84+
85+
public string Content { get; init; } = string.Empty;
86+
87+
public Blog Blog { get; init; } = null!;
88+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Entity Framework Core YDB Quick Start
2+
3+
A sample application from the [official documentation](https://learn.microsoft.com/en-us/ef/core/get-started/overview/first-app?tabs=netcore-cli)
4+
shows how to get started with EF, define a model, populate it with data and then query the database.
5+
6+
## Running QuickStart
7+
8+
1. Setup [YDB local](https://ydb.tech/docs/en/reference/docker/start).
9+
2. Create the database:
10+
```bash
11+
dotnet tool install --global dotnet-ef
12+
dotnet add package Microsoft.EntityFrameworkCore.Design
13+
dotnet ef migrations add InitialCreate
14+
dotnet ef database update
15+
```
16+
3. Run the app: `dotnet run`

examples/src/YdbExamples.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YC", "YC\YC.csproj", "{753E
1717
EndProject
1818
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Topic", "Topic\Topic.csproj", "{0FB9A1C2-4B0C-4AE4-9FA2-E0ED37802A6E}"
1919
EndProject
20-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EF", "EF\EF.csproj", "{0CE9DF93-1411-4E73-BA88-A66018FAB948}"
20+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkCore.Ydb.QuickStart", "EntityFrameworkCore.Ydb.QuickStart\EntityFrameworkCore.Ydb.QuickStart.csproj", "{0CE9DF93-1411-4E73-BA88-A66018FAB948}"
2121
EndProject
2222
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkCore.Ydb.Yandex.Cloud", "EntityFrameworkCore.Ydb.Yandex.Cloud\EntityFrameworkCore.Ydb.Yandex.Cloud.csproj", "{8F7266C7-75BB-4753-9FB2-BDF4678AF73B}"
2323
EndProject

src/EFCore.Ydb/src/Infrastructure/Internal/YdbOptionsExtension.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public class YdbOptionsExtension : RelationalOptionsExtension
1313

1414
public X509Certificate2Collection? ServerCertificates { get; private set; }
1515

16+
public bool DisableRetryExecutionStrategy { get; private set; }
17+
1618
private DbContextOptionsExtensionInfo? _info;
1719

1820
public YdbOptionsExtension()
@@ -23,11 +25,13 @@ private YdbOptionsExtension(YdbOptionsExtension copyFrom) : base(copyFrom)
2325
{
2426
CredentialsProvider = copyFrom.CredentialsProvider;
2527
ServerCertificates = copyFrom.ServerCertificates;
28+
DisableRetryExecutionStrategy = copyFrom.DisableRetryExecutionStrategy;
2629
}
2730

2831
protected override RelationalOptionsExtension Clone() => new YdbOptionsExtension(this);
2932

30-
public override void ApplyServices(IServiceCollection services) => services.AddEntityFrameworkYdb();
33+
public override void ApplyServices(IServiceCollection services) =>
34+
services.AddEntityFrameworkYdb(!DisableRetryExecutionStrategy);
3135

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

@@ -49,6 +53,15 @@ public YdbOptionsExtension WithServerCertificates(X509Certificate2Collection? se
4953
return clone;
5054
}
5155

56+
public YdbOptionsExtension DisableRetryOnFailure()
57+
{
58+
var clone = (YdbOptionsExtension)Clone();
59+
60+
clone.DisableRetryExecutionStrategy = true;
61+
62+
return clone;
63+
}
64+
5265
private sealed class ExtensionInfo(YdbOptionsExtension extension) : RelationalExtensionInfo(extension)
5366
{
5467
public override bool IsDatabaseProvider => true;

src/EFCore.Ydb/src/Infrastructure/YdbDbContextOptionsBuilder.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ public YdbDbContextOptionsBuilder WithCredentialsProvider(ICredentialsProvider?
1414

1515
public YdbDbContextOptionsBuilder WithServerCertificates(X509Certificate2Collection? serverCertificates) =>
1616
WithOption(optionsBuilder => optionsBuilder.WithServerCertificates(serverCertificates));
17+
18+
public YdbDbContextOptionsBuilder DisableRetryOnFailure() =>
19+
WithOption(optionsBuilder => optionsBuilder.DisableRetryOnFailure());
1720
}

src/EFCore.Ydb/src/Storage/Internal/YdbRelationalConnection.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class YdbRelationalConnection : RelationalConnection, IYdbRelationalConne
1313
{
1414
private readonly ICredentialsProvider? _credentialsProvider;
1515
private readonly X509Certificate2Collection? _serverCertificates;
16+
private readonly bool _disableRetryExecuteStrategy;
1617

1718
public YdbRelationalConnection(RelationalConnectionDependencies dependencies) : base(dependencies)
1819
{
@@ -21,6 +22,7 @@ public YdbRelationalConnection(RelationalConnectionDependencies dependencies) :
2122

2223
_credentialsProvider = ydbOptionsExtension.CredentialsProvider;
2324
_serverCertificates = ydbOptionsExtension.ServerCertificates;
25+
_disableRetryExecuteStrategy = ydbOptionsExtension.DisableRetryExecutionStrategy;
2426
}
2527

2628
protected override DbConnection CreateDbConnection()
@@ -40,6 +42,10 @@ public IYdbRelationalConnection Clone()
4042
{
4143
builder.WithCredentialsProvider(_credentialsProvider);
4244
builder.WithServerCertificates(_serverCertificates);
45+
if (_disableRetryExecuteStrategy)
46+
{
47+
builder.DisableRetryOnFailure();
48+
}
4349
}).Options;
4450

4551
return new YdbRelationalConnection(Dependencies with { ContextOptions = options });

0 commit comments

Comments
 (0)