diff --git a/src/Conductors/RepositoryConductor.cs b/src/Conductors/RepositoryConductor.cs index 9c7279d..98a9187 100644 --- a/src/Conductors/RepositoryConductor.cs +++ b/src/Conductors/RepositoryConductor.cs @@ -2,6 +2,7 @@ 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; @@ -14,71 +15,9 @@ namespace AndcultureCode.CSharp.Conductors /// Provides CRUD operations on a given repository /// /// - public class RepositoryConductor : Conductor, IRepositoryConductor + public partial class RepositoryConductor : Conductor, IRepositoryConductor where T : Entity { - #region Properties - - /// - /// Ability to set and get the underlying repository's command timeout - /// - public int? CommandTimeout - { - get => _readConductor.CommandTimeout; - set - { - _createConductor.CommandTimeout = value; - _deleteConductor.CommandTimeout = value; - _readConductor.CommandTimeout = value; - _updateConductor.CommandTimeout = value; - } - } - - /// - /// Conductor property to create an entity or entities - /// - protected readonly IRepositoryCreateConductor _createConductor; - - /// - /// Conductor property to get an entity or entities - /// - protected readonly IRepositoryReadConductor _readConductor; - - /// - /// Conductor property to update an entity or entities - /// - protected readonly IRepositoryUpdateConductor _updateConductor; - - /// - /// Conductor property to delete an entity or entities - /// - protected readonly IRepositoryDeleteConductor _deleteConductor; - - #endregion Properties - - #region Constructor - - /// - /// Constructor - /// - /// The conductor instance that should be used to perform create operations - /// The conductor instance that should be used to perform read operations - /// The conductor instance that should be used to perform update operations - /// The conductor instance that should be used to perform delete operations - public RepositoryConductor( - IRepositoryCreateConductor createConductor, - IRepositoryReadConductor readConductor, - IRepositoryUpdateConductor updateConductor, - IRepositoryDeleteConductor deleteConductor) - { - _createConductor = createConductor; - _readConductor = readConductor; - _updateConductor = updateConductor; - _deleteConductor = deleteConductor; - } - - #endregion Constructor - #region Public Methods #region Create @@ -88,7 +27,8 @@ public RepositoryConductor( /// /// List of items to create /// Id of user creating the items - /// + /// A collection of the created items + [Obsolete("This method is deprecated in favor of its async counter part", false)] public virtual IResult> BulkCreate(IEnumerable items, long? createdById = default(long?)) => _createConductor.BulkCreate(items, createdById); /// diff --git a/src/Conductors/RepositoryConductorAsync.cs b/src/Conductors/RepositoryConductorAsync.cs new file mode 100644 index 0000000..ce30201 --- /dev/null +++ b/src/Conductors/RepositoryConductorAsync.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using AndcultureCode.CSharp.Core.Interfaces; +using AndcultureCode.CSharp.Core.Interfaces.Conductors; +using AndcultureCode.CSharp.Core.Models.Entities; + +namespace AndcultureCode.CSharp.Conductors +{ + public partial class RepositoryConductor : Conductor, IRepositoryConductor + where T : Entity + { + #region Properties + + /// + /// Ability to set and get the underlying repository's command timeout + /// + public int? CommandTimeout + { + get => _readConductor.CommandTimeout; + set + { + _createConductor.CommandTimeout = value; + _deleteConductor.CommandTimeout = value; + _readConductor.CommandTimeout = value; + _updateConductor.CommandTimeout = value; + } + } + + /// + /// Conductor property to create an entity or entities + /// + protected readonly IRepositoryCreateConductor _createConductor; + + /// + /// Conductor property to get an entity or entities + /// + protected readonly IRepositoryReadConductor _readConductor; + + /// + /// Conductor property to update an entity or entities + /// + protected readonly IRepositoryUpdateConductor _updateConductor; + + /// + /// Conductor property to delete an entity or entities + /// + protected readonly IRepositoryDeleteConductor _deleteConductor; + + #endregion Properties + + #region Constructor + + /// + /// Constructor + /// + /// The conductor instance that should be used to perform create operations + /// The conductor instance that should be used to perform read operations + /// The conductor instance that should be used to perform update operations + /// The conductor instance that should be used to perform delete operations + public RepositoryConductor( + IRepositoryCreateConductor createConductor, + IRepositoryReadConductor readConductor, + IRepositoryUpdateConductor updateConductor, + IRepositoryDeleteConductor deleteConductor) + { + _createConductor = createConductor; + _readConductor = readConductor; + _updateConductor = updateConductor; + _deleteConductor = deleteConductor; + } + + #endregion Constructor + + + public Task>> BulkCreateAsync(IEnumerable items, long? createdById = null) => + _createConductor.BulkCreateAsync(items, createdById); + } +} diff --git a/src/Conductors/RepositoryCreateConductor.cs b/src/Conductors/RepositoryCreateConductor.cs index 6488a1a..03deac9 100644 --- a/src/Conductors/RepositoryCreateConductor.cs +++ b/src/Conductors/RepositoryCreateConductor.cs @@ -1,5 +1,6 @@ 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; @@ -11,47 +12,13 @@ namespace AndcultureCode.CSharp.Conductors /// Ability to create an entity or multiple entities /// /// - public class RepositoryCreateConductor : Conductor, IRepositoryCreateConductor + public partial class RepositoryCreateConductor : Conductor, IRepositoryCreateConductor where T : class, IEntity { - #region Properties - - /// - /// Ability to set and get the underlying DbContext's command timeout - /// - public int? CommandTimeout - { - get => _repository.CommandTimeout; - set => _repository.CommandTimeout = value; - } - - readonly IRepository _repository; - - #endregion - - #region Constructor - - /// - /// Constructor - /// - /// - public RepositoryCreateConductor( - IRepository repository - ) - { - _repository = repository; - } - - #endregion #region IRepositoryCreateConductor - /// - /// Ability to create entities using a list in a single bulk operation. - /// - /// List of items to create - /// Id of user creating the items - /// + /// public virtual IResult> BulkCreate(IEnumerable items, long? createdById = default(long?)) { return _repository.BulkCreate(items, createdById); diff --git a/src/Conductors/RepositoryCreateConductorAsync.cs b/src/Conductors/RepositoryCreateConductorAsync.cs new file mode 100644 index 0000000..40c6417 --- /dev/null +++ b/src/Conductors/RepositoryCreateConductorAsync.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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 +{ + public partial class RepositoryCreateConductor : Conductor, IRepositoryCreateConductor + where T : class, IEntity + { + /// + /// Ability to set and get the underlying DbContext's command timeout + /// + public int? CommandTimeout + { + get => _repository.CommandTimeout; + set => _repository.CommandTimeout = value; + } + + readonly IRepository _repository; + + + /// + /// Constructor + /// + /// + public RepositoryCreateConductor( + IRepository repository + ) + { + _repository = repository; + } + + /// + public virtual Task>> BulkCreateAsync(IEnumerable items, long? createdById = null) + { + 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); + } + } +} diff --git a/src/Core/Interfaces/Conductors/IRepositoryCreateConductor.cs b/src/Core/Interfaces/Conductors/IRepositoryCreateConductor.cs index bab32a4..99268d4 100644 --- a/src/Core/Interfaces/Conductors/IRepositoryCreateConductor.cs +++ b/src/Core/Interfaces/Conductors/IRepositoryCreateConductor.cs @@ -4,7 +4,7 @@ namespace AndcultureCode.CSharp.Core.Interfaces.Conductors { - public interface IRepositoryCreateConductor + public partial interface IRepositoryCreateConductor where T : class, IEntity { #region Properties @@ -19,6 +19,13 @@ public interface IRepositoryCreateConductor #region Methods + /// + /// Ability to create entities using a list in a single bulk operation. + /// + /// List of items to create + /// Id of user creating the items + /// + [Obsolete("This method is deprecated in favor of its async counter part", false)] IResult> BulkCreate(IEnumerable items, long? createdById = null); /// diff --git a/src/Core/Interfaces/Conductors/IRepositoryCreateConductorAsync.cs b/src/Core/Interfaces/Conductors/IRepositoryCreateConductorAsync.cs new file mode 100644 index 0000000..fa2d21e --- /dev/null +++ b/src/Core/Interfaces/Conductors/IRepositoryCreateConductorAsync.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using AndcultureCode.CSharp.Core.Interfaces.Entity; + +namespace AndcultureCode.CSharp.Core.Interfaces.Conductors +{ + public partial interface IRepositoryCreateConductor + where T : class, IEntity + { + /// + /// Ability to asynchronously create entities using a list in a single bulk operation. + /// + /// List of items to create + /// Id of user creating the items + /// A collection of the created items + Task>> BulkCreateAsync(IEnumerable items, long? createdById = null); + } +} diff --git a/src/Core/Interfaces/Data/IRepository.cs b/src/Core/Interfaces/Data/IRepository.cs index 9f196fb..c431ca9 100644 --- a/src/Core/Interfaces/Data/IRepository.cs +++ b/src/Core/Interfaces/Data/IRepository.cs @@ -10,7 +10,7 @@ namespace AndcultureCode.CSharp.Core.Interfaces.Data /// /// /// - public interface IRepository + public partial interface IRepository where T : class, IEntity { #region Properties diff --git a/src/Core/Interfaces/Data/IRepositoryAsync.cs b/src/Core/Interfaces/Data/IRepositoryAsync.cs new file mode 100644 index 0000000..4169d2b --- /dev/null +++ b/src/Core/Interfaces/Data/IRepositoryAsync.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using AndcultureCode.CSharp.Core.Interfaces.Entity; + +namespace AndcultureCode.CSharp.Core.Interfaces.Data +{ + public partial interface IRepository + where T : class, IEntity + { + /// + /// Perform a DbContext.BulkInsert on an enumeration of T within a single transaction + /// + /// + /// + /// + Task>> BulkCreateAsync(IEnumerable items, long? createdById = null); + + } +} diff --git a/test/Conductors.Tests/RepositoryConductorTests/BulkCreateAsyncShould.cs b/test/Conductors.Tests/RepositoryConductorTests/BulkCreateAsyncShould.cs new file mode 100644 index 0000000..1263ec8 --- /dev/null +++ b/test/Conductors.Tests/RepositoryConductorTests/BulkCreateAsyncShould.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Linq; +using AndcultureCode.CSharp.Core.Interfaces.Conductors; +using AndcultureCode.CSharp.Testing.Extensions; +using AndcultureCode.CSharp.Testing.Tests; +using Moq; +using Shouldly; +using Xunit; +using Xunit.Abstractions; +using System.Linq.Expressions; +using System; +using AndcultureCode.CSharp.Testing.Models.Stubs; +using System.Threading.Tasks; + +namespace AndcultureCode.CSharp.Conductors.Tests.RepositoryConductorTests +{ + public class BulkCreateAsyncShould : ProjectUnitTest + { + #region Setup + + public BulkCreateAsyncShould(ITestOutputHelper output) : base(output) + { + } + + private IRepositoryConductor SetupSut( + IRepositoryMock repositoryMock + ) + { + var repositoryCreateConductor = new RepositoryCreateConductor(repositoryMock.Object); + return new RepositoryConductor( + createConductor: repositoryCreateConductor, + deleteConductor: null, + readConductor: null, + updateConductor: null + ); + } + + #endregion Setup + + [Fact] + public async Task Throw_Argument_Null_Exception_When_Given_Null_Input() + { + // Arrange + var repositoryMock = new IRepositoryMock(); + var respositoryConductor = SetupSut(repositoryMock); + + // Act & Assert + await Assert.ThrowsAsync(() => respositoryConductor.BulkCreateAsync(null)); + } + + [Fact] + public async Task Throw_Argument_Exception_When_Given_Empty_Input() + { + // Arrange + var repositoryMock = new IRepositoryMock(); + var respositoryConductor = SetupSut(repositoryMock); + + // Act & Assert + await Assert.ThrowsAsync(() => respositoryConductor.BulkCreateAsync(new List())); + } + + } +} diff --git a/test/Conductors.Tests/RepositoryConductorTests/IRepositoryMock.cs b/test/Conductors.Tests/RepositoryConductorTests/IRepositoryMock.cs new file mode 100644 index 0000000..a26c13f --- /dev/null +++ b/test/Conductors.Tests/RepositoryConductorTests/IRepositoryMock.cs @@ -0,0 +1,24 @@ +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; +using Moq; + +namespace AndcultureCode.CSharp.Conductors.Tests.RepositoryConductorTests +{ + + + internal sealed class IRepositoryMock : Mock> + where T : class, IEntity + { + public IRepositoryMock BulkCreateAsync(IResult> output, Action callback) + { + Setup(x => x.BulkCreateAsync(It.IsAny>(), null)) + .Returns(Task.FromResult(output)); + return this; + } + } +}