Skip to content

Commit

Permalink
Issue rsm-hcd#34 and rsm-hcd#17: Implemented BulkCreateDistinctAsync …
Browse files Browse the repository at this point in the history
…and some clean up
  • Loading branch information
klauffer committed Mar 25, 2022
1 parent 392be7c commit d1688f0
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 35 deletions.
1 change: 0 additions & 1 deletion src/Conductors/RepositoryConductor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using AndcultureCode.CSharp.Core;
using AndcultureCode.CSharp.Core.Extensions;
using AndcultureCode.CSharp.Core.Interfaces;
Expand Down
8 changes: 6 additions & 2 deletions src/Conductors/RepositoryConductorAsync.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AndcultureCode.CSharp.Core.Interfaces;
using AndcultureCode.CSharp.Core.Interfaces.Conductors;
Expand Down Expand Up @@ -72,7 +73,10 @@ public RepositoryConductor(
#endregion Constructor


public Task<IResult<List<T>>> BulkCreateAsync(IEnumerable<T> items, long? createdById = null) =>
_createConductor.BulkCreateAsync(items, createdById);
public Task<IResult<List<T>>> BulkCreateAsync(IEnumerable<T> items, long? createdById = null, CancellationToken cancellationToken = default) =>
_createConductor.BulkCreateAsync(items, createdById, cancellationToken);

public Task<IResult<List<T>>> BulkCreateDistinctAsync<TKey>(IEnumerable<T> items, System.Func<T, TKey> property, long? createdById = null, CancellationToken cancellationToken = default) =>
_createConductor.BulkCreateDistinctAsync(items, property, createdById, cancellationToken);
}
}
2 changes: 0 additions & 2 deletions src/Conductors/RepositoryCreateConductor.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AndcultureCode.CSharp.Core.Interfaces;
using AndcultureCode.CSharp.Core.Interfaces.Conductors;
using AndcultureCode.CSharp.Core.Interfaces.Data;
using AndcultureCode.CSharp.Core.Interfaces.Entity;

namespace AndcultureCode.CSharp.Conductors
Expand Down
15 changes: 13 additions & 2 deletions src/Conductors/RepositoryCreateConductorAsync.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AndcultureCode.CSharp.Core.Interfaces;
using AndcultureCode.CSharp.Core.Interfaces.Conductors;
Expand Down Expand Up @@ -36,11 +37,21 @@ IRepository<T> repository
}

/// <inheritdoc />
public virtual Task<IResult<List<T>>> BulkCreateAsync(IEnumerable<T> items, long? createdById = null)
public virtual Task<IResult<List<T>>> BulkCreateAsync(IEnumerable<T> items, long? createdById = null, CancellationToken cancellationToken = default)
{
if(items == null) throw new ArgumentNullException(nameof(items));
cancellationToken.ThrowIfCancellationRequested();
if (items == null) throw new ArgumentNullException(nameof(items));
if(!items.Any()) throw new ArgumentException("An empty collection was provided", nameof(items));
return _repository.BulkCreateAsync(items, createdById);
}

/// <inheritdoc />
public Task<IResult<List<T>>> BulkCreateDistinctAsync<TKey>(IEnumerable<T> items, Func<T, TKey> property, long? createdById = null, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (items == null) throw new ArgumentNullException(nameof(items));
if (!items.Any()) throw new ArgumentException("An empty collection was provided", nameof(items));
return _repository.BulkCreateDistinctAsync(items, property, createdById, cancellationToken);
}
}
}
4 changes: 2 additions & 2 deletions src/Core/Interfaces/Conductors/IRepositoryConductor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

