Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions EfCore.BulkOperations.API/EfCore.BulkOperations.API.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.4"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4"/>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\EfCore.BulkOperations\EfCore.BulkOperations.csproj"/>
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions EfCore.BulkOperations.API/EfCore.BulkOperations.API.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@EfCore.BulkOperations.API_HostAddress = http://localhost:5287

GET {{EfCore.BulkOperations.API_HostAddress}}/weatherforecast/
Accept: application/json

###
5 changes: 5 additions & 0 deletions EfCore.BulkOperations.API/IPlaceHolderForAssemblyReference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace EfCore.BulkOperations.API;

public interface IPlaceHolderForAssemblyReference
{
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
namespace EfCore.BulkOperations.Test.Setup;
namespace EfCore.BulkOperations.API.Models;

public class Product(string name, decimal price)
{
public Guid Id { get; init; } = Guid.NewGuid();
public string Name { get; init; } = name;
public string Name { get; private set; } = name;

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

public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

public void UpdateName(string name)
{
Name = name;
}
}
23 changes: 23 additions & 0 deletions EfCore.BulkOperations.API/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using EfCore.BulkOperations.API.Startup;

namespace EfCore.BulkOperations.API;

public abstract class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddServices();

var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.Run();
}
}
41 changes: 41 additions & 0 deletions EfCore.BulkOperations.API/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:48122",
"sslPort": 44393
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5287",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7020;http://localhost:5287",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
using EfCore.BulkOperations.API.Models;
using Microsoft.EntityFrameworkCore;

namespace EfCore.BulkOperations.Test.Setup;
namespace EfCore.BulkOperations.API.Repositories;

public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
: base(options)
{
}

public DbSet<Product> EquityTransactions => Set<Product>();
public DbSet<Product> Products => Set<Product>();

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

modelBuilder.ApplyConfigurationsFromAssembly(typeof(ProductMap).Assembly);

}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using EfCore.BulkOperations.API.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace EfCore.BulkOperations.Test.Setup;
namespace EfCore.BulkOperations.API.Repositories;

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

builder.HasIndex(x => x.Id)
.IsUnique();
.IsUnique();

builder.Property(x => x.Id)
.ValueGeneratedOnAdd();
.ValueGeneratedOnAdd();

builder.Property(x => x.Name)
.HasMaxLength(100)
.IsRequired();
.HasMaxLength(100)
.IsRequired();

builder.Property(x => x.Price)
.HasPrecision(19, 6)
.IsRequired();
.HasPrecision(19, 6)
.IsRequired();
}
}
18 changes: 18 additions & 0 deletions EfCore.BulkOperations.API/Repositories/IProductRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using EfCore.BulkOperations.API.Models;

namespace EfCore.BulkOperations.API.Repositories;

public interface IProductRepository
{
public Task<List<Product>> GetProducts();
public Task<Product?> GetProduct(Guid id);

public Task<int> InsertProducts(List<Product> products);
public Task<int> UpdateProducts(List<Product> products);
public Task<int> DeleteProducts(List<Product> products);
public Task<int> MergeProducts(List<Product> products);

public Task SyncDataThenCommit(List<Product> list1, List<Product> list2);

public Task SyncDataThenRollback(Product item1, List<Product> list2, List<Product> list3);
}
111 changes: 111 additions & 0 deletions EfCore.BulkOperations.API/Repositories/ProductRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System.Data;
using System.Data.Common;
using EfCore.BulkOperations.API.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;

namespace EfCore.BulkOperations.API.Repositories;

public class ProductRepository(ApplicationDbContext dbContext) : IProductRepository
{
public async Task<Product?> GetProduct(Guid id)
{
return await dbContext
.Products
.Where(x => x.Id == id)
.FirstOrDefaultAsync();
}

public async Task<List<Product>> GetProducts()
{
return await dbContext.Products.ToListAsync();
}

public async Task<int> InsertProducts(List<Product> products)
{
var rowAffected = await dbContext.BulkInsertAsync(
products,
option => { option.UniqueKeys = x => new { x.Id }; });
return rowAffected;
}

public async Task<int> UpdateProducts(List<Product> products)
{
var rowAffected = await dbContext.BulkUpdateAsync(
products,
option => { option.UniqueKeys = x => new { x.Id }; });
return rowAffected;
}

public async Task<int> DeleteProducts(List<Product> products)
{
var rowAffected = await dbContext.BulkDeleteAsync(
products,
option => { option.UniqueKeys = x => new { x.Id }; });
return rowAffected;
}

public async Task<int> MergeProducts(List<Product> products)
{
var rowAffected = await dbContext.BulkMergeAsync(
products,
option => { option.UniqueKeys = x => new { x.Id }; });
return rowAffected;
}

public async Task SyncDataThenCommit(List<Product> list1, List<Product> list2)
{
IDbContextTransaction? transaction = null;
DbConnection? connection = null;
try
{
connection = dbContext.Database.GetDbConnection();
if (connection.State != ConnectionState.Open) await connection.OpenAsync();
transaction = await dbContext.Database.BeginTransactionAsync();
var dbTransaction = transaction.GetDbTransaction();

await dbContext.BulkInsertAsync(list1, null, dbTransaction);
await dbContext.BulkInsertAsync(list2, null, dbTransaction);

await transaction.CommitAsync();
}
catch (Exception)
{
if (transaction is not null) await transaction.RollbackAsync();
throw;
}
finally
{
if (connection is { State: ConnectionState.Open }) await connection.CloseAsync();
}
}

public async Task SyncDataThenRollback(Product item1, List<Product> list2, List<Product> list3)
{
IDbContextTransaction? transaction = null;
DbConnection? connection = null;
try
{
connection = dbContext.Database.GetDbConnection();
if (connection.State != ConnectionState.Open) await connection.OpenAsync();
transaction = await dbContext.Database.BeginTransactionAsync();
var dbTransaction = transaction.GetDbTransaction();

dbContext.Products.Add(item1);
await dbContext.SaveChangesAsync();
await dbContext.BulkInsertAsync(list2, null, dbTransaction);
await dbContext.BulkInsertAsync(list3, null, dbTransaction);

throw new DbUpdateException("Internal Server Error");
}
catch (Exception)
{
if (transaction is not null) await transaction.RollbackAsync();
throw;
}
finally
{
if (connection is { State: ConnectionState.Open }) await connection.CloseAsync();
}
}
}
13 changes: 13 additions & 0 deletions EfCore.BulkOperations.API/Startup/ServiceRegistrationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using EfCore.BulkOperations.API.Repositories;

namespace EfCore.BulkOperations.API.Startup;

public static class ServiceRegistrationExtensions
{
public static IServiceCollection AddServices(this IServiceCollection services)
{
services.AddScoped<IProductRepository, ProductRepository>();

return services;
}
}
8 changes: 8 additions & 0 deletions EfCore.BulkOperations.API/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
12 changes: 12 additions & 0 deletions EfCore.BulkOperations.API/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"App": "server=localhost; database=db; user=; password="
}
}
Loading