Base solution for your next web application
Open Closed

Two Records inserted at same exact time #9953


User avatar
0
twig202 created

The following issue happens about once every 6-8 weeks. Not always the same user.

I used the Rad Tool to create AppServices etc. with the standard Create function below, somehow an the App Service is inserting TWO rows with the exact same data (exepct the PK Id (int) field).

I've added to the create code to use a Tenant Specific Sequential ID, that increments and stores the value in a table.

       [AbpAuthorize(AppPermissions.Entities_PurchaseOrders_Create)]
        private async Task Create(CreateOrEditPurchaseOrderDto input)
        {
            var purchaseOrder = ObjectMapper.Map<PurchaseOrder>(input);

            if (AbpSession.TenantId != null)
            {
                purchaseOrder.TenantId = (int)AbpSession.TenantId;
            }

            purchaseOrder.SequentialId = await _sequenceManager.GetPurchaseOrderSequence();

            if (input.PurchaseOrderStatus == (int)PurchaseOrderStatus.Closed)
                purchaseOrder.ClosedByUserId = AbpSession.UserId;

            await _purchaseOrderRepository.InsertAsync(purchaseOrder);
        }

SequenceManager below handles 9 other tables and AppServices that have this Tenant specific SequentialId.


        public async Task<int> GetPurchaseOrderSequence()
        {
            return await GetSequence(_purchaseOrderSequence);
        }
        
        private async Task<int> GetSequence(string sequenceEntity)
        {
            using (_unitOfWorkManager.Current.SetTenantId(AbpSession.TenantId.Value))
            {
                var sequence = await _repository.FirstOrDefaultAsync(x => x.Entity == sequenceEntity);
                if (sequence == null)
                    sequence = new SequentialId() { Entity = sequenceEntity, Sequence = 1 };
                else
                    sequence.Sequence = sequence.Sequence + 1;

                await _repository.InsertOrUpdateAsync(sequence);

                await _unitOfWorkManager.Current.SaveChangesAsync();

                return sequence.Sequence;
            }
        }

The SequenceManager is NOT decorated with any UnitOfWork attributes or anything like that.

Can anyone help me solve this issue that has been plaguing the app for the last year or so? EDIT: I can never recreate the issue (trying to double click the Create Modal etc.)

  • What is your product version?
    • Zero 5.4
    • Abp.AspNetZeroCore 1.1.3
    • Abp.ZeroCore 3.7.2
  • What is your product type (Angular or MVC)?
    • MVC
  • What is product framework type (.net framework or .net core)?
    • .NET Core
  • What is ABP Framework version?
    • Abp 3.7.2

