I'm using a direct PostgreSQL database connection for the Scheduler's DataAdapter due to API performance issues.
However, when using the DataAdapter.ReadAsync method, I encounter an Npgsql.NpgsqlOperationInProgressException: 'A command is already in progress.' It is observed in both Entity Framework (EF) and ADO.NET DataReader (registered to dependency injection transient). I noticed the issue arises because the Scheduler executes the same query multiple times simultaneously.
Switching to the synchronous DataAdapter.Read resolves the concurrency error but results in a SocketException: An existing connection was forcibly closed by the remote host or an NpgsqlException: 'Exception while reading from stream' after idling.
Is there a more effective solution to handle this scenario?
public class ScheduleDataAdapter(ScheduleDataService service) : DataAdaptor
{
//public override async Task<object> ReadAsync(DataManagerRequest dataManagerRequest, string? additionalParam = null)
//{
// var parameters = dataManagerRequest.Params;
// var start = DateTime.Parse((string)parameters["StartDate"]);
// var end = DateTime.Parse((string)parameters["EndDate"]);
// var schedules = await service.GetAsync(start, end);
// return dataManagerRequest.RequiresCounts
// ? new DataResult() { Result = schedules, Count = schedules.Count }
// : schedules;
// //var schedules = service.Get(start, end);
// //return dataManagerRequest.RequiresCounts
// // ? new DataResult() { Result = schedules, Count = await schedules.CountAsync() }
// // : schedules;
//}
public override object Read(DataManagerRequest dataManagerRequest, string? additionalParam = null)
{
var parameters = dataManagerRequest.Params;
var start = DateTime.Parse((string)parameters["StartDate"]);
var end = DateTime.Parse((string)parameters["EndDate"]);
var schedules = service.Get(start, end);
return dataManagerRequest.RequiresCounts
? new DataResult() { Result = schedules, Count = schedules.Count() }
: schedules;
}
}
public class ScheduleDataService(IMyDb db,
//ScheduleDbContext dbContext)
{
public async Task<IList<Schedule>> GetAsync(DateTime start, DateTime end)
{
var schedules = new List<Schedule>();
await foreach (var schedule in db.GetSchedulesAsync(start, end))
schedules.Add(schedule);
return schedules;
}
public IList<Schedule> Get(DateTime start, DateTime end)
=> db.GetSchedules(start, end);
}
Hi Yongkee Cho,
Thank you for reaching out to us.
The issue you are experiencing is related to the use of the foreach loop within the GetAsync method when calling db.GetSchedulesAsync. This results in simultaneous database calls, leading to the Npgsql.NpgsqlOperationInProgressException error you encountered.
In our Scheduler, the ReadAsync and Read methods are designed to trigger only once during the data load process. They will not invoke multiple database calls simultaneously, which might help avoid concurrency-related issues.
Regards,
Swathi
You have to use foreach at least once to get all the results.
I used the foreach later for IAsyncEnumerable<Schedule> in the example above.
My other example, that returning Task<IList<Schdule>> from ADO.NET DataReader also causes the same issue.
public static async IAsyncEnumerable<Schedule> GetSchedulesAsync(this IMyDb db, DateTime startTime, DateTime endTime, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
if (db == null) throw new ArgumentNullException(nameof(db));
const string commandText = @"select b.id, b.subject, b.start_time, b.end_time, b.""location"", b.description, b.all_day, b.read_only, b.equipment_id, e.asset_type, e.manufacturer, e.model, b.recurrence_id, b.recurrence_rule, b.recurrence_exception, b.reserved_by, b.""archived$""
from equipment_booking1$raw b
join shared_equipment$raw e on e.id = b.equipment_id
where b.""archived$"" = false and b.start_time >= :startTime and b.end_time <= :endTime";
var inParameters = new Dictionary<string, object>
{
{ "startTime", startTime },
{ "endTime", endTime },
};
var reader = await db.ExecuteReaderAsync(commandText, inParameters, CommandBehavior.Default, cancellationToken);
while (await reader.ReadAsync(cancellationToken))
yield return reader.ReadSchedule();
reader.Close();
}
public static async Task<IList<Schedule>> GetSchedulesAsync(this IMyDb db, DateTime startTime, DateTime endTime, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
if (db == null) throw new ArgumentNullException(nameof(db));
const string commandText = @"select b.id, b.subject, b.start_time, b.end_time, b.""location"", b.description, b.all_day, b.read_only, b.equipment_id, e.asset_type, e.manufacturer, e.model, b.recurrence_id, b.recurrence_rule, b.recurrence_exception, b.reserved_by, b.""archived$""
from equipment_booking1$raw b
join shared_equipment$raw e on e.id = b.equipment_id
where b.""archived$"" = false and b.start_time >= :startTime and b.end_time <= :endTime";
var inParameters = new Dictionary<string, object>
{
{ "startTime", startTime },
{ "endTime", endTime },
};
var reader = await db.ExecuteReaderAsync(commandText, inParameters, CommandBehavior.Default, cancellationToken);
var schedules = new List<Schedule>();
while (await reader.ReadAsync(cancellationToken))
schedules.Add(reader.ReadSchedule());
reader.Close();
return schedules;
}
Is there any chance that the query in DataAdapter.ReadAsync is called multiple times in my Shedule component?
<SfSchedule @ref="_scheduler"
TValue="Schedule" CssClass="schedule-overview" Width="auto" Height="100%"
@bind-SelectedDate="@_selectedDate"
@bind-CurrentView="@_currentView"
Timezone="@_timezoneValue"
FirstDayOfWeek="@_firstDayOfWeek"
ShowWeekNumber="@_showWeekNumber"
WeekRule="@_weekRule"
WorkDays="@_workDays"
StartHour="@_dayStartHour"
EndHour="@_dayEndHour"
TimeFormat="@_timeFormat">
<ScheduleResources>
<ScheduleResource TItem="Equipment" TValue="string" DataSource="@_equipment" Field="EquipmentId" Title="Equipment" Name="Equipment" TextField="AssetId" IdField="Id" ColorField="Color" GroupIDField="EquipmentType"></ScheduleResource>
</ScheduleResources>
<ScheduleViews>
<ScheduleView Option="View.Day" />
<ScheduleView Option="View.Week" />
<ScheduleView Option="View.Month" />
<ScheduleView Option="View.Year" />
<ScheduleView Option="View.Agenda" />
</ScheduleViews>
<ScheduleWorkHours Start="@_workStartHour" End="@_workEndHour" />
<ScheduleEventSettings TValue="@Schedule" EnableTooltip="@_tooltipEnable">
<SfDataManager AdaptorInstance="@typeof(ScheduleDataAdapter)" Adaptor="Syncfusion.Blazor.Adaptors.CustomAdaptor"></SfDataManager>
<ScheduleField>
<FieldStartTime Name="StartTime" Title="From"></FieldStartTime>
<FieldEndTime Name="EndTime" Title="To"></FieldEndTime>
</ScheduleField>
</ScheduleEventSettings>
<ScheduleTemplates>
<EditorTemplate>
<ScheduleEdit Schedule="@(context as Schedule)" Equipment="@_equipment" />
</EditorTemplate>
</ScheduleTemplates>
</SfSchedule>
Yongkee Cho,
You have to use foreach at least once to get all the results.
Instead of using `foreach` to collect the data, you can directly call the GetSchedulesAsync method within the GetAsync method.
public async Task<IList<Schedule>> GetAsync(DateTime start, DateTime end) { // db.GetSchedulesAsync returns an asynchronous enumerable (IAsyncEnumerable<Schedule>) return await db.GetSchedulesAsync(start, end).ToListAsync(); } |
Is there any chance that the query in DataAdapter.ReadAsync is called multiple times in my Schedule component?
In your scheduler, the foreach loop is causing multiple triggers.
It seems you are using Reactive System.Linq.Async.
The implementation inside of their ToListAsync() also iterates through the list. https://github.com/dotnet/reactive/blob/fefe75993de542bc8dc6e01287fce0956a6770ab/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/ToList.cs#L22