Weird behavior SaveChangesAsync

raysefo

Well-known member
Joined
Feb 22, 2019
Messages
361
Programming Experience
10+
Hello guys,
I have this Blazor Server application. I am adding (OnSubmit) records by selecting the game, quantity, and description. The problem is when I add records one after another by clicking the purchase button, the previously added record gets the value of the last added record as you can see in the screenshot below.

eg. I first added 922, 33, and "orhan 1" for the first record. Then added 43, 11, and "orhan 2". But somehow the first record's total amount becomes 11 and the description becomes orhan 2.

What is the problem?

Ekran görüntüsü 2023-03-18 161447.jpg


Here is the razor page:
OnSubmit:
@page "/purchase"
@using OyunPalas.CoreBusiness
@using OyunPalas.CoreBusiness.Models
@using OyunPalas.UseCases.Interfaces.Product
@using OyunPalas.UseCases.Interfaces.Reconciliation
@using System.Security.Claims
@using OfficeOpenXml
@using OfficeOpenXml.Style
@using OyunPalas.UseCases.Interfaces.BulkPurchaseRequest
@inject ILogger<Purchase> Logger
@inject IViewProductsUseCase ViewProductsUseCase
@inject IAddBulkPurchaseRequestsViewCase AddBulkPurchaseRequestsViewCase
@inject AuthenticationStateProvider _authenticationStateProvider
@inject NavigationManager NavigationManager
@inject DialogService DialogService
@inject IViewBulkPurchaseRequestViewCase ViewBulkPurchaseRequestViewCase
@inject IUpdateBulkPurchaseRequestStatusUseCase UpdateBulkPurchaseRequestStatusUseCase
@inject IJSRuntime JS
@inject NotificationService NotificationService
@implements IDisposable
<PageTitle>Purchase</PageTitle>

<style>
    #description { width: 450px !important; }
</style>

<RadzenCard Class="my-2">
    <RadzenTemplateForm TItem="BulkPurchaseRequest" Data=@bulkPurchaseRequest Submit=@OnSubmit InvalidSubmit=@OnInvalidSubmit>
        <RadzenFieldset Text="">
            <RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Start" Wrap="FlexWrap.Wrap">
                <RadzenStack Orientation="Orientation.Vertical" Gap="10px">
                    Game
                    <RadzenDropDown AllowClear="true" TValue="ProductCode" FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" AllowFiltering="true" Class="w-100"
                                    Placeholder="Select..." Data=@_products TextProperty="ClientCode" ValueProperty="GameCode" Change=@(args => OnCodeChange(args)) Name="product"/>
                    <RadzenNumericRangeValidator Component="product" Min="1" Text="Product Code is Required!" Popup=@popup Style="position: relative;"/>
                </RadzenStack>
                <RadzenStack Orientation="Orientation.Vertical" Gap="10px">
                    Quantity
                    <RadzenNumeric TValue="int" Change=@(args => OnAmountChange(args)) Class="w-100" Name="TotalAmount" @bind-Value=@bulkPurchaseRequest.TotalAmount/>
                    <RadzenNumericRangeValidator Component="TotalAmount" Min="1" Max="20000" Text="Quantity should be between 1 and 20000" Popup=@popup Style="position: relative"/>
                </RadzenStack>
                <RadzenStack Orientation="Orientation.Vertical" Gap="10px">
                    Description
                    <RadzenTextBox Class="w-100" Name="Description" id="description" Change=@(args => OnDescriptionChange(args)) @bind-Value=@bulkPurchaseRequest.Description/>
                    <RadzenRequiredValidator Component="Description" Text="Description is required" Popup=@popup Style="position: relative"/>
                </RadzenStack>
                <RadzenStack Orientation="Orientation.Vertical" Gap="10px">
                    &nbsp;
                    <RadzenButton Text="Purchase" id="btn" ButtonType="ButtonType.Submit"/>
                </RadzenStack>
            </RadzenStack>
        </RadzenFieldset>
    </RadzenTemplateForm>
</RadzenCard>

<RadzenButton Text="Export XLS" Icon="grid_on" Click="@(args => Export())" ButtonStyle="ButtonStyle.Secondary" Size="ButtonSize.Small" Class="mb-1"/>