14 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @twig202

    Is it possible for you to check AuditLogs and see if there are duplicate records at the same time ? It can help us to understand if Create method of the related AppService is called twice or once when this problem happened.

  • User Avatar
    0
    twig202 created

    Thanks for the response @ismcagdas!

    It appears the AuditLogs shows the Service being called twice at the same time. We're using the standard CreateOrEditModal to call the AppService from JS.

  • User Avatar
    0
    twig202 created

    @ismcagdas What are your thoughts on the AppService being called twice (same millisecond) with two different requests? As in, the ModalManager blocked the UI but somehow called the AppService twice.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @twig202,

    Could you share your cshtml and JS files ? There might be a problem in ModalManager.

    Thanks,

  • User Avatar
    0
    twig202 created
    (function ($) {
        app.modals.CreateOrEditPurchaseOrderModal = function () {
    
            var _purchaseOrdersService = abp.services.app.purchaseOrders;
    
            var _modalManager;
            var _$purchaseOrderInformationForm = null;
    
            this.init = function (modalManager) {
                _modalManager = modalManager;
    
                var modal = _modalManager.getModal();
                modal.find('.date-picker').datetimepicker({
                    locale: abp.localization.currentLanguage.name,
                    format: 'L'
                });
    
                initSearchPoTypesModal();
                initSearchDivisionsModal();
                initSearchEmployeesModal();
                initSearchVendorsModal();
    
                _$purchaseOrderInformationForm = _modalManager.getModal().find('form[name=PurchaseOrderInformationsForm]');
                _$purchaseOrderInformationForm.validate();
            };
    
           
    
            this.save = function () {
                if (!_$purchaseOrderInformationForm.valid()) {
                    return;
                }
    
                var purchaseOrder = _$purchaseOrderInformationForm.serializeFormToObject();
    
                _modalManager.setBusy(true);
                _purchaseOrdersService.createOrEdit(
                    purchaseOrder
                ).done(function () {
                    abp.notify.info(app.localize('SavedSuccessfully'));
                    _modalManager.close();
                    abp.event.trigger('app.createOrEditPurchaseOrderModalSaved');
                }).always(function () {
                    _modalManager.setBusy(false);
                });
            };
        };
    })(jQuery);
    
    
    @using System.Globalization
    @using Force.Authorization
    @using Force.Web.Areas.App.Models.Common.Modals
    @using Force.Web.Areas.App.Models.PurchaseOrders
    @using Abp.Application.Services.Dto
    @model CreateOrEditPurchaseOrderModalViewModel
    
    @Html.Partial("~/Areas/App/Views/Common/Modals/_ModalHeader.cshtml", new ModalHeaderViewModel(Model.IsEditMode ? (L("EditPurchaseOrder")) : L("CreateNewPurchaseOrder")))
    
    <div class="modal-body">
        <div id="PurchaseOrderInformationsTab">
            <form name="PurchaseOrderInformationsForm" role="form" novalidate class="form-validation">
                @if (Model.IsEditMode)
                {
                    <input type="hidden" name="id" value="@Model.PurchaseOrder.Id" />
                    <input type="hidden" name="shopPurchaseOrderId" value="@Model.PurchaseOrder.ShopPurchaseOrderId" />
                }
                <div class="row">
                    <div class="col-md-6">
                        <div class="form-group m-form__group">
                            <label for="purchaseOrderTypeId">@L("PurchaseOrderType")</label>
                            <select name="purchaseOrderTypeId" id="purchaseOrderTypeId" class="search-potypes-select2-modal" data-width="100%" required>
                                @if (Model.PurchaseOrder.PurchaseOrderTypeId != 0)
                                {
                                    <option value="@Model.PurchaseOrder.PurchaseOrderTypeId" selected="selected">@Model.PurchaseOrderTypeName</option>
                                }
                            </select>
                        </div>
                    </div>
                    <div class="col-md-6">
                        <div class="form-group m-form__group">
                            <label for="vendorId">@L("Vendor")</label>
                            <select name="vendorId" id="vendorId" class="search-vendors-select2-modal" data-width="100%" required>
                                @if (Model.PurchaseOrder.VendorId != 0)
                                {
                                    <option value="@Model.PurchaseOrder.VendorId" selected="selected">@Model.VendorName</option>
                                }
                            </select>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-md-6">
                        <div class="form-group m-form__group">
                            <label for="organizationUnitId">@L("Division")</label>
                            <select name="organizationUnitId" id="organizationUnitId" class="search-divisions-select2-modal" data-width="100%" required>
                                @if (Model.PurchaseOrder.OrganizationUnitId != 0)
                                {
                                    <option value="@Model.PurchaseOrder.OrganizationUnitId" selected="selected">@Model.OrganizationUnitDisplayName</option>
                                }
                            </select>
                        </div>
                    </div>
                    <div class="col-md-6">
                        <div class="form-group m-form__group">
                            <label for="employeeId">@L("Employee")</label>
                            <select name="employeeId" id="employeeId" class="search-employees-select2-modal" data-width="100%" required>
                                @if (Model.PurchaseOrder.EmployeeId != 0 && Model.EmployeeFirstName != null)
                                {
                                    <option value="@Model.PurchaseOrder.EmployeeId" selected="selected">@Model.EmployeeFirstName</option>
                                }
                            </select>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-md-4">
                        <div class="form-group">
                            <label for="PurchaseOrder_OrderTime">@L("OrderTime")</label>
                            <input class="form-control m-input date-picker" id="PurchaseOrder_OrderTime" type="text" name="orderTime" value="@Model.PurchaseOrder.OrderTime" />
                        </div>
                    </div>
                    <div class="col-md-4">
                        <div class="form-group">
                            <label for="PurchaseOrder_PriceEstimate">@L("PriceEstimate")</label>
                            <input class="form-control m-input" id="PurchaseOrder_PriceEstimate" value="@Model.PurchaseOrder.PriceEstimate.ToString(CultureInfo.InvariantCulture)" type="number" name="priceEstimate" />
                        </div>
                    </div>
                    <div class="col-md-4">
                        <div class="form-group">
                            <label for="PurchaseOrder_Status">@L("Status")</label>
                            @Html.DropDownList("PurchaseOrderStatus", (SelectList)Model.PurchaseOrderStatusSelectList, null, new { @class = "form-control m-input select2me", required = "required" })
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <label for="PurchaseOrder_Description">@L("Description")</label>
                    <input class="form-control" id="PurchaseOrder_Description" value="@Model.PurchaseOrder.Description" type="text" name="description" required maxlength="@Force.PurchaseOrders.PurchaseOrderConsts.MaxDescriptionLength" minlength="@Force.PurchaseOrders.PurchaseOrderConsts.MinDescriptionLength" />
                </div>
                <div class="row">
                    <div class="col-md-3">
                        <div class="form-group">
                            <label for="PurchaseOrder_Quantity">@L("Quantity")</label>
                            <input class="form-control m-input" id="PurchaseOrder_Quantity" value="@Model.PurchaseOrder.Quantity?.ToString(CultureInfo.InvariantCulture)" type="number" name="quantity" />
                        </div>
                    </div>
                    <div class="col-md-3">
                        <div class="form-group">
                            <label for="PurchaseOrder_PriceEach">@L("PriceEach")</label>
                            <input class="form-control m-input" id="PurchaseOrder_PriceEach" value="@Model.PurchaseOrder.PriceEach?.ToString(CultureInfo.InvariantCulture)" type="number" name="priceEach" />
                        </div>
                    </div>
                    <div class="col-md-3">
                        <div class="form-group">
                            <label for="PurchaseOrder_TotalTax">@L("TotalTax")</label>
                            <input class="form-control m-input" id="PurchaseOrder_TotalTax" value="@Model.PurchaseOrder.TotalTax?.ToString(CultureInfo.InvariantCulture)" type="number" name="totalTax" />
                        </div>
                    </div>
                    <div class="col-md-3">
                        <div class="form-group">
                            <label for="PurchaseOrder_PriceTotal">@L("PriceTotal")</label>
                            <input class="form-control m-input" id="PurchaseOrder_PriceTotal" value="@Model.PurchaseOrder.PriceTotal?.ToString(CultureInfo.InvariantCulture)" type="number" name="priceTotal" />
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-md-6">
                        <div class="form-group">
                            <label for="PurchaseOrder_JobField">@L("JobField")</label>
                            <input class="form-control m-input" id="PurchaseOrder_JobField" value="@Model.PurchaseOrder.JobField" type="text" name="jobField" />
                        </div>
                    </div>
                    <div class="col-md-6">
                        <div class="form-group">
                            <label for="poAccountId">@L("AccountNumber")</label>
                            @Html.DropDownList("poAccountId", Model.PoAccountItems.Select(i => i.ToSelectListItem()), new { @class = "form-control select2me" })
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <label for="PurchaseOrder_Notes">@L("Notes")</label>
                    @Html.TextArea("Notes", Model.PurchaseOrder.Notes, 4, 1, new { @class = "form-control" })
                </div>
    
            </form>
        </div>
    </div>
    
    @if (Model.PurchaseOrder.PurchaseOrderStatus != 3 || IsGranted(AppPermissions.Entities_PurchaseOrders_EditClosed))
    {
        @Html.Partial("~/Areas/App/Views/Common/Modals/_ModalFooterWithSaveAndCancel.cshtml")
    }
    else
    {
        @Html.Partial("~/Areas/App/Views/Common/Modals/_ModalFooterWithClose.cshtml")
    }
    
    
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Could you modify your save function like below and see if it makes any difference ?

    this.save = function () {
                _modalManager.setBusy(true);
                
                if (!_$purchaseOrderInformationForm.valid()) {
                    _modalManager.setBusy(false);
                    return;
                }
    
                var purchaseOrder = _$purchaseOrderInformationForm.serializeFormToObject();
                
                _purchaseOrdersService.createOrEdit(
                    purchaseOrder
                ).done(function () {
                    abp.notify.info(app.localize('SavedSuccessfully'));
                    _modalManager.close();
                    abp.event.trigger('app.createOrEditPurchaseOrderModalSaved');
                }).always(function () {
                    _modalManager.setBusy(false);
                });
            };
    
  • User Avatar
    0
    twig202 created

    Sure. I can try that, but I have never been able to recreate the issue to know for sure if your change will fix it. Do you find it strange that the AuditLog showed two entries for the CreateOrEdit call at the same EXACT Milisecond? A double click (even if we didn't block the UI) would show two different times for the AuditLog, agree?

    I say this because these issues appear to be 1 in 10000.

  • User Avatar
    0
    ismcagdas created
    Support Team

    You are right, double click probably will create two different ExecutionTime. It is hard for us to make an estimation about how to reproduce this problem.

  • User Avatar
    0
    twig202 created

    We cannot reproduce this either and have had more examples with different entities over the past week.

    This is becoming a much larger issue since our client is looking for these now and is happening much more often than thought.

    What should we look into next?

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    We will investigate this problem but since you already have it, could you try implementing my suggestion https://support.aspnetzero.com/QA/Questions/9953/Two-Records-inserted-at-same-exact-time#answer-c23a9a06-e5aa-a141-782a-39f9e420a458 and see if it makes any difference ?

  • User Avatar
    0
    twig202 created

    Will do! I will get this pushed out to our TESTING site now.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Thanks @twig202, we are also checking JS side to see if it can cause such a problem or not.

  • User Avatar
    0
    twig202 created

    We just got a few more duplicated with the above code implemented.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @twig202

    1. Is this happening on the same endpoint or on different endpoints ?
    2. Is it possible for you to share yoıur project with us via email ? So, we can examine it.

    Thanks,