1

I'm new to blazor (server) and I'm building a small application. I have a parent component "AccountCard.razor" a child component called "ContactsEditorModal.razor" and a child component called "ContactsList.razor". Both use their own models.

In the ContactsEditorModal I can add a contactperson. And with the EventCallback method I can do some update stuff. What I have working is that I can update the list in the ContactsList.razor after I've added a contact. But the parent component is not updated. Note, in my test, I first call the parent. When everything is rendered and on screen, I change the name of the Account in the database via SQL. Then I add the new contact (via the modal). I can see that the HandleContactModal method is hit (it updates the child component "ContactList". I also notice that the UpdateData method is called, but the result is the old object and not the object with the adjusted name.

AccountCard.razor

@page "/app/accounts/card/{id}"

<h3>Component1</h3>
<div>@Id</div>
<div>@Account.Name</div>

<button class="btn btn-primary" @onclick="() => modal.Open()">Contactperson: New</button>

<ContactList @ref="contactList" AccountId="@Account.AccountId" />

<ContactEditorModal @ref="modal" AccountId="@Account.AccountId.ToString()" CloseEventCallback="HandleContactModal">   
    <Body>
        test
    </Body>
</ContactEditorModal>

@code {
    [Inject]
    private IAccountRepository accountRepository { get; set; }

    public Entities.Account Account { get; set; } = new Entities.Account();

    [Parameter]
    public string Id { get; set; } = "";

    private ContactEditorModal modal { get; set; } = new ContactEditorModal();

    private ContactList contactList { get; set; } = new ContactList();

    protected override async Task OnInitializedAsync()
    {
        await UpdateData();
    }

    private async Task UpdateData()
    {
        try
        {
            Account = accountRepository.GetAccounts().Result.FirstOrDefault(p => p.AccountId == Guid.Parse(Id));
        }
        catch(Exception ex)
        {
            throw;
        }
    }

    async Task HandleContactModal(bool test)
    {
        await UpdateData();
        await contactList.UpdateData();
        StateHasChanged();
    }

    public string GetAddUrl() => $"/app/contacts/add/{Id}";

}

The contactList.Update() is a function in the ContactList.razor component. This function is executed and the data is updated.

ContactList.razor (update function)

public async Task UpdateData()
{
    if (AccountId == Guid.Empty)
    {
        Contacts = ContactRepository.GetContacts().ToList();
    }
    else
    {
        Contacts = ContactRepository.GetContacts().Where(p => p.Accounts.Any(p => p.AccountId == AccountId)).ToList();
                    StateHasChanged();
    }
}

The DbContext looks like this

DataContext.cs public class DataContext : DbContext { public DataContext(DbContextOptions opts) : base(opts) { }

    public DbSet<Account> Accounts => Set<Account>();
    public DbSet<Contact> Contacts => Set<Contact>(); 
}

And I'm using it in my AccountRepository (and also ContactRepository) like this:

AccountRepository.cs

public class AccountRepository : IAccountRepository
{
    private readonly DataContext _ctx;

    public AccountRepository(DataContext ctx)
    {
        _ctx = ctx;
    }

    public async Task<IQueryable<Account>> GetAccounts()
    {
        return _ctx.Accounts;
    }

    public void CreateAccount(Account account)
    {
        _ctx.Add(account);
        _ctx.SaveChanges();
    }

    public void SaveAccount(Account account)
    {
        _ctx.SaveChanges();
    }
}
KWV
  • 71
  • 5
  • 1
    Not a new problem. Here's two similar questions which I and others have answered in the past. https://stackoverflow.com/questions/70713133/blazor-child-component-rendering-and-parameter-changes AND https://stackoverflow.com/questions/69558006/how-can-i-trigger-refresh-my-main-razor-page-from-all-of-its-sub-components-wit – MrC aka Shaun Curtis Jul 11 '22 at 16:39
  • @MrCakaShaunCurtis thanks for your help. I've looked at the two questions you mentioned, but I think my question is a bit different. The HandleContactModal method is called and also the UpdateData() method. But the result of the accountRepository is not correct. If I would do something like Account.Name = "test" in the HandleContactModal, it would show the change on the parent component. – KWV Jul 11 '22 at 17:54
  • Are you saying that `Account = accountRepository....` returns the old value? BTW - the point about the referred questions and answers is that data doesn't belong in components. You should have a view Service to hold the data and do the queries back into the repository objects. And events to drive component updates when data changes. – MrC aka Shaun Curtis Jul 11 '22 at 18:09
  • @MrCakaShaunCurtis that is what I try to say. And that is not what I would expect because of the update query I did on the database. – KWV Jul 11 '22 at 19:14
  • Are you using a DBContext Factory or a single DBContext? If in doubt add the DBContext section of your Service configuration to your question. Also what does `contactList.UpdateData();` do? – MrC aka Shaun Curtis Jul 11 '22 at 20:17
  • @MrCakaShaunCurtis I've added some code to explain how I get the data. The `contactList.UpdateData();` is a function within the ContactList component and gets the data for the ContactList component. – KWV Jul 12 '22 at 05:26

2 Answers2

0

It looks like the real problem is not in the blazor component, but in EF Core. EF Core is not aware that the database record is updated via an external application / service. So it wil use the EF Core cache.

I've found a solution in this post:

How to refresh an Entity Framework Core DBContext?

KWV
  • 71
  • 5
0

Parents don't know about changes in children. The only reason it works with <ContactEditorModal> is because you have an event callback HandleContactModal that calls StateHasChanged() on the parent. You need to do the same thing with <ContactList>.

ContactList.razor (update function)

[Parameter] public EventCallback OnContactUpdated { get; set; }
public async Task UpdateData()
{
    if (AccountId == Guid.Empty)
    {
        Contacts = ContactRepository.GetContacts().ToList();
    }
    else
    {
        Contacts = ContactRepository.GetContacts().Where(p => p.Accounts.Any(p => p.AccountId == AccountId)).ToList();
                    StateHasChanged();
    }
    await OnContactUpdated.InvokeAsync();
}

AccountCard.razor

@page "/app/accounts/card/{id}"

...
<ContactList @ref="contactList" AccountId="@Account.AccountId" OnContactUpdated ="ContactUpdated "/>
...


@code {
     public async Task ContactUpdated ()
     {
          await UpdateData();
          await contactList.UpdateData();
          StateHasChanged();
     }
}

I'm away form my dev environment so you may run into some syntax errors.

clamchoda
  • 4,411
  • 2
  • 36
  • 74