<RadzenDataGrid @ref="_grid" AllowPaging="true" AllowSorting="true" Data="@_bulkPurchaseRequest" TItem="BulkPurchaseRequest"
                PagerHorizontalAlign="HorizontalAlign.Justify" ShowPagingSummary="true" AllowColumnResize="true"
                AllowVirtualization="true" Style="width: calc(100vw - 55px);" PageSize="10" GridLines="DataGridGridLines.Both"
                AllowColumnPicking="true" AllowFiltering="true" FilterMode="FilterMode.Advanced" LogicalFilterOperator="LogicalFilterOperator.Or"
                IsLoading=@isLoading Sort="@ShowLoading" Page="@ShowLoading" Filter="@ShowLoading">

    <Columns>
        <RadzenDataGridColumn TItem="BulkPurchaseRequest" Property="Id" Title="Id" OrderIndex="1" Visible="false"/>
        <RadzenDataGridColumn TItem="BulkPurchaseRequest" Property="ProductCode" Title="Product Code" Width="120px" OrderIndex="2"/>
        <RadzenDataGridColumn TItem="BulkPurchaseRequest" Property="TotalAmount" Title="Total Amount" Width="120px" OrderIndex="3"/>
        <RadzenDataGridColumn TItem="BulkPurchaseRequest" Property="Description" Title="Description" Width="150px" OrderIndex="4"/>
        <RadzenDataGridColumn TItem="BulkPurchaseRequest" Property="RequestDateTime" Title="Request Date" Width="85px" OrderIndex="5">
            <Template Context="BulkPurchaseRequest">
                @($"{BulkPurchaseRequest.RequestDateTime:dd/MM/yyyy}")
            </Template>
        </RadzenDataGridColumn>
        <RadzenDataGridColumn TItem="BulkPurchaseRequest" Property="Status" Title="Status" Width="80px" OrderIndex="6">
            <Template Context="BulkPurchaseRequest">
                @if (BulkPurchaseRequest.Status == 0)
                {
                    <span style='color: red'>Continues</span>
                }
                else
                {
                    <span style='color: black'>Completed</span>
                }
            </Template>
        </RadzenDataGridColumn>
        <RadzenDataGridColumn TItem="BulkPurchaseRequest" Context="display" Filterable="false" Sortable="false" TextAlign="TextAlign.Center" Width="25px" OrderIndex="7">
            <Template Context="display">
                <RadzenButton Icon="receipt" ButtonStyle="ButtonStyle.Secondary" Class="m-1" Click="@(args => OpenOrder(display))" Size="ButtonSize.Small">
                </RadzenButton>

            </Template>

        </RadzenDataGridColumn>
    </Columns>
</RadzenDataGrid>

