Skip to content

Commit 7d4fc28

Browse files
feat: data service resiliency (#946)
* temp: add sql retry * Data Service Accessor Execution Stratergy for retries * fix: bug where add many * make transasction commit consistent
1 parent f69f062 commit 7d4fc28

File tree

2 files changed

+88
-31
lines changed

2 files changed

+88
-31
lines changed

application/CohortManager/src/Functions/Shared/DataServices.Core/DataServiceAccessor.cs

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,69 +37,123 @@ public async Task<bool> InsertSingle(TEntity entity)
3737

3838
public async Task<bool> InsertMany(IEnumerable<TEntity> entities)
3939
{
40-
using var transaction = await _context.Database.BeginTransactionAsync();
40+
int rowsEffected = 0;
41+
var strategy = _context.Database.CreateExecutionStrategy();
4142

42-
await _context.AddRangeAsync(entities);
43-
var result = await _context.SaveChangesAsync();
43+
await strategy.ExecuteAsync(
44+
async () =>
45+
{
46+
using var transaction = await _context.Database.BeginTransactionAsync();
4447

45-
await transaction.CommitAsync();
46-
return result > 0;
48+
await _context.AddRangeAsync(entities);
49+
rowsEffected = await _context.SaveChangesAsync();
50+
51+
await transaction.CommitAsync();
52+
}
53+
);
54+
55+
return rowsEffected > 0;
4756
}
4857

4958
public async Task<bool> Remove(Expression<Func<TEntity, bool>> predicate)
5059
{
5160

52-
using var transaction = await _context.Database.BeginTransactionAsync();
53-
var result = await _context.Set<TEntity>().AsNoTracking().SingleOrDefaultAsync(predicate);
61+
int rowsEffected = 0;
62+
var strategy = _context.Database.CreateExecutionStrategy();
63+
64+
await strategy.ExecuteAsync(
65+
async () => {
66+
using var transaction = await _context.Database.BeginTransactionAsync();
67+
var result = await _context.Set<TEntity>().AsNoTracking().SingleOrDefaultAsync(predicate);
68+
69+
if (result == null)
70+
{
71+
return;
72+
}
73+
_context.Set<TEntity>().Remove(result);
74+
rowsEffected = await _context.SaveChangesAsync();
75+
if(rowsEffected > 1)
76+
{
77+
await transaction.RollbackAsync();
78+
return;
79+
80+
}
81+
82+
await transaction.CommitAsync();
83+
}
84+
);
5485

55-
if (result == null)
56-
{
57-
return false;
58-
}
59-
_context.Set<TEntity>().Remove(result);
60-
var rowsEffected = await _context.SaveChangesAsync();
6186
if(rowsEffected > 1)
6287
{
63-
await _context.Database.RollbackTransactionAsync();
64-
6588
_logger.LogError("There was an error while trying to deleted despite a record being found");
6689
throw new MultipleRecordsFoundException("Multiple Records were updated by PUT request, Changes have been Rolled-back");
6790
}
91+
else if(rowsEffected == 0)
92+
{
93+
return false;
94+
}
6895

69-
await _context.Database.CommitTransactionAsync();
7096
return true;
7197

7298
}
7399

74100
public async Task<TEntity> Update(TEntity entity, Expression<Func<TEntity, bool>> predicate)
75101
{
102+
int rowsEffected = 0;
103+
var strategy = _context.Database.CreateExecutionStrategy();
104+
105+
106+
107+
TEntity? dbEntity = await strategy.ExecuteAsync(
108+
async () => {
109+
using var transaction = await _context.Database.BeginTransactionAsync();
110+
111+
var existingEntity = await _context.Set<TEntity>().AsNoTracking().SingleOrDefaultAsync(predicate);
112+
113+
if (existingEntity == null)
114+
{
115+
return null;
116+
}
117+
_context.Update(entity);
118+
rowsEffected = await _context.SaveChangesAsync();
76119

120+
if(rowsEffected == 0)
121+
{
122+
await transaction.RollbackAsync();
123+
return existingEntity;
124+
}
125+
else if (rowsEffected > 1)
126+
{
127+
await transaction.RollbackAsync();
128+
return null;
77129

78-
using var transaction = await _context.Database.BeginTransactionAsync();
130+
}
131+
await transaction.CommitAsync();
132+
return entity;
79133

80-
var existingEntity = await _context.Set<TEntity>().AsNoTracking().SingleOrDefaultAsync(predicate);
134+
}
135+
);
81136

82-
if (existingEntity == null)
137+
if(rowsEffected == 0 && dbEntity == null)
83138
{
139+
_logger.LogWarning("Entity to be updated not found");
84140
return null;
85141
}
86-
_context.Update(entity);
87-
var rowsEffected = await _context.SaveChangesAsync();
88-
89-
90-
if (rowsEffected == 1)
142+
else if(rowsEffected == 0 && dbEntity != null)
91143
{
92-
await _context.Database.CommitTransactionAsync();
93-
return entity;
144+
_logger.LogError("Records where found to be updated but the update failed");
145+
throw new MultipleRecordsFoundException("Records where found to be updated but the update failed");
94146
}
95-
else if (rowsEffected > 1)
147+
else if(rowsEffected > 1)
96148
{
97-
await transaction.RollbackAsync();
98149
_logger.LogError("Multiple Records were updated by PUT request, Changes have been Rolled-back");
99150
throw new MultipleRecordsFoundException("Multiple Records were updated by PUT request, Changes have been Rolled-back");
100151
}
101-
_logger.LogError("No records were updated despite a record being found");
102-
throw new MultipleRecordsFoundException("Multiple Records were updated by PUT request, Changes have been Rolled-back");
152+
return dbEntity!;
153+
154+
155+
156+
103157
}
104158

105159
}

application/CohortManager/src/Functions/Shared/DataServices.Core/Extensions/DataServicesCoreExtension.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ public static IHostBuilder AddDataServicesHandler<DBContextType>(
2929
if (dbContextOptionsBuilder == null)
3030
{
3131
_.AddDbContextPool<DBContextType>(
32-
options => options.UseSqlServer(connectionString ?? Environment.GetEnvironmentVariable("DtOsDatabaseConnectionString"), sqlServerOptions => sqlServerOptions.CommandTimeout(180))
32+
options => options.UseSqlServer(connectionString ?? Environment.GetEnvironmentVariable("DtOsDatabaseConnectionString"), sqlServerOptions => {
33+
sqlServerOptions.CommandTimeout(180);
34+
sqlServerOptions.EnableRetryOnFailure();
35+
})
3336
);
3437
}
3538
else

0 commit comments

Comments
 (0)