Skip to content

Commit 7306698

Browse files
authored
Feature/integration test (#8)
1 parent d2eab25 commit 7306698

25 files changed

Lines changed: 914 additions & 301 deletions
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.4"/>
11+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4"/>
12+
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2"/>
13+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\EfCore.BulkOperations\EfCore.BulkOperations.csproj"/>
18+
</ItemGroup>
19+
20+
</Project>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@EfCore.BulkOperations.API_HostAddress = http://localhost:5287
2+
3+
GET {{EfCore.BulkOperations.API_HostAddress}}/weatherforecast/
4+
Accept: application/json
5+
6+
###
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace EfCore.BulkOperations.API;
2+
3+
public interface IPlaceHolderForAssemblyReference
4+
{
5+
}
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
namespace EfCore.BulkOperations.Test.Setup;
1+
namespace EfCore.BulkOperations.API.Models;
22

33
public class Product(string name, decimal price)
44
{
55
public Guid Id { get; init; } = Guid.NewGuid();
6-
public string Name { get; init; } = name;
6+
public string Name { get; private set; } = name;
77

88
public decimal Price { get; init; } = price;
99

1010
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
11+
12+
public void UpdateName(string name)
13+
{
14+
Name = name;
15+
}
1116
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using EfCore.BulkOperations.API.Startup;
2+
3+
namespace EfCore.BulkOperations.API;
4+
5+
public abstract class Program
6+
{
7+
public static void Main(string[] args)
8+
{
9+
var builder = WebApplication.CreateBuilder(args);
10+
builder.Services.AddEndpointsApiExplorer();
11+
builder.Services.AddSwaggerGen();
12+
builder.Services.AddServices();
13+
14+
var app = builder.Build();
15+
if (app.Environment.IsDevelopment())
16+
{
17+
app.UseSwagger();
18+
app.UseSwaggerUI();
19+
}
20+
21+
app.Run();
22+
}
23+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"$schema": "http://json.schemastore.org/launchsettings.json",
3+
"iisSettings": {
4+
"windowsAuthentication": false,
5+
"anonymousAuthentication": true,
6+
"iisExpress": {
7+
"applicationUrl": "http://localhost:48122",
8+
"sslPort": 44393
9+
}
10+
},
11+
"profiles": {
12+
"http": {
13+
"commandName": "Project",
14+
"dotnetRunMessages": true,
15+
"launchBrowser": true,
16+
"launchUrl": "swagger",
17+
"applicationUrl": "http://localhost:5287",
18+
"environmentVariables": {
19+
"ASPNETCORE_ENVIRONMENT": "Development"
20+
}
21+
},
22+
"https": {
23+
"commandName": "Project",
24+
"dotnetRunMessages": true,
25+
"launchBrowser": true,
26+
"launchUrl": "swagger",
27+
"applicationUrl": "https://localhost:7020;http://localhost:5287",
28+
"environmentVariables": {
29+
"ASPNETCORE_ENVIRONMENT": "Development"
30+
}
31+
},
32+
"IIS Express": {
33+
"commandName": "IISExpress",
34+
"launchBrowser": true,
35+
"launchUrl": "swagger",
36+
"environmentVariables": {
37+
"ASPNETCORE_ENVIRONMENT": "Development"
38+
}
39+
}
40+
}
41+
}

EfCore.BulkOperations.Test/Setup/ApplicationDbContext.cs renamed to EfCore.BulkOperations.API/Repositories/ApplicationDbContext.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1+
using EfCore.BulkOperations.API.Models;
12
using Microsoft.EntityFrameworkCore;
23

3-
namespace EfCore.BulkOperations.Test.Setup;
4+
namespace EfCore.BulkOperations.API.Repositories;
45

56
public class ApplicationDbContext : DbContext
67
{
78
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
8-
: base(options)
9+
: base(options)
910
{
1011
}
1112

12-
public DbSet<Product> EquityTransactions => Set<Product>();
13+
public DbSet<Product> Products => Set<Product>();
1314

1415
protected override void OnModelCreating(ModelBuilder modelBuilder)
1516
{
1617
base.OnModelCreating(modelBuilder);
1718

1819
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ProductMap).Assembly);
19-
2020
}
2121
}
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
using EfCore.BulkOperations.API.Models;
12
using Microsoft.EntityFrameworkCore;
23
using Microsoft.EntityFrameworkCore.Metadata.Builders;
34

4-
namespace EfCore.BulkOperations.Test.Setup;
5+
namespace EfCore.BulkOperations.API.Repositories;
56