@code {
    IEnumerable<ProductCode?> _products;
    Orientation orientation = Orientation.Horizontal;
    AlignItems alignItems = AlignItems.Normal;

    RadzenDataGrid<BulkPurchaseRequest?> _grid;
    RadzenDataGrid<BulkPurchase?> _gridDetail;

    IEnumerable<BulkPurchaseRequest?> _bulkPurchaseRequest;
    IEnumerable<BulkPurchase?> _bulkPurchases;
    BulkPurchaseRequest coupons;

    IQueryable<BulkPurchaseRequest?> filter;

    readonly bool _auto = true;
    ClaimsPrincipal user;
    int totalAmount;
    string productCode;
    string description;
    bool popup = false;

    bool isLoading = false;

    BulkPurchaseRequest bulkPurchaseRequest = new();

    async Task ShowLoading()
    {
        isLoading = true;

        await Task.Yield();

        isLoading = false;
    }

    protected override async Task OnInitializedAsync()
    {
        await ShowLoading();
        user = (await _authenticationStateProvider.GetAuthenticationStateAsync()).User;
        
        if (!user.Identity.IsAuthenticated)
        {
            NavigationManager.NavigateTo("/Identity/Account/Login", false);
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        if (firstRender)
        {
            await ShowLoading();
            _products = await ViewProductsUseCase.ExecuteAsync();
            _bulkPurchaseRequest = await ViewBulkPurchaseRequestViewCase.ExecuteAsync();
            await UpdateBulkPurchaseRequestStatusUseCase.ExecuteAsync();
            StateHasChanged();
        }
    }

    async Task OnSubmit(BulkPurchaseRequest model)
    {
        bulkPurchaseRequest = new BulkPurchaseRequest
        {
            ProductCode = productCode,
            TotalAmount = model.TotalAmount,
            Email = user.Identity.Name,
            Description = model.Description
        };

        //Save
        await AddBulkPurchaseRequestsViewCase.ExecuteAsync(bulkPurchaseRequest);
        _bulkPurchaseRequest = await ViewBulkPurchaseRequestViewCase.ExecuteAsync();
        StateHasChanged();
        NotificationService.Notify(new NotificationMessage { Severity = NotificationSeverity.Success, Summary = "Transaction Successful", Detail = productCode + " Added to the list", Duration = 4000 });
    }

    void OnInvalidSubmit(FormInvalidSubmitEventArgs args)
    {
    }

    void OnCodeChange(object args)
    {
        if (args != null)
        {
            productCode = args.ToString();
        }
    }

    void OnAmountChange(object args)
    {
        if (args != null)
        {
            totalAmount = Convert.ToInt32(args.ToString());
        }
    }

    void OnDescriptionChange(object args)
    {
        if (args != null)
        {
            description = args.ToString();
        }
    }

    private async Task Export()
    {
        filter = _grid.View;
        ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
        var stream = new MemoryStream();

        using (var package = new ExcelPackage(stream))
        {
            var workSheet = package.Workbook.Worksheets.Add("Razer Bulk Purchase");
            workSheet.Protection.IsProtected = true;

            var recordIndex = 2;

            workSheet.Row(1).Style.Font.Bold = true;
            var headerCells = workSheet.Cells["A1:E1"];
            headerCells.Style.Font.Bold = true;
            headerCells.Style.Font.Size = 13;
            headerCells.Style.Border.BorderAround(ExcelBorderStyle.Thin);
            headerCells.Style.Border.Top.Style = ExcelBorderStyle.Thin;
            headerCells.Style.Border.Left.Style = ExcelBorderStyle.Thin;
            headerCells.Style.Border.Right.Style = ExcelBorderStyle.Thin;
            headerCells.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
            headerCells.Style.Font.Color.SetColor(System.Drawing.Color.Black);
            headerCells = workSheet.Cells["A1:E1"];
            // Set their background color to DarkBlue.
            headerCells.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
            headerCells.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray);

            workSheet.Cells[1, 1].Value = "Product Code";
            workSheet.Cells[1, 2].Value = "Total Amount";
            workSheet.Cells[1, 3].Value = "Description";
            workSheet.Cells[1, 4].Value = "Requested Date";
            workSheet.Cells[1, 5].Value = "Status";


            foreach (var bulk in filter)
            {
                workSheet.Cells[recordIndex, 1].Value = bulk.ProductCode;
                workSheet.Cells[recordIndex, 1].Style.Font.Bold = true;
                workSheet.Cells[recordIndex, 2].Value = bulk.TotalAmount;
                workSheet.Cells[recordIndex, 3].Value = bulk.Description;
                workSheet.Cells[recordIndex, 4].Value = bulk.RequestDateTime.ToString();
                if (bulk.Status == 0)
                {
                    workSheet.Cells[recordIndex, 5].Value = "Continues";
                    workSheet.Cells[recordIndex, 5].Style.Font.Color.SetColor(System.Drawing.Color.Red);
                }
                else
                {
                    workSheet.Cells[recordIndex, 5].Value = "Completed";
                    workSheet.Cells[recordIndex, 5].Style.Font.Color.SetColor(System.Drawing.Color.Black);
                }
                    
                
                workSheet.Cells[recordIndex, 5].Style.Font.Bold = true;
                

                recordIndex++;
            }

            //Make all text fit the cells
            workSheet.Cells[workSheet.Dimension.Address].AutoFitColumns();
            await package.SaveAsync();
        }
        stream.Position = 0;
        var excelName = $"RazerBulkPurchaseRequestList-{DateTime.Now.ToString("ddMMyyyyHHmm")}.xlsx";


        using var streamRef = new DotNetStreamReference(stream: stream);

        await JS.InvokeVoidAsync("downloadFileFromStream", excelName, streamRef);
    }

    public void Dispose()
    {
    
        DialogService.Dispose();
    }

    
    private async Task OpenOrder(object args)
    {
        coupons = (BulkPurchaseRequest)args;
        await DialogService.OpenAsync<DialogCardPage>("",
            new Dictionary<string, object>() { { "Coupons", coupons } },
            new DialogOptions() { Width = "1000px", Height = "685px", Resizable = true, Draggable = true, ShowClose = false, CloseDialogOnEsc = true });
    }

}

