Skip to content

Commit 7e438bf

Browse files
committed
wrap operations in EF transaction
1 parent 7471aa5 commit 7e438bf

File tree

3 files changed

+86
-57
lines changed

3 files changed

+86
-57
lines changed

src/JsonApiDotNetCore/Internal/JsonApiException.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public class JsonApiException : Exception
88
private readonly ErrorCollection _errors = new ErrorCollection();
99

1010
public JsonApiException(ErrorCollection errorCollection)
11-
{
11+
{
1212
_errors = errorCollection;
1313
}
1414

@@ -42,15 +42,15 @@ public JsonApiException(int statusCode, string message, Exception innerException
4242

4343
public int GetStatusCode()
4444
{
45-
if(_errors.Errors.Count == 1)
45+
if (_errors.Errors.Count == 1)
4646
return _errors.Errors[0].StatusCode;
4747

48-
if(_errors.Errors.FirstOrDefault(e => e.StatusCode >= 500) != null)
48+
if (_errors.Errors.FirstOrDefault(e => e.StatusCode >= 500) != null)
4949
return 500;
50-
51-
if(_errors.Errors.FirstOrDefault(e => e.StatusCode >= 400) != null)
50+
51+
if (_errors.Errors.FirstOrDefault(e => e.StatusCode >= 400) != null)
5252
return 400;
53-
53+
5454
return 500;
5555
}
5656
}

src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using JsonApiDotNetCore.Models;
66
using JsonApiDotNetCore.Models.Operations;
77
using JsonApiDotNetCore.Models.Pointers;
8+
using Microsoft.EntityFrameworkCore;
89

910
namespace JsonApiDotNetCore.Services.Operations
1011
{
@@ -16,35 +17,60 @@ public interface IOperationsProcessor
1617
public class OperationsProcessor : IOperationsProcessor
1718
{
1819
private readonly IOperationProcessorResolver _processorResolver;
20+
private readonly DbContext _dbContext;
1921

20-
public OperationsProcessor(IOperationProcessorResolver processorResolver)
22+
public OperationsProcessor(
23+
IOperationProcessorResolver processorResolver,
24+
DbContext dbContext)
2125
{
2226
_processorResolver = processorResolver;
27+
_dbContext = dbContext;
2328
}
2429

2530
public async Task<List<Operation>> ProcessAsync(List<Operation> inputOps)
2631
{
2732
var outputOps = new List<Operation>();
28-
29-
foreach (var op in inputOps)
33+
var opIndex = 0;
34+
using (var transaction = await _dbContext.Database.BeginTransactionAsync())
3035
{
31-
// TODO: parse pointers:
32-
// locate all objects within the document and replace them
33-
var operationsPointer = new OperationsPointer();
34-
35-
ReplaceDataPointers(op.DataObject, outputOps);
36-
ReplaceRefPointers(op.Ref, outputOps);
37-
38-
var processor = GetOperationsProcessor(op);
39-
var resultOp = await processor.ProcessAsync(op);
36+
try
37+
{
38+
foreach (var op in inputOps)
39+
{
40+
await ProcessOperation(op, outputOps);
41+
opIndex++;
42+
}
4043

41-
if (resultOp != null)
42-
outputOps.Add(resultOp);
44+
transaction.Commit();
45+
}
46+
catch (JsonApiException e)
47+
{
48+
outputOps = new List<Operation>();
49+
throw new JsonApiException(e.GetStatusCode(), $"Transaction failed on operation[{opIndex}].", e);
50+
}
51+
catch (Exception e)
52+
{
53+
throw new JsonApiException(500, $"Transaction failed on operation[{opIndex}] for an unexpected reason.", e);
54+
}
4355
}
4456

4557
return outputOps;
4658
}
4759

60+
private async Task ProcessOperation(Operation op, List<Operation> outputOps)
61+
{
62+
var operationsPointer = new OperationsPointer();
63+
64+
ReplaceDataPointers(op.DataObject, outputOps);
65+
ReplaceRefPointers(op.Ref, outputOps);
66+
67+
var processor = GetOperationsProcessor(op);
68+
var resultOp = await processor.ProcessAsync(op);
69+
70+
if (resultOp != null)
71+
outputOps.Add(resultOp);
72+
}
73+
4874
private void ReplaceDataPointers(DocumentData dataObject, List<Operation> outputOps)
4975
{
5076
if (dataObject == null) return;
Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
using System.Collections.Generic;
12
using System.Linq;
23
using System.Net;
34
using System.Threading.Tasks;
5+
using Bogus;
46
using JsonApiDotNetCore.Extensions;
7+
using JsonApiDotNetCore.Models.Operations;
58
using Microsoft.EntityFrameworkCore;
69
using OperationsExample.Data;
710
using OperationsExampleTests.Factories;
@@ -13,6 +16,7 @@ namespace OperationsExampleTests
1316
public class AddTests
1417
{
1518
private readonly Fixture _fixture;
19+
private readonly Faker _faker = new Faker();
1620

1721
public AddTests(Fixture fixture)
1822
{
@@ -41,62 +45,61 @@ public async Task Can_Create_Article()
4145
};
4246

4347
// act
44-
var response = await _fixture.PatchAsync("api/bulk", content);
48+
var result = await _fixture.PatchAsync<OperationsDocument>("api/bulk", content);
4549

4650
// assert
47-
Assert.NotNull(response);
48-
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
51+
Assert.NotNull(result);
52+
Assert.Equal(HttpStatusCode.OK, result.response.StatusCode);
4953

50-
var lastArticle = await context.Articles.LastAsync();
54+
var id = (string)result.data.Operations.Single().DataObject.Id;
55+
var lastArticle = await context.Articles.SingleAsync(a => a.StringId == id);
5156
Assert.Equal(article.Name, lastArticle.Name);
5257
}
5358

5459
[Fact]
5560
public async Task Can_Create_Articles()
5661
{
5762
// arrange
63+
var expectedCount = _faker.Random.Int(1, 10);
5864
var context = _fixture.GetService<AppDbContext>();
59-
var articles = ArticleFactory.Get(2);
65+
var articles = ArticleFactory.Get(expectedCount);
6066
var content = new
6167
{
62-
operations = new[] {
63-
new {
64-
op = "add",
65-
data = new {
66-
type = "articles",
67-
attributes = new {
68-
name = articles[0].Name
69-
}
70-
}
71-
},
72-
new {
73-
op = "add",
74-
data = new {
75-
type = "articles",
76-
attributes = new {
77-
name = articles[1].Name
78-
}
79-
}
80-
}
81-
}
68+
operations = new List<object>()
8269
};
8370

71+
for (int i = 0; i < expectedCount; i++)
72+
{
73+
content.operations.Add(
74+
new
75+
{
76+
op = "add",
77+
data = new
78+
{
79+
type = "articles",
80+
attributes = new
81+
{
82+
name = articles[i].Name
83+
}
84+
}
85+
}
86+
);
87+
}
88+
8489
// act
85-
var response = await _fixture.PatchAsync("api/bulk", content);
90+
var result = await _fixture.PatchAsync<OperationsDocument>("api/bulk", content);
8691

8792
// assert
88-
Assert.NotNull(response);
89-
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
93+
Assert.NotNull(result);
94+
Assert.Equal(HttpStatusCode.OK, result.response.StatusCode);
95+
Assert.Equal(expectedCount, result.data.Operations.Count);
9096

91-
var lastArticles = (await context.Articles
92-
.OrderByDescending(d => d.Id)
93-
.Take(2)
94-
.ToListAsync())
95-
.OrderBy(l => l.Id)
96-
.ToList();
97-
98-
Assert.Equal(articles[0].Name, lastArticles[0].Name);
99-
Assert.Equal(articles[1].Name, lastArticles[1].Name);
97+
for (int i = 0; i < expectedCount; i++)
98+
{
99+
var data = result.data.Operations[i].DataObject;
100+
var article = context.Articles.Single(a => a.StringId == data.Id.ToString());
101+
Assert.Equal(articles[i].Name, article.Name);
102+
}
100103
}
101104
}
102105
}

0 commit comments

Comments
 (0)