namespace AndcultureCode.CSharp.Core.Interfaces.Conductors
{
public interface IRepositoryConductor<T> : IConductor,
public partial interface IRepositoryConductor<T> : IConductor,
IRepositoryCreateConductor<T>,
IRepositoryDeleteConductor<T>,
IRepositoryReadConductor<T>,
IRepositoryUpdateConductor<T>
where T : AndcultureCode.CSharp.Core.Models.Entities.Entity
where T : Models.Entities.Entity
{
#region Properties

Expand Down
16 changes: 16 additions & 0 deletions src/Core/Interfaces/Conductors/IRepositoryConductorAsync.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace AndcultureCode.CSharp.Core.Interfaces.Conductors
{
public partial interface IRepositoryConductor<T> : IConductor,
IRepositoryCreateConductor<T>,
IRepositoryDeleteConductor<T>,
IRepositoryReadConductor<T>,
IRepositoryUpdateConductor<T>
where T : Models.Entities.Entity
{

}
}
19 changes: 17 additions & 2 deletions src/Core/Interfaces/Conductors/IRepositoryCreateConductorAsync.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AndcultureCode.CSharp.Core.Interfaces.Entity;

Expand All @@ -14,7 +14,22 @@ public partial interface IRepositoryCreateConductor<T>
/// </summary>
/// <param name="items">List of items to create</param>
/// <param name="createdById">Id of user creating the items</param>
/// <param name="cancellationToken">a token allowing aborting of this request</param>
/// <returns>A collection of the created items</returns>
Task<IResult<List<T>>> BulkCreateAsync(IEnumerable<T> items, long? createdById = null);
Task<IResult<List<T>>> BulkCreateAsync(IEnumerable<T> items, long? createdById = null, CancellationToken cancellationToken = default);

/// <summary>
/// Calls BulkCreate() with a de-duped list of objects as determined by the
/// property (or properties) of the object for the 'property' argument
/// NOTE: Bulking is generally faster than batching, but locks the table.
/// </summary>
/// <param name="items">List of items to attempt to create</param>
/// <param name="property">Property or properties of the typed object to determine distinctness</param>
/// <param name="createdById">Id of the user creating the entity</param>
/// <param name="cancellationToken">a token allowing aborting of this request</param>
/// <typeparam name="TKey"></typeparam>
/// <returns></returns>
Task<IResult<List<T>>> BulkCreateDistinctAsync<TKey>(IEnumerable<T> items, Func<T, TKey> property, long? createdById = null, CancellationToken cancellationToken = default);

}
}
20 changes: 5 additions & 15 deletions src/Core/Interfaces/Data/IRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,18 @@ public partial interface IRepository<T>
/// <summary>
/// Perform a DbContext.BulkInsert on an enumeration of T within a single transaction
/// </summary>
/// <param name="items"></param>
/// <param name="createdById"></param>
/// <param name="items">that items of type <see cref="IEnumerable{T}" to be inserted</param>
/// <param name="createdById">Audit field for who did this task</param>
/// <returns></returns>
IResult<List<T>> BulkCreate(IEnumerable<T> items, long? createdById = null);

/// <summary>
/// Calls BulkCreate() with a de-duped list of objects as determined by the
/// property (or properties) of the object for the 'property' argument
/// NOTE: Bulking is generally faster than batching, but locks the table.
/// </summary>
/// <param name="items">List of items to attempt to create</param>
/// <param name="property">Property or properties of the typed object to determine distinctness</param>
/// <param name="createdById">Id of the user creating the entity</param>
/// <typeparam name="TKey"></typeparam>
/// <returns></returns>

/// <summary>
/// Calls BulkCreate() after selecting a subset of 'items' based on the distinct value of 'property'
/// </summary>
/// <param name="items"></param>
/// <param name="property"></param>
/// <param name="createdById"></param>
/// <param name="items">that items of type <see cref="IEnumerable{T}" to be inserted</param>
/// <param name="property">that determines distinct records</param>
/// <param name="createdById">Audit field for who did this task</param>
/// <typeparam name="TKey"></typeparam>
/// <returns></returns>
IResult<List<T>> BulkCreateDistinct<TKey>(IEnumerable<T> items, Func<T, TKey> property, long? createdById = null);
Expand Down
22 changes: 18 additions & 4 deletions src/Core/Interfaces/Data/IRepositoryAsync.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AndcultureCode.CSharp.Core.Interfaces.Entity;

Expand All @@ -10,10 +12,22 @@ public partial interface IRepository<T>
/// <summary>
/// Perform a DbContext.BulkInsert on an enumeration of T within a single transaction
/// </summary>
/// <param name="items"></param>
/// <param name="createdById"></param>
/// <param name="items">that items of type <see cref="IEnumerable{T}" to be inserted</param>
/// <param name="createdById">Audit field for who did this task</param>
/// <param name="cancellationToken">a token allowing aborting of this request</param>
/// <returns></returns>
Task<IResult<List<T>>> BulkCreateAsync(IEnumerable<T> items, long? createdById = null);
Task<IResult<List<T>>> BulkCreateAsync(IEnumerable<T> items, long? createdById = null, CancellationToken cancellationToken = default);

