diff --git a/src/AbpDesk/AbpDesk.Application.Contracts/AbpDesk/Tickets/ITicketAppService.cs b/src/AbpDesk/AbpDesk.Application.Contracts/AbpDesk/Tickets/ITicketAppService.cs index 3f3824bb9b..6aca279236 100644 --- a/src/AbpDesk/AbpDesk.Application.Contracts/AbpDesk/Tickets/ITicketAppService.cs +++ b/src/AbpDesk/AbpDesk.Application.Contracts/AbpDesk/Tickets/ITicketAppService.cs @@ -1,4 +1,5 @@ -using AbpDesk.Tickets.Dtos; +using System.Threading.Tasks; +using AbpDesk.Tickets.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Application.Services.Dtos; @@ -6,6 +7,6 @@ namespace AbpDesk.Tickets { public interface ITicketAppService : IApplicationService { - ListResultDto GetAll(GetAllTicketsInput input); + Task> GetAll(GetAllTicketsInput input); } } diff --git a/src/AbpDesk/AbpDesk.Application/AbpDesk/Tickets/TicketAppService.cs b/src/AbpDesk/AbpDesk.Application/AbpDesk/Tickets/TicketAppService.cs index e51402d435..a9814e841f 100644 --- a/src/AbpDesk/AbpDesk.Application/AbpDesk/Tickets/TicketAppService.cs +++ b/src/AbpDesk/AbpDesk.Application/AbpDesk/Tickets/TicketAppService.cs @@ -1,8 +1,10 @@ using System.Linq; +using System.Threading.Tasks; using AbpDesk.Tickets.Dtos; using Volo.Abp.Application.Services.Dtos; using Volo.Abp.Domain.Repositories; using Volo.Abp.Linq.Extensions; +using Volo.Abp.Uow; using Volo.ExtensionMethods; namespace AbpDesk.Tickets @@ -10,22 +12,32 @@ namespace AbpDesk.Tickets public class TicketAppService : ITicketAppService { private readonly IQueryableRepository _ticketRepository; + private readonly IUnitOfWorkManager _unitOfWorkManager; - public TicketAppService(IQueryableRepository ticketRepository) + public TicketAppService( + IQueryableRepository ticketRepository, + IUnitOfWorkManager unitOfWorkManager + ) { _ticketRepository = ticketRepository; + _unitOfWorkManager = unitOfWorkManager; } - public ListResultDto GetAll(GetAllTicketsInput input) + public async Task> GetAll(GetAllTicketsInput input) { - var tickets = _ticketRepository - .WhereIf( - !input.Filter.IsNullOrWhiteSpace(), - t => t.Title.Contains(input.Filter) || t.Body.Contains(input.Filter) - ).Select(t => new TicketDto { Id = t.Id, Title = t.Title, Body = t.Body }) - .ToList(); + using (var unitOfWork = _unitOfWorkManager.Begin()) + { + var tickets = _ticketRepository + .WhereIf( + !input.Filter.IsNullOrWhiteSpace(), + t => t.Title.Contains(input.Filter) || t.Body.Contains(input.Filter) + ).Select(t => new TicketDto { Id = t.Id, Title = t.Title, Body = t.Body }) + .ToList(); - return new ListResultDto(tickets); + await unitOfWork.CompleteAsync(); + + return new ListResultDto(tickets); + } } } } diff --git a/src/AbpDesk/AbpDesk.ConsoleDemo/AbpDesk/ConsoleDemo/TicketLister.cs b/src/AbpDesk/AbpDesk.ConsoleDemo/AbpDesk/ConsoleDemo/TicketLister.cs index b616915f51..17c05d2cc4 100644 --- a/src/AbpDesk/AbpDesk.ConsoleDemo/AbpDesk/ConsoleDemo/TicketLister.cs +++ b/src/AbpDesk/AbpDesk.ConsoleDemo/AbpDesk/ConsoleDemo/TicketLister.cs @@ -1,6 +1,7 @@ using System; using AbpDesk.Tickets; using AbpDesk.Tickets.Dtos; +using Volo.Abp.Threading; using Volo.DependencyInjection; namespace AbpDesk.ConsoleDemo @@ -16,7 +17,7 @@ namespace AbpDesk.ConsoleDemo public void List() { - var result = _ticketAppService.GetAll(new GetAllTicketsInput()); + var result = AsyncHelper.RunSync(() => _ticketAppService.GetAll(new GetAllTicketsInput())); foreach (var ticket in result.Items) { diff --git a/src/AbpDesk/AbpDesk.UI/Controllers/TicketsController.cs b/src/AbpDesk/AbpDesk.UI/Controllers/TicketsController.cs index 8d7ee64962..249313a985 100644 --- a/src/AbpDesk/AbpDesk.UI/Controllers/TicketsController.cs +++ b/src/AbpDesk/AbpDesk.UI/Controllers/TicketsController.cs @@ -1,4 +1,5 @@ -using AbpDesk.Models.Tickets; +using System.Threading.Tasks; +using AbpDesk.Models.Tickets; using AbpDesk.Tickets; using AbpDesk.Tickets.Dtos; using Microsoft.AspNetCore.Mvc; @@ -15,9 +16,9 @@ namespace AbpDesk.Controllers _ticketAppService = ticketAppService; } - public IActionResult Index(GetAllTicketsInput input) + public async Task Index(GetAllTicketsInput input) { - var result = _ticketAppService.GetAll(input); + var result = await _ticketAppService.GetAll(input); var model = new IndexViewModel { diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Repositories/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Repositories/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs index b27c5b6526..4601cec847 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Repositories/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Repositories/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs @@ -1,31 +1,65 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Uow; namespace Volo.Abp.Repositories.EntityFrameworkCore { - public class UnitOfWorkDbContextProvider : IDbContextProvider + public class UnitOfWorkDbContextProvider : IDbContextProvider where TDbContext : AbpDbContext { - private readonly TDbContext _dbContext; - private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly IConnectionStringResolver _connectionStringResolver; public UnitOfWorkDbContextProvider( - TDbContext dbContext, - IUnitOfWorkManager unitOfWorkManager) //TODO: Should create this dynamically inside a unit of work. + IUnitOfWorkManager unitOfWorkManager, + IConnectionStringResolver connectionStringResolver) { - _dbContext = dbContext; _unitOfWorkManager = unitOfWorkManager; + _connectionStringResolver = connectionStringResolver; } public TDbContext GetDbContext() { - //if (_unitOfWorkManager.Current == null) - //{ - // throw new AbpException("A DbContext can only be created inside a unit of work!"); - //} + var unitOfWork = _unitOfWorkManager.Current; + if (unitOfWork == null) + { + throw new AbpException("A DbContext can only be created inside a unit of work!"); + } + + var moduleName = "";//TODO: Get module name from DbContext? + var dbContextKey = $"{moduleName}_{typeof(TDbContext).FullName}_{_connectionStringResolver.Resolve(moduleName)}"; + + var databaseApi = unitOfWork.GetOrAddDatabaseApi( + dbContextKey, + () => new DbContextDatabaseApi( + unitOfWork.ServiceProvider.GetRequiredService() + )); + + return ((DbContextDatabaseApi)databaseApi).DbContext; + } + } + + public class DbContextDatabaseApi : IDatabaseApi + where TDbContext : AbpDbContext + { + public TDbContext DbContext { get; } - return _dbContext; + public DbContextDatabaseApi(TDbContext dbContext) + { + DbContext = dbContext; + } + + public Task SaveChangesAsync() + { + return DbContext.SaveChangesAsync(); + } + + public Task CommitAsync() + { + return DbContext.SaveChangesAsync(); } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Data/ConnectionStringResolver.cs b/src/Volo.Abp/Volo/Abp/Data/DefaultConnectionStringResolver.cs similarity index 82% rename from src/Volo.Abp/Volo/Abp/Data/ConnectionStringResolver.cs rename to src/Volo.Abp/Volo/Abp/Data/DefaultConnectionStringResolver.cs index 67fefa3881..0db356b65c 100644 --- a/src/Volo.Abp/Volo/Abp/Data/ConnectionStringResolver.cs +++ b/src/Volo.Abp/Volo/Abp/Data/DefaultConnectionStringResolver.cs @@ -5,11 +5,11 @@ using Volo.ExtensionMethods.Collections.Generic; namespace Volo.Abp.Data { - public class ConnectionStringResolver : IConnectionStringResolver, ITransientDependency + public class DefaultConnectionStringResolver : IConnectionStringResolver, ITransientDependency { - private readonly DbConnectionOptions _options; //TODO: Use IOptionsSnapshot + private readonly DbConnectionOptions _options; //TODO: Use IOptionsSnapshot? - public ConnectionStringResolver(IOptions options) + public DefaultConnectionStringResolver(IOptions options) { _options = options.Value; } diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index acc898710a..c1158f5abe 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using JetBrains.Annotations; @@ -5,6 +6,8 @@ namespace Volo.Abp.Uow { internal class ChildUnitOfWork : IUnitOfWork { + public IServiceProvider ServiceProvider => _parent.ServiceProvider; + private readonly IUnitOfWork _parent; public ChildUnitOfWork([NotNull] IUnitOfWork parent) @@ -23,6 +26,21 @@ namespace Volo.Abp.Uow { return Task.CompletedTask; } + + public IDatabaseApi FindDatabaseApi(string id) + { + return _parent.FindDatabaseApi(id); + } + + public IDatabaseApi GetOrAddDatabaseApi(string id, Func factory) + { + return _parent.GetOrAddDatabaseApi(id, factory); + } + + public IDatabaseApi AddDatabaseApi(string id, IDatabaseApi databaseApi) + { + return _parent.AddDatabaseApi(id, databaseApi); + } public void Dispose() { diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs index 935f674e2c..e333ee56f8 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs @@ -1,11 +1,20 @@ using System; using System.Threading.Tasks; +using JetBrains.Annotations; using Volo.DependencyInjection; namespace Volo.Abp.Uow { - public interface IUnitOfWork : IDisposable, ITransientDependency + public interface IUnitOfWork : IDisposable, IServiceProviderAccessor, ITransientDependency { + [CanBeNull] + IDatabaseApi FindDatabaseApi([NotNull] string id); + + [NotNull] + IDatabaseApi GetOrAddDatabaseApi(string id, Func factory); + + IDatabaseApi AddDatabaseApi(string id, IDatabaseApi databaseApi); + Task SaveChangesAsync(); Task CompleteAsync(); diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs index 6ec2d6911f..835c751451 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs @@ -1,4 +1,6 @@ -namespace Volo.Abp.Uow +using System.Threading.Tasks; + +namespace Volo.Abp.Uow { public interface IUnitOfWorkManager { @@ -6,4 +8,11 @@ IUnitOfWork Begin(); } + + public interface IDatabaseApi + { + Task SaveChangesAsync(); + + Task CommitAsync(); + } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs index 5a1377b520..51dce746f3 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -1,23 +1,68 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; +using Volo.ExtensionMethods.Collections.Generic; namespace Volo.Abp.Uow { public class UnitOfWork : IUnitOfWork { + public IServiceProvider ServiceProvider { get; } + + private readonly Dictionary _databaseApis; + + public UnitOfWork(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + + _databaseApis = new Dictionary(); + } + public void Dispose() { - throw new NotImplementedException(); + //TODO: Remove itself from IUnitOfWorkManager + } + + public async Task SaveChangesAsync() + { + foreach (var databaseApi in _databaseApis.Values) + { + await databaseApi.SaveChangesAsync(); + } } - public Task SaveChangesAsync() + public async Task CompleteAsync() { - throw new NotImplementedException(); + foreach (var databaseApi in _databaseApis.Values) + { + await databaseApi.CommitAsync(); + } } - public Task CompleteAsync() + public IDatabaseApi FindDatabaseApi(string id) { - throw new NotImplementedException(); + return _databaseApis.GetOrDefault(id); + } + + public IDatabaseApi GetOrAddDatabaseApi(string id, Func factory) + { + Check.NotNull(id, nameof(id)); + Check.NotNull(factory, nameof(factory)); + + return _databaseApis.GetOrAdd(id, factory); + } + + public IDatabaseApi AddDatabaseApi(string id, IDatabaseApi databaseApi) + { + Check.NotNull(id, nameof(id)); + Check.NotNull(databaseApi, nameof(databaseApi)); + + if (_databaseApis.ContainsKey(id)) + { + throw new AbpException($"There is already a database api with same id: {id}"); + } + + return _databaseApis[id] = databaseApi; } } } \ No newline at end of file diff --git a/test/AbpDesk/AbpDesk.Application.Tests/AbpDesk/Tickets/TicketAppService_Tests.cs b/test/AbpDesk/AbpDesk.Application.Tests/AbpDesk/Tickets/TicketAppService_Tests.cs index 44b0cc0a96..ca56ac9b6a 100644 --- a/test/AbpDesk/AbpDesk.Application.Tests/AbpDesk/Tickets/TicketAppService_Tests.cs +++ b/test/AbpDesk/AbpDesk.Application.Tests/AbpDesk/Tickets/TicketAppService_Tests.cs @@ -1,4 +1,5 @@ -using AbpDesk.Tickets.Dtos; +using System.Threading.Tasks; +using AbpDesk.Tickets.Dtos; using Microsoft.Extensions.DependencyInjection; using Shouldly; using Xunit; @@ -15,11 +16,11 @@ namespace AbpDesk.Tickets } [Fact] - public void GetAll_Test() + public async Task GetAll_Test() { //Act - var result = _ticketAppService.GetAll(new GetAllTicketsInput()); + var result = await _ticketAppService.GetAll(new GetAllTicketsInput()); //Assert @@ -27,11 +28,11 @@ namespace AbpDesk.Tickets } [Fact] - public void GetAll_Filtered_Test() + public async Task GetAll_Filtered_Test() { //Act - var result = _ticketAppService.GetAll(new GetAllTicketsInput { Filter = "non-existing-text" }); + var result = await _ticketAppService.GetAll(new GetAllTicketsInput { Filter = "non-existing-text" }); //Assert