67
public class ProductMap : IEntityTypeConfiguration<Product>
78
{
@@ -10,17 +11,17 @@ public void Configure(EntityTypeBuilder<Product> builder)
1011
builder.HasKey(i => i.Id);
1112

1213
builder.HasIndex(x => x.Id)
13-
.IsUnique();
14+
.IsUnique();
1415

1516
builder.Property(x => x.Id)
16-
.ValueGeneratedOnAdd();
17+
.ValueGeneratedOnAdd();
1718

1819
builder.Property(x => x.Name)
19-
.HasMaxLength(100)
20-
.IsRequired();
20+
.HasMaxLength(100)
21+
.IsRequired();
2122

2223
builder.Property(x => x.Price)
23-
.HasPrecision(19, 6)
24-
.IsRequired();
24+
.HasPrecision(19, 6)
25+
.IsRequired();
2526
}
2627
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using EfCore.BulkOperations.API.Models;
2+
3+
namespace EfCore.BulkOperations.API.Repositories;
4+
5+
public interface IProductRepository
6+
{
7+
public Task<List<Product>> GetProducts();
8+
public Task<Product?> GetProduct(Guid id);
9+
10+
public Task<int> InsertProducts(List<Product> products);
11+
public Task<int> UpdateProducts(List<Product> products);
12+
public Task<int> DeleteProducts(List<Product> products);
13+
public Task<int> MergeProducts(List<Product> products);
14+
15+
public Task SyncDataThenCommit(List<Product> list1, List<Product> list2);
16+
17+
public Task SyncDataThenRollback(Product item1, List<Product> list2, List<Product> list3);
18+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
using System.Data;
2+
using System.Data.Common;
3+
using EfCore.BulkOperations.API.Models;
4+
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.EntityFrameworkCore.Storage;
6+
7+
namespace EfCore.BulkOperations.API.Repositories;
8+
9+
public class ProductRepository(ApplicationDbContext dbContext) : IProductRepository
10+
{
11+
public async Task<Product?> GetProduct(Guid id)
12+
{
13+
return await dbContext
14+
.Products
15+
.Where(x => x.Id == id)
16+
.FirstOrDefaultAsync();
17+
}
18+
19+
public async Task<List<Product>> GetProducts()
20+
{
21+
return await dbContext.Products.ToListAsync();
22+
}
23+
24+
public async Task<int> InsertProducts(List<Product> products)
25+
{
26+
var rowAffected = await dbContext.BulkInsertAsync(
27+
products,
28+
option => { option.UniqueKeys = x => new { x.Id }; });
29+
return rowAffected;
30+
}
31+
32+
public async Task<int> UpdateProducts(List<Product> products)
33+
{
34+
var rowAffected = await dbContext.BulkUpdateAsync(
35+
products,
36+
option => { option.UniqueKeys = x => new { x.Id }; });
37+
return rowAffected;
38+
}
39+
40+
public async Task<int> DeleteProducts(List<Product> products)
41+
{
42+
var rowAffected = await dbContext.BulkDeleteAsync(
43+
products,
44+
option => { option.UniqueKeys = x => new { x.Id }; });
45+
return rowAffected;
46+
}
47+
48+
public async Task<int> MergeProducts(List<Product> products)
49+
{
50+
var rowAffected = await dbContext.BulkMergeAsync(
51+
products,
52+
option => { option.UniqueKeys = x => new { x.Id }; });
53+
return rowAffected;
54+
}
55+
56+
public async Task SyncDataThenCommit(List<Product> list1, List<Product> list2)
57+
{
58+
IDbContextTransaction? transaction = null;
59+
DbConnection? connection = null;
60+
try
61+
{
62+
connection = dbContext.Database.GetDbConnection();
63+
if (connection.State != ConnectionState.Open) await connection.OpenAsync();
64+
transaction = await dbContext.Database.BeginTransactionAsync();
65+
var dbTransaction = transaction.GetDbTransaction();
66+
67+
await dbContext.BulkInsertAsync(list1, null, dbTransaction);
68+
await dbContext.BulkInsertAsync(list2, null, dbTransaction);
69+
70+
await transaction.CommitAsync();
71+
}
72+
catch (Exception)
73+
{
74+
if (transaction is not null) await transaction.RollbackAsync();
75+
throw;
76+
}
77+
finally
78+
{
79+
if (connection is { State: ConnectionState.Open }) await connection.CloseAsync();
80+
}
81+
}
82+
83+
public async Task SyncDataThenRollback(Product item1, List<Product> list2, List<Product> list3)
84+
{
85+
IDbContextTransaction? transaction = null;
86+
DbConnection? connection = null;
87+
try
88+
{
89+
connection = dbContext.Database.GetDbConnection();
90+
if (connection.State != ConnectionState.Open) await connection.OpenAsync();
91+
transaction = await dbContext.Database.BeginTransactionAsync();
92+
var dbTransaction = transaction.GetDbTransaction();
93+
94+
dbContext.Products.Add(item1);
95+
await dbContext.SaveChangesAsync();
96+
await dbContext.BulkInsertAsync(list2, null, dbTransaction);
97+
await dbContext.BulkInsertAsync(list3, null, dbTransaction);
98+
99+
throw new DbUpdateException("Internal Server Error");
100+
}
101+
catch (Exception)
102+
{
103+
if (transaction is not null) await transaction.RollbackAsync();
104+
throw;
105+
}
106+
finally
107+
{
108+
if (connection is { State: ConnectionState.Open }) await connection.CloseAsync();
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)