Skip to content

Commit bd87cf2

Browse files
dev: SLO Entity Framework (#432)
1 parent 942ed4c commit bd87cf2

File tree

17 files changed

+352
-305
lines changed

17 files changed

+352
-305
lines changed

.github/workflows/slo-topic.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ jobs:
2626
- name: Prepare SLO Database
2727
run: |
2828
cd slo/src/TopicService
29-
dotnet run create grpc://localhost:2135 /Root/testdb
29+
dotnet run create "Host=localhost;Port=2135;Database=/Root/testdb"
3030
- name: Run SLO Tests
3131
continue-on-error: true
3232
run: |
3333
cd slo/src/TopicService
34-
dotnet run run grpc://localhost:2135 /Root/testdb \
34+
dotnet run run "Host=localhost;Port=2135;Database=/Root/testdb" \
3535
--write-rps 50 \
3636
--time 600

.github/workflows/slo.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ jobs:
3131
matrix:
3232
workload:
3333
- AdoNet
34+
- EF
3435

3536
concurrency:
3637
group: slo-${{ github.ref }}-${{ matrix.workload }}
@@ -56,12 +57,12 @@ jobs:
5657
- name: Prepare SLO Database
5758
run: |
5859
cd slo/src/${{ matrix.workload }}
59-
dotnet run create grpc://localhost:2135 /Root/testdb
60+
dotnet run create "Host=localhost;Port=2135;Database=/Root/testdb"
6061
6162
- name: Run SLO Tests
6263
run: |
6364
cd slo/src/${{ matrix.workload }}
64-
dotnet run run grpc://localhost:2135 /Root/testdb \
65+
dotnet run run "Host=localhost;Port=2135;Database=/Root/testdb" \
6566
--prom-pgw http://localhost:9091 \
6667
--report-period 250 \
6768
--time ${{inputs.slo_workload_duration_seconds || 600 }} \

slo/playground/configs/compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ x-runtime: &runtime
55
network_mode: host
66

77
x-ydb-node: &ydb-node
8-
image: cr.yandex/crptqonuodf51kdj7a7d/ydb:24.3.11.13
8+
image: cr.yandex/crptqonuodf51kdj7a7d/ydb:24.4.4.2
99
restart: always
1010
<<: *runtime
1111
volumes:

slo/src/AdoNet/SloTableContext.cs

Lines changed: 88 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
using System.Data;
12
using Internal;
23
using Microsoft.Extensions.Logging;
34
using Polly;
45
using Prometheus;
56
using Ydb.Sdk;
67
using Ydb.Sdk.Ado;
7-
using Ydb.Sdk.Value;
88

99
namespace AdoNet;
1010

@@ -22,17 +22,36 @@ public class SloTableContext : SloTableContext<YdbDataSource>
2222

2323
protected override string Job => "AdoNet";
2424

25-
protected override async Task Create(YdbDataSource client, string createTableSql, int operationTimeout)
25+
protected override YdbDataSource CreateClient(Config config) => new(
26+
new YdbConnectionStringBuilder(config.ConnectionString) { LoggerFactory = ISloContext.Factory }
27+
);
28+
29+
protected override async Task Create(YdbDataSource client, int operationTimeout)
2630
{
2731
await using var ydbConnection = await client.OpenConnectionAsync();
28-
2932
await new YdbCommand(ydbConnection)
30-
{ CommandText = createTableSql, CommandTimeout = operationTimeout }
31-
.ExecuteNonQueryAsync();
33+
{
34+
CommandText = $"""
35+
CREATE TABLE `{SloTable.Name}` (
36+
Guid UUID,
37+
Id Int32,
38+
PayloadStr Text,
39+
PayloadDouble Double,
40+
PayloadTimestamp Timestamp,
41+
PRIMARY KEY (Guid, Id)
42+
);
43+
{SloTable.Options}
44+
""",
45+
CommandTimeout = operationTimeout
46+
}.ExecuteNonQueryAsync();
3247
}
3348

34-
protected override async Task<(int, StatusCode)> Upsert(YdbDataSource dataSource, string upsertSql,
35-
Dictionary<string, YdbValue> parameters, int writeTimeout, Counter? errorsTotal = null)
49+
protected override async Task<(int, StatusCode)> Save(
50+
YdbDataSource client,
51+
SloTable sloTable,
52+
int writeTimeout,
53+
Counter? errorsTotal = null
54+
)
3655
{
3756
var context = new Context();
3857
if (errorsTotal != null)
@@ -42,15 +61,49 @@ protected override async Task Create(YdbDataSource client, string createTableSql
4261

4362
var policyResult = await _policy.ExecuteAndCaptureAsync(async _ =>
4463
{
45-
await using var ydbConnection = await dataSource.OpenConnectionAsync();
64+
await using var ydbConnection = await client.OpenConnectionAsync();
4665

4766
var ydbCommand = new YdbCommand(ydbConnection)
48-
{ CommandText = upsertSql, CommandTimeout = writeTimeout };
49-
50-
foreach (var (key, value) in parameters)
5167
{
52-
ydbCommand.Parameters.AddWithValue(key, value);
53-
}
68+
CommandText = $"""
69+
INSERT INTO `{SloTable.Name}` (Guid, Id, PayloadStr, PayloadDouble, PayloadTimestamp)
70+
VALUES (@Guid, @Id, @PayloadStr, @PayloadDouble, @PayloadTimestamp)
71+
""",
72+
CommandTimeout = writeTimeout,
73+
Parameters =
74+
{
75+
new YdbParameter
76+
{
77+
DbType = DbType.Guid,
78+
ParameterName = "Guid",
79+
Value = sloTable.Guid
80+
},
81+
new YdbParameter
82+
{
83+
DbType = DbType.Int32,
84+
ParameterName = "Id",
85+
Value = sloTable.Id
86+
},
87+
new YdbParameter
88+
{
89+
DbType = DbType.String,
90+
ParameterName = "PayloadStr",
91+
Value = sloTable.PayloadStr
92+
},
93+
new YdbParameter
94+
{
95+
DbType = DbType.Double,
96+
ParameterName = "PayloadDouble",
97+
Value = sloTable.PayloadDouble
98+
},
99+
new YdbParameter
100+
{
101+
DbType = DbType.DateTime2,
102+
ParameterName = "PayloadTimestamp",
103+
Value = sloTable.PayloadTimestamp
104+
}
105+
}
106+
};
54107

55108
await ydbCommand.ExecuteNonQueryAsync();
56109
}, context);
@@ -60,8 +113,12 @@ protected override async Task Create(YdbDataSource client, string createTableSql
60113
((YdbException)policyResult.FinalException)?.Code ?? StatusCode.Success);
61114
}
62115

63-
protected override async Task<(int, StatusCode, object?)> Select(YdbDataSource dataSource, string selectSql,
64-
Dictionary<string, YdbValue> parameters, int readTimeout, Counter? errorsTotal = null)
116+
protected override async Task<(int, StatusCode, object?)> Select(
117+
YdbDataSource client,
118+
(Guid Guid, int Id) select,
119+
int readTimeout,
120+
Counter? errorsTotal = null
121+
)
65122
{
66123
var context = new Context();
67124
if (errorsTotal != null)
@@ -73,39 +130,33 @@ protected override async Task Create(YdbDataSource client, string createTableSql
73130
var policyResult = await _policy.ExecuteAndCaptureAsync(async _ =>
74131
{
75132
attempts++;
76-
await using var ydbConnection = await dataSource.OpenConnectionAsync();
133+
await using var ydbConnection = await client.OpenConnectionAsync();
77134

78135
var ydbCommand = new YdbCommand(ydbConnection)
79-
{ CommandText = selectSql, CommandTimeout = readTimeout };
80-
81-
foreach (var (key, value) in parameters)
82136
{
83-
ydbCommand.Parameters.AddWithValue(key, value);
84-
}
137+
CommandText = $"""
138+
SELECT Guid, Id, PayloadStr, PayloadDouble, PayloadTimestamp
139+
FROM `{SloTable.Name}` WHERE Guid = @Guid AND Id = @Id;
140+
""",
141+
CommandTimeout = readTimeout,
142+
Parameters =
143+
{
144+
new YdbParameter { ParameterName = "Guid", DbType = DbType.Guid, Value = select.Guid },
145+
new YdbParameter { ParameterName = "Id", DbType = DbType.Int32, Value = select.Id }
146+
}
147+
};
85148

86149
return await ydbCommand.ExecuteScalarAsync();
87150
}, context);
88151

89152
return (attempts, ((YdbException)policyResult.FinalException)?.Code ?? StatusCode.Success, policyResult.Result);
90153
}
91154

92-
protected override Task<YdbDataSource> CreateClient(Config config)
155+
protected override async Task<int> SelectCount(YdbDataSource client)
93156
{
94-
var splitEndpoint = config.Endpoint.Split("://");
95-
var useTls = splitEndpoint[0] switch
96-
{
97-
"grpc" => false,
98-
"grpcs" => true,
99-
_ => throw new ArgumentException("Don't support schema: " + splitEndpoint[0])
100-
};
101-
102-
var host = splitEndpoint[1].Split(":")[0];
103-
var port = splitEndpoint[1].Split(":")[1];
157+
await using var ydbConnection = await client.OpenConnectionAsync();
104158

105-
return Task.FromResult(new YdbDataSource(new YdbConnectionStringBuilder
106-
{
107-
UseTls = useTls, Host = host, Port = int.Parse(port), Database = config.Db,
108-
LoggerFactory = ISloContext.Factory
109-
}));
159+
return (int)(await new YdbCommand(ydbConnection) { CommandText = $"SELECT MAX(Id) FROM {SloTable.Name}" }
160+
.ExecuteScalarAsync())!;
110161
}
111162
}

