I have a blazor web app with a InteractiveServer rendermode. I have a edit page with a SignalR hub that must refresh the object when someone else has edited it. The following code is within my Razor page:
@page "/Projects/edit"@inject BlazorApp2.Data.ApplicationDbContext DB@using BlazorApp2.Data.Models@inject NavigationManager NavigationManager@using Microsoft.AspNetCore.SignalR.Client@using Microsoft.EntityFrameworkCore@rendermode InteractiveServer<PageTitle>Edit</PageTitle>@if (Project is null){<p><em>Loading...</em></p>}else{<div class="row"><div class="col-md-4"><EditForm method="post" Model="Project" OnValidSubmit="UpdateProject" FormName="edit" Enhance><DataAnnotationsValidator /><ValidationSummary /><input type="hidden" name="Project.Id" value="@Project.Id" /><div class="mb-3"><label for="name" class="form-label">Name:</label><InputText id="name" @bind-Value="Project.Name" class="form-control" /><ValidationMessage For="() => Project.Name" class="text-danger" /></div> @if (Project.Subjects is object) { @foreach (var subject in Project.Subjects) {<div class="mb-3"><label for="subject" class="form-label">Name:</label><InputText id="subject" @bind-Value="subject.Name" class="form-control" /><ValidationMessage For="() => subject.Name" class="text-danger" /></div><div @onclick="() => RemoveSubject(subject)">REMOVE</div> } } @if (Orders is object) { @foreach (var order in Orders) {<input checked="@((Project.Orders is object && Project.Orders.Any(x => x.Id == order.Id)) ? true : false)" @onchange="eventArgs => OrderClicked(order, eventArgs.Value)" type="checkbox" class="custom-control-input"></input><label class="custom-control-label">@(order.Name)</label><br /> } }<button type="submit" class="btn btn-primary">Save</button></EditForm><button type="button" @onclick="NewSubject" class="btn btn-primary">+ Subject</button><h1>@(Testtest)</h1></div></div>}<div><a href="/projects">Back to List</a></div>@code { [SupplyParameterFromQuery] public int Id { get; set; } public Project Project = new(); private List<Order> Orders = new(); private EditContext _editContext; private HubConnection _hub; private string Testtest = ""; protected override async Task OnInitializedAsync() { Project = await DB.Project.Include(x => x.Subjects).Include(x => x.Orders).FirstOrDefaultAsync(m => m.Id == Id); Orders = await DB.Order.ToListAsync(); if (Project is null) { NavigationManager.NavigateTo("notfound"); } _hub = new HubConnectionBuilder() .WithUrl(NavigationManager.ToAbsoluteUri("/dmhub")) .Build(); _hub.On<string, int>("CUD", (Model, Id) => { if (Model == "debtor" && Id == Id) { InvokeAsync(() => RefreshData()); } }); await _hub.StartAsync(); } private async Task RefreshData() { Project = await DB.Project.Include(x => x.Subjects).Include(x => x.Orders).FirstOrDefaultAsync(m => m.Id == Id); await InvokeAsync(StateHasChanged); } public async Task UpdateProject() { DB.Attach(Project).State = EntityState.Modified; try { await DB.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!ProjectExists(Project!.Id)) { NavigationManager.NavigateTo("notfound"); } else { throw; } } await _hub.SendAsync("CUD", "debtor", Project.Id); NavigationManager.NavigateTo("/projects"); } bool ProjectExists(int id) { return DB.Project.Any(e => e.Id == id); } private void NewSubject() { if (Project.Subjects is not object) { Project.Subjects = new(); } Project.Subjects.Add(new Subject()); } private void RemoveSubject(Subject subject) { if (Project.Subjects is not object) { Project.Subjects = new(); } Project.Subjects.Remove(subject); } public void OrderClicked(Order order, object checkValue) { if (Project.Orders is not object) { Project.Orders = new(); } if ((bool)checkValue == true) { if (!Project.Orders.Any(x => x.Id == order.Id)) { Project.Orders.Add(order); } } else { if (Project.Orders.Any(x => x.Id == order.Id)) { Project.Orders.RemoveAll(s => s.Id == order.Id); } } }}
Whenever I add a new Order
or Subject
to the Project
in browser tab 1, this is instantly showing in browser tab 2. But whenever I remove one or change the name of the Project, this will not be shown in browser tab 2 unless I hit F5.
I tried changing to the following code:
IDbContextFactory<BlazorApp2.Data.ApplicationDbContext> _contextprivate async Task RefreshData(){ using (var _db = _context.CreateDbContext()) { Project = await _db.Project.Include(x => x.Subjects).Include(x => x.Orders).FirstOrDefaultAsync(m => m.Id == Id); await InvokeAsync(StateHasChanged); }}public async Task UpdateProject(){ using (var _db = _context.CreateDbContext()) { _db.Attach(Project).State = EntityState.Modified; try { await DB.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!ProjectExists(Project!.Id)) { NavigationManager.NavigateTo("notfound"); } else { throw; } } await _hub.SendAsync("CUD", "debtor", Project.Id); NavigationManager.NavigateTo("/projects"); }}
But whenever I use this code, the Project won't save correctly. Added Orders or Subjects are not saved to the database. EF Core does not see the changes made to the object this way.
What is the correct way to use EF Core within my Blazor Project?