Base solution for your next web application

Activities of "Siyeza"

Thank you

Hi,

I have a long running operation that creates a unit of work using UnitOfWorkManager.Begin(TransactionScopeOption.Suppress) in order to prevent table/row locks.

Inside this unit of work I have operations that create their own unit of works, either via the UnitOfWork attribute or via the UnitOfWorkManager. I've noticed two things:

  1. The inner unit of works seem to inherit TransactionScopeOption.Suppress as well., Methods with the UnitOfWork attribute will inherit TransactionScopeOption.Suppress.
  2. An inner unit of work created with UnitOfWorkManager.Begin(TransactionScopeOption.Require) has to wait for outer unit of work, created with TransactionScopeOption.Suppres, to complete before it's changes are committed

The behaviour of 2) seem wrong to me.

An inner unit of work started with UnitOfWorkManager.Begin(TransactionScopeOption.Required) should commit changes immediately when it has completed, regardless if the outer unit of work was created with TransactionScopeOption.Suppress.

Thanks

Hi,

ABP version is 5.6 Base framework is .NET Core

I have an ApplicationService with the following method:

/// <summary>
        /// Submits the specified item to the workflow
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>                      
        public async Task SubmitAsync(ItemActionRequestDto request)
        {
            Guard.ArgumentNotNull(request, nameof(request));      

            using (var unitOfWork = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
            {
                // Map the request
                var workflowRequest = ObjectMapper.Map<ItemActionRequest>(request);
                workflowRequest.UserId = AbpSession.UserId.Value;

                // Submit the item to the workflow
                var workflow = await ItemWorkflowFactory.CreateAsync(workflowRequest);
                await workflow.SubmitAsync(workflowRequest);

                unitOfWork.Complete();
            }
        }

The workflow.SubmitAsync call will eventually call the following DomainService method:

/// <summary>
        /// Creates the workflow item for the specified Item
        /// </summary>
        /// <param name="item"></param>                
        public virtual void CreateWorkflowItem(TItem item)
        {
            Guard.ArgumentNotNull(item, nameof(item));

            using(var unitOfWork = UnitOfWorkManager.Begin(TransactionScopeOption.Required))
            {
                // Check if workflow item already exists
                var workflowItem = WorkflowItemRepository.FirstOrDefault(w => w.ItemRef == item.Id);
                if (workflowItem != null)
                {
                    throw new InvalidOperationException(string.Format(InternalMessages.Workflow_ItemAlreadyExists, item.Id));
                }

                // Create workflow item
                workflowItem = new WorkflowItem
                {
                    ItemRef = item.Id,
                    ItemType = item.ItemType,
                    ItemSubTypeCode = item.ItemSubTypeCode,
                    ItemSubTypeName = GetItemSubTypeName(item),
                    ItemAction = item.ItemAction,
                    TenantId = item.TenantId,
                    Trigger = WorkflowTrigger.None,
                    State = WorkflowState.NotStarted
                };

                // Get the route for the item
                var routingManager = GetRoutingManager(item);
                workflowItem.RouteItemTypeCode = routingManager.Route.ItemTypeCode;

                // Save the item
                item.WorkflowItem = workflowItem;
                ItemRepository.Update(item);


                unitOfWork.Complete();
            }

           
        }

The changes made in this inner UnitOfWork will not reflect in the DB until the outer UnitOfWork (created with TransactionScopeOption.Suppress) has completed.

Thanks

Hi,

I tried CurrentUnitOfWork.SaveChangesAsync() but it doens't solve the problem. It saves the changes in the context of the transaction (IE. genrate entity IDs etc) but those changes don't show in the DB until the outer unit of work has completed.

This is bad because it means the outer unit of work is keeping alive the transaction created in the inner unit of work. That goes against how TransactionScope.Suppress is supposed to work. The outer unit or work (created with TransactionScope.Suppress) is behaving like it was created with TransactionScope.Required.

For example, consider the code below (in a non-ABP scenario). As far as I know, changes made in innerScope will complete and reflect as soon as the innerScope has completed. It won't wait for outerScope to complete first.

using(var outerScope = new TransactionScope(TransactionScopeOption.Suppress)){

    using(var innerScope = new TransactionScope(TransactionScopeOption.Required)){
            // Make changes
            ...
            
            innerScope.Complete();
    }

    outerScope.Complete();
}

Hi,

I tested my assumption about TransactionScope.Suppress and I was correct. The inner scope doesn't wait for the outer scope to complete before the changes show in the database. Please see the code below.

 using (var outerScope = new TransactionScope(TransactionScopeOption.Suppress))
            {
                var outerConnection = new SqlConnection("Server=localhost;Database=Test;Trusted_Connection=true;Integrated Security=True;");
                outerConnection.Open();

                var command = outerConnection.CreateCommand();
                command.CommandText = "SELECT * FROM dbo.ResearchProjects";
                command.ExecuteNonQuery();

                using (var innerScope = new TransactionScope(TransactionScopeOption.Required))
                {
                    var innerCommand = outerConnection.CreateCommand();
                    command.CommandText = $"INSERT INTO dbo.ResearchProjects(ID) VALUES('{Guid.NewGuid()}');";
                    command.ExecuteNonQuery();

                    innerScope.Complete();
                }

                outerScope.Complete();
            }

I still think UnitOfWork in ABP should work the same way.

I appreciate that it currently does not work this way, so my question is how can I disable UnitOfWork interceptor for a specific ApplicationService. I can't use [UnitOfWork(IsDisabled=true)] because this ApplicationService gets called by other ApplicationServices.

Thanks,

Question

I have forgotten my host password - trying to reset using 'Forgot Password' link is not working (unknown password). How can I do this?

Answer

ASP.NET CORE MVC & jQuery, .NET CORE 3.1, v8.4

Thank you!

Hi,

ABP Version: 5.6 AspNetZero Angular 8.6 .NET Core

I'm using the default BackgroundJobManager, provided by ABP, to enqueue and process background jobs.

I'm enqueuing a background job to send email notifications. However, every second or third attempt to enqueue the job throws the following error:

 error occurred while updating the entries. See the inner exception for details.    at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList`1 entries)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList`1 entriesToSave)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Abp.EntityFrameworkCore.AbpDbContext.SaveChanges()
   at Abp.Zero.EntityFrameworkCore.AbpZeroCommonDbContext`3.SaveChanges()
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesInDbContext(DbContext dbContext)
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChanges()
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.CompleteUow()
   at Abp.Domain.Uow.UnitOfWorkBase.Complete()
   at RMS.Workflow.WorkflowAppService.SubmitAsync(ItemActionRequestDto request) in C:\Development\IDS\RMS\Main\src\RMS.Workflow\Workflow\WorkflowAppService.cs:line 278
   at Abp.Authorization.AuthorizationInterceptor.InternalInterceptAsynchronous(IInvocation invocation)
   at Abp.Domain.Uow.UnitOfWorkInterceptor.InternalInterceptAsynchronous(IInvocation invocation)
   at Abp.Domain.Uow.UnitOfWorkInterceptor.InternalInterceptAsynchronous(IInvocation invocation)
   at Abp.Auditing.AuditingInterceptor.InternalInterceptAsynchronous(IInvocation invocation)
   at Abp.Auditing.AuditingInterceptor.InternalInterceptAsynchronous(IInvocation invocation)
   at Abp.Runtime.Validation.Interception.ValidationInterceptor.InternalInterceptAsynchronous(IInvocation invocation)
   at RMS.Items.ItemAppService.SubmitAsync(ItemActionRequestDto request) in C:\Development\IDS\RMS\Main\src\RMS.Application\Items\ItemAppService.cs:line 188
   at Abp.Authorization.AuthorizationInterceptor.InternalInterceptAsynchronous(IInvocation invocation)
   at Abp.Domain.Uow.UnitOfWorkInterceptor.InternalInterceptAsynchronous(IInvocation invocation)
   at Abp.Domain.Uow.UnitOfWorkInterceptor.InternalInterceptAsynchronous(IInvocation invocation)
   at Abp.Auditing.AuditingInterceptor.InternalInterceptAsynchronous(IInvocation invocation)
   at Abp.Runtime.Validation.Interception.ValidationInterceptor.InternalInterceptAsynchronous(IInvocation invocation)
   at lambda_method(Closure , Object )
   at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&lt;InvokeNextActionFilterAsync&gt;g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&lt;InvokeInnerFilterAsync&gt;g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&lt;InvokeNextExceptionFilterAsync&gt;g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
23505: duplicate key value violates unique constraint "PK_AbpBackgroundJobs"    at Npgsql.NpgsqlConnector.&lt;&gt;c__DisplayClass160_0.&lt;&lt;DoReadMessage&gt;g__ReadMessageLong|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Npgsql.NpgsqlConnector.&lt;&gt;c__DisplayClass160_0.&lt;&lt;DoReadMessage&gt;g__ReadMessageLong|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming)
   at Npgsql.NpgsqlDataReader.NextResult()
   at Npgsql.NpgsqlCommand.ExecuteReaderAsync(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior)
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader(RelationalCommandParameterObject parameterObject)
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection ]connection) Severity: ERROR;InvariantSeverity: ERROR;SqlState: 23505;MessageText: duplicate key value violates unique constraint "PK_AbpBackgroundJobs";Detail: Key ("Id")=(6) already exists.;SchemaName: public;TableName: AbpBackgroundJobs;ConstraintName: PK_AbpBackgroundJobs;File: d:\pginstaller_12.auto\postgres.windows-x64\src\backend\access\nbtree\nbtinsert.c;Line: 570;Routine: _bt_check_unique"

As you can see, it looks the BackgroundJobManager is trying to re-insert a job entry with the same ID.

Below is how I'm enqueueing the job:

/// &lt;summary&gt;
        /// Sends the collection of templated notifications
        /// &lt;/summary&gt;
        /// &lt;param name=&quot;notifications&quot;&gt;&lt;/param&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        public async Task SendAsync(IEnumerable&lt;TemplatedNotification&gt; notifications)
        {
            foreach(var notification in notifications)
            {
                // Enqueue the job
                await _backgroundJobManager.EnqueueAsync&lt;SendTemplatedNotificationJob, TemplatedNotification&gt;(notification);
            }
        }

Below is the background job definition:

public class SendTemplatedNotificationJob : BackgroundJob&lt;TemplatedNotification&gt;, ITransientDependency
    {
        #region Constructors

        public SendTemplatedNotificationJob(
            IEmailSender emailSender,
            IRepository&lt;NotificationTemplate&gt; notificationTemplateRepository,
            UserManager userManager            
            )
        {
            Guard.ArgumentNotNull(emailSender, nameof(emailSender));
            Guard.ArgumentNotNull(notificationTemplateRepository, nameof(notificationTemplateRepository));
            Guard.ArgumentNotNull(userManager, nameof(userManager));

            _emailSender = emailSender;
            _notificationTemplateRepository = notificationTemplateRepository;
            _userManager = userManager;
            AbpSession = NullAbpSession.Instance;
        }

        #endregion

        #region Properties

        public IAbpSession AbpSession { get; set; }

        #endregion

        #region Fields


        private readonly IEmailSender _emailSender;
        private readonly IRepository&lt;NotificationTemplate&gt; _notificationTemplateRepository;
        private readonly UserManager _userManager;        

        #endregion

        #region Methods

        /// &lt;summary&gt;
        /// Sends the specified notification
        /// &lt;/summary&gt;
        /// &lt;param name=&quot;notification&quot;&gt;&lt;/param&gt;
        [UnitOfWork]
        public override void Execute(TemplatedNotification notification)
        {
            // TODO: Cater for other notification channels, besides email

            if(notification != null)
            {
                // Switch to correct tenant
                using (CurrentUnitOfWork.SetTenantId(notification.TenantId))
                {
                    // Set the session
                    using (AbpSession.Use(notification.TenantId, null))
                    {
                        // Get the template
                        var template = _notificationTemplateRepository.FirstOrDefault(t => t.Code == notification.TemplateCode);
                        if (template == null)
                        {
                            // Try to get the non-tenant template
                            using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant))
                            {
                                template = _notificationTemplateRepository.FirstOrDefault(t => t.Code == notification.TemplateCode);
                            }
                        }

                        if (template != null)
                        {
                            // First build the list of recipients
                            HashSet&lt;string&gt; recipients = new HashSet&lt;string&gt;();

                            // Get recipients by user identifier
                            foreach (var userIndentifier in notification.RecipientIdentifiers)
                            {
                                var user = _userManager.GetUserOrNull(userIndentifier);
                                if (!string.IsNullOrWhiteSpace(user?.EmailAddress))
                                {
                                    recipients.Add(user.EmailAddress);
                                }
                            }

                            // TODO: Get recipients by role
                            
                            if (recipients.Any())
                            {
                                // Build template body
                                StringBuilder body = new StringBuilder(template.Body);
                                foreach (var key in notification.TemplateKeyValues.Keys)
                                {
                                    body.Replace($"&lt;%{key}%&gt;", notification.TemplateKeyValues[key] ?? string.Empty);
                                }

                                // Construct mail message
                                MailMessage mailMessage = new MailMessage();
                                recipients.ForEach(r => mailMessage.To.Add(r));
                                mailMessage.Subject = template.Subject;
                                mailMessage.Body = body.ToString();
                                mailMessage.IsBodyHtml = true;

                                // Send the email
                                _emailSender.Send(mailMessage);                                
                            }

                        }
                    }                    
                }
            }
        }

        #endregion
    }

Thanks

Hi,

Yes, we're hosting the application in a single docker container. I also get this error when running the application locally on my development machine.

We're also using the database per tenant architecure, but I don't think this is the issue because as far as I can tell the background jobs are always created in the host DB.

Thanks

Showing 101 to 110 of 113 entries