Here is the repo method:
SaveChangesAsync:
public async Task AddBulkRequestAsync(BulkPurchaseRequest bulkPurchaseRequest)
        {
            await _oyunPalasContext.BulkPurchaseRequests.AddAsync(bulkPurchaseRequest);
            await _oyunPalasContext.SaveChangesAsync();

            #region BulkPurchase generation

            var requestedAmount = bulkPurchaseRequest.TotalAmount;

            while (requestedAmount > 0)
            {
                var count = Math.Min(requestedAmount, 20);

                var bulkPurchase = new BulkPurchase
                {
                    Amount = count,
                    BulkPurchaseRequestId = bulkPurchaseRequest.Id,
                    ProductCode = bulkPurchaseRequest.ProductCode,
                    PurchaseDateTime = DateTime.Now,
                    Status = 0
                };

                await _oyunPalasContext.BulkPurchases.AddAsync(bulkPurchase);
                await _oyunPalasContext.SaveChangesAsync();

                requestedAmount -= count;
            }

            #endregion

        }
 
In the future, please post the minimal amount of code to reproduce the problem, or at least tell us what bits of code are relevant.

Anyway, it seems to me that the code that really matters are these two:
C#:
<RadzenDataGrid @ref="_grid" AllowPaging="true" AllowSorting="true" Data="@_bulkPurchaseRequest" TItem="BulkPurchaseRequest"
and
C#:
_bulkPurchaseRequest = await ViewBulkPurchaseRequestViewCase.ExecuteAsync();

with the latter being more important. The former will presumably just render a grid given the data found in _bulkPurchaseRequest. The latter changes the data found there. So the question is what does ViewBulkPurchaseRequestViewCase.ExecuteAsync() do? Can you share the code with us?

Or is the problem really in the preceding line:
C#:
await AddBulkPurchaseRequestsViewCase.ExecuteAsync(bulkPurchaseRequest);
where the method doesn't actually save data correctly, and so the next line does not get the correct data to display?

The more unlikely case is there is a bug in that RadzenDataGrid where it doesn't like variables being swapped underneath it.
 
C#:
IEnumerable<BulkPurchaseRequest?> _bulkPurchaseRequest;
...
BulkPurchaseRequest bulkPurchaseRequest;

Setting yourself up for a world of pain with code like that, tbh. Your IEnumerable should have a plural name

Also, perhaps don't use a BulkPurchaseRequest as the binder target for your textboxes; there isn't any need to have an instance of it just so you can bind to a string property of it. If you just use a basic string, int etc as your bind targets then when you want to compose them into a BulkPuchaseRequest to add to the grid, do so witjin the method doing the adding, building a new BPR object from the simple primitives bound..

I suspect you don't need to do it as a submit either. Dial down the class level occurrences of a BPR and you'll be less likely to run into some situation where what you thought was a new object is actually a reused reference to an old one, leading to repeated data

If I was doing this Ui, aside from the fact that I'd probably be doing it in MudBlazor rather than RadZen, it would look more like:

C#:
<MudTextField @bind-Value="_name" />
<MudNumericField @bind-Value="_age" />
<MudButton OnClick="AddNewPersonAsync">Add</MudButton>
<MudTable Items="_people"/>

@code{
  string _name = "";
  int _age = 21;
  List<Person> _people = new();

  async Task AddNewPersonAsync(){
    _people.Add(new(_name, _age));
  }

  record Person(string Name, int age);
}

Note, I don't guarantee this code compiles and works, it's purely to illustrate the notion of not having multiple model objects lying around that you could end up getting confused between
 
Back
Top Bottom