Я работаю над ASP. NET приложением Core Blazor с. Net Core 3.0 (я знаю 3.1, но из-за Морды c я пока застрял с этой версией).
У меня есть многокомпонентная страница, и некоторым из этих компонентов требуется доступ к одним и тем же данным, и все они должны обновляться при обновлении коллекции. Я пытался использовать обратные вызовы на основе EventHandler, но они запускаются в своих собственных потоках примерно в одно и то же время (если я правильно понимаю ), в результате чего обратные вызовы в компонентах .razor пытаются сделать служба вызывает контекст одновременно.
Примечание: я пытался сделать переходный период жизни моего DbContext, но я все еще получаю условия гонки.
Вполне возможно, что я попал в асиновый c блендер и не знаю, как выбраться.
Я предварительно пришел к выводу, что методология event EventHandler
здесь не сработает. Мне нужен какой-то способ для запуска обновлений «измененных наборов» компонентов без запуска условия гонки.
Я думал об обновлении служб, участвующих в этих условиях гонки, следующим образом:
- Заменить каждую функцию поиска общедоступным свойством коллекции
- При каждом вызове создания / обновления / удаления обновлять каждую из этих коллекций
Это будет разрешить компонентам связываться непосредственно с измененными коллекциями, что, как я думаю, заставит обновляться каждую привязку к нему в любом компоненте без необходимости явного уведомления, а это, в свою очередь, позволит мне отказаться обработка события «коллекция изменена» полностью.
Но я не решаюсь попробовать это и еще не сделал , потому что это приведет к значительным накладным расходам на каждую основную сервисную функцию.
Другие идеи? Пожалуйста, помогите. Если коллекция изменилась, я хочу, чтобы компоненты Blazor, которые полагаются на эту коллекцию, каким-либо образом могли обновляться, будь то с помощью уведомлений или связывания, или каким-либо другим способом.
Следующий код представляет собой значительное упрощение того, что я получил, и это все еще вызывает условия гонки, когда обработчики событий вызываются из службы.
Model
public class Model
{
public int Id { get; set; }
public string Msg { get; set; }
}
MyContext
public class MyContext : DbContext
{
public MyContext() : base()
{
Models = Set<Model>();
}
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
Models = Set<Model>();
}
public DbSet<Model> Models { get; set; }
}
ModelService
public class ModelService
{
private readonly MyContext context;
private event EventHandler? CollectionChangedCallbacks;
public ModelService(MyContext context)
{
this.context = context;
}
public void RegisterCollectionChangedCallback(EventHandler callback)
{
CollectionChangedCallbacks += callback;
}
public void UnregisterCollectionChangedCallback(EventHandler callback)
{
CollectionChangedCallbacks -= callback;
}
public async Task<Model[]> FindAllAsync()
{
return await Task.FromResult(context.Models.ToArray());
}
public async Task CreateAsync(Model model)
{
context.Models.Add(model);
await context.SaveChangesAsync();
// No args necessary; the callbacks know what to do.
CollectionChangedCallbacks?.Invoke(this, EventArgs.Empty);
}
}
Startup.cs (отрывок)
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
string connString = Configuration["ConnectionStrings:DefaultConnection"];
services.AddDbContext<MyContext>(optionsBuilder => optionsBuilder.UseSqlServer(connString), ServiceLifetime.Transient);
services.AddScoped<ModelService>();
}
ParentPage.razor
@page "/simpleForm"
@using Data
@inject ModelService modelService
@implements IDisposable
@if (AllModels is null)
{
<p>Loading...</p>
}
else
{
@foreach (var model in AllModels)
{
<label>@model.Msg</label>
}
<label>Other view</label>
<ChildComponent></ChildComponent>
<button @onclick="(async () => await modelService.CreateAsync(new Model()))">Add</button>
}
@code {
private Model[] AllModels { get; set; } = null!;
public bool ShowForm { get; set; } = true;
private object disposeLock = new object();
private bool disposed = false;
public void Dispose()
{
lock (disposeLock)
{
disposed = true;
modelService.UnregisterCollectionChangedCallback(CollectionChangedCallback);
}
}
protected override async Task OnInitializedAsync()
{
AllModels = await modelService.FindAllAsync();
modelService.RegisterCollectionChangedCallback(CollectionChangedCallback);
}
private void CollectionChangedCallback(object? sender, EventArgs args)
{
// Feels dirty that I can't await this without changing the function signature. Adding async
// will make it unable to be registered as a callback.
InvokeAsync(async () =>
{
AllModels = await modelService.FindAllAsync();
// Protect against event-handler-invocation race conditions with disposing.
lock (disposeLock)
{
if (!disposed)
{
StateHasChanged();
}
}
});
}
}
ChildComponent.razor
Copy-paste (for the sake of demonstration) of ParentPage minus the label, ChildComponent, and model-adding button.
Примечание: Я также экспериментировал с попыткой вставить блок кода в часть HTML компонента, но это тоже не сработало, поскольку я не могу использовать await
там.
Возможно плохая идея, с которой я экспериментировал (и которая до сих пор не избежала коллизии потоков):
@if (AllModels is null)
{
<p><em>Loading...</em></p>
@Load();
@*
Won't compile.
@((async () => await Load())());
*@
}
else
{
...every else
}
@code {
...Initialization, callbacks, etc.
// Note: Have to return _something_ or else the @Load() call won't compile.
private async Task<string> Load()
{
ActiveChargeCodes = await chargeCodeService.FindActiveAsync();
}
}
Пожалуйста, помогите. Я экспериментирую на (для меня) неизведанной территории.