DataAdapter issue in Scheduler

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);
}

6 Replies

SR Swathi Ravi Syncfusion Team November 19, 2024 12:31 PM UTC

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



YC Yongkee Cho November 19, 2024 12:43 PM UTC

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;
    }


YC Yongkee Cho November 19, 2024 03:18 PM UTC

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>


SR Swathi Ravi Syncfusion Team November 20, 2024 12:46 PM UTC

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.

 



YC Yongkee Cho December 6, 2024 02:11 PM UTC

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








SR Swathi Ravi Syncfusion Team December 9, 2024 12:38 PM UTC

Yongkee Cho,

If your GetSchedulesAsync method already returns a list, there is no need to use ToListAsync(). You can simply use: return await db.GetSchedulesAsync(start, end);

Loader.
Up arrow icon