/// <summary>
/// Calls BulkCreate() after selecting a subset of 'items' based on the distinct value of 'property'
/// </summary>
/// <param name="items">that items of type <see cref="IEnumerable{T}" to be inserted</param>
/// <param name="property">that determines distinct records</param>
/// <param name="createdById">Audit field for who did this task</param>
/// <param name="cancellationToken">a token allowing aborting of this request</param>
/// <typeparam name="TKey"></typeparam>
/// <returns></returns>
Task<IResult<List<T>>> BulkCreateDistinctAsync<TKey>(IEnumerable<T> items, Func<T, TKey> property, long? createdById = null, CancellationToken cancellationToken = default);

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AndcultureCode.CSharp.Core.Interfaces.Conductors;
using AndcultureCode.CSharp.Testing.Models.Stubs;
Expand All @@ -10,8 +11,6 @@ namespace AndcultureCode.CSharp.Conductors.Tests.RepositoryConductorTests
{
public class BulkCreateAsyncShould : ProjectUnitTest
{
#region Setup

public BulkCreateAsyncShould(ITestOutputHelper output) : base(output)
{
}
Expand All @@ -29,8 +28,6 @@ IRepositoryMock<UserStub> repositoryMock
);
}

#endregion Setup

[Fact]
public async Task Throw_Argument_Null_Exception_When_Given_Null_Input()
{
Expand All @@ -53,5 +50,17 @@ public async Task Throw_Argument_Exception_When_Given_Empty_Input()
await Assert.ThrowsAsync<ArgumentException>(() => respositoryConductor.BulkCreateAsync(new List<UserStub>()));
}

[Fact]
public async Task Throw_Stop_If_Canceled()
{
// Arrange
var repositoryMock = new IRepositoryMock<UserStub>();
var respositoryConductor = SetupSut(repositoryMock);
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
cancellationTokenSource.Cancel();
// Act & Assert
await Assert.ThrowsAsync<OperationCanceledException>(() => respositoryConductor.BulkCreateAsync(new List<UserStub>(), 5, cancellationToken));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AndcultureCode.CSharp.Core.Interfaces.Conductors;
using AndcultureCode.CSharp.Testing.Models.Stubs;
using Xunit;
using Xunit.Abstractions;


namespace AndcultureCode.CSharp.Conductors.Tests.RepositoryConductorTests
{
public class BulkCreateDistinctAsyncShould : ProjectUnitTest
{
public BulkCreateDistinctAsyncShould(ITestOutputHelper output) : base(output)
{
}

private IRepositoryConductor<UserStub> SetupSut(
IRepositoryMock<UserStub> repositoryMock
)
{
var repositoryCreateConductor = new RepositoryCreateConductor<UserStub>(repositoryMock.Object);
return new RepositoryConductor<UserStub>(
createConductor: repositoryCreateConductor,
deleteConductor: null,
readConductor: null,
updateConductor: null
);
}

[Fact]
public async Task Throw_Argument_Null_Exception_When_Given_Null_Input()
{
// Arrange
var repositoryMock = new IRepositoryMock<UserStub>();
var respositoryConductor = SetupSut(repositoryMock);

// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(() => respositoryConductor.BulkCreateAsync(null));
}

[Fact]
public async Task Throw_Argument_Exception_When_Given_Empty_Input()
{
// Arrange
var repositoryMock = new IRepositoryMock<UserStub>();
var respositoryConductor = SetupSut(repositoryMock);

// Act & Assert
await Assert.ThrowsAsync<ArgumentException>(() => respositoryConductor.BulkCreateAsync(new List<UserStub>()));
}

[Fact]
public async Task Throw_Stop_If_Canceled()
{
// Arrange
var repositoryMock = new IRepositoryMock<UserStub>();
var respositoryConductor = SetupSut(repositoryMock);
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
cancellationTokenSource.Cancel();
// Act & Assert
await Assert.ThrowsAsync<OperationCanceledException>(() => respositoryConductor.BulkCreateAsync(new List<UserStub>(), 5, cancellationToken));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AndcultureCode.CSharp.Core.Interfaces;
using AndcultureCode.CSharp.Core.Interfaces.Data;
Expand All @@ -13,7 +14,7 @@ internal sealed class IRepositoryMock<T> : Mock<IRepository<T>>
{
public IRepositoryMock<T> BulkCreateAsync(IResult<List<T>> output, Action<long> callback)
{
Setup(x => x.BulkCreateAsync(It.IsAny<IEnumerable<T>>(), null))
Setup(x => x.BulkCreateAsync(It.IsAny<IEnumerable<T>>(), null, It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(output));
return this;
}
Expand Down

0 comments on commit d1688f0

Please sign in to comment.