slo/src/EF/EF.csproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<ProjectReference Include="..\Internal\Internal.csproj" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\..\src\EFCore.Ydb\src\EntityFrameworkCore.Ydb.csproj" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5">
20+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
21+
<PrivateAssets>all</PrivateAssets>
22+
</PackageReference>
23+
</ItemGroup>
24+
</Project>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// See https://aka.ms/new-console-template for more information
22

3+
using EF;
34
using Internal;
4-
using TableService;
55

6-
return await Cli.Run(new SloTableContext(), args);
6+
await Cli.Run(new SloTableContext(), args);

slo/src/EF/SloTableContext.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using EntityFrameworkCore.Ydb.Extensions;
2+
using Internal;
3+
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.EntityFrameworkCore.Infrastructure;
5+
using Prometheus;
6+
using Ydb.Sdk;
7+
8+
namespace EF;
9+
10+
public class SloTableContext : SloTableContext<PooledDbContextFactory<TableDbContext>>
11+
{
12+
protected override string Job => "EF";
13+
14+
protected override PooledDbContextFactory<TableDbContext> CreateClient(Config config) =>
15+
new(new DbContextOptionsBuilder<TableDbContext>().UseYdb(config.ConnectionString).Options);
16+
17+
protected override async Task Create(
18+
PooledDbContextFactory<TableDbContext> client,
19+
int operationTimeout
20+
)
21+
{
22+
await using var dbContext = await client.CreateDbContextAsync();
23+
await dbContext.Database.EnsureCreatedAsync();
24+
await dbContext.Database.ExecuteSqlRawAsync(SloTable.Options);
25+
}
26+
27+
protected override async Task<(int, StatusCode)> Save(
28+
PooledDbContextFactory<TableDbContext> client,
29+
SloTable sloTable,
30+
int writeTimeout,
31+
Counter? errorsTotal = null)
32+
{
33+
await using var dbContext = await client.CreateDbContextAsync();
34+
dbContext.SloEntities.Add(sloTable);
35+
await dbContext.SaveChangesAsync();
36+
37+
return (1, StatusCode.Success);
38+
}
39+
40+
protected override async Task<(int, StatusCode, object?)> Select(
41+
PooledDbContextFactory<TableDbContext> client,
42+
(Guid Guid, int Id) select,
43+
int readTimeout,
44+
Counter? errorsTotal = null
45+
)
46+
{
47+
await using var dbContext = await client.CreateDbContextAsync();
48+
await dbContext.SloEntities.FindAsync(select.Guid, select.Id);
49+
50+
return (0, StatusCode.Success, null);
51+
}
52+
53+
protected override async Task<int> SelectCount(PooledDbContextFactory<TableDbContext> client)
54+
{
55+
await using var dbContext = await client.CreateDbContextAsync();
56+
57+
return await dbContext.SloEntities.CountAsync();
58+
}
59+
}

slo/src/EF/TableDbContext.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Internal;
2+
using Microsoft.EntityFrameworkCore;
3+
4+
namespace EF;
5+
6+
public class TableDbContext(DbContextOptions<TableDbContext> options) : DbContext(options)
7+
{
8+
public DbSet<SloTable> SloEntities { get; set; }
9+
10+
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
11+
modelBuilder.Entity<SloTable>()
12+
.ToTable(SloTable.Name)
13+
.HasKey(e => new { e.Guid, e.Id });
14+
}

0 commit comments

Comments
 (0)