Skip to main content
All docs
V24.1

Load Data on Demand

  • 8 minutes to read

The SchedulerControl can load data on demand. You can write code so that the control only fetches the items that belong to the current visible interval. Use this technique to optimize initial load time and memory consumption if the Scheduler works with a large data source.

Run Demo: On-Demand Data Loading

View Example

To load items on demand, handle the following events:

Appointments
DataSource.FetchAppointments
Time Regions
DataSource.FetchTimeRegions

Use the FetchDataEventArgs.Result property to fetch data synchronously. Note that the application may freeze during the data fetch in case of a slow connection.

Use the FetchDataEventArgs.AsyncResult property to fetch data asynchronously. While the Scheduler loads data, a user can navigate to other time ranges, switch between Views, and interact with the control in other ways, except appointment changes. If the Scheduler needs time to load data, the Wait Indicator appears. To turn off the Wait Indicator during data load, set the ShowWaitIndicator property to false.

Time Interval

The SchedulerControl loads items based on the visible range. To load more items in advance, set your custom time interval in the DataSource.FetchRange property. In the DataSource.FetchAppointments and DataSource.FetchTimeRegions event handlers, you can use the FetchDataEventArgs.Interval property to get the time range for which the Scheduler loads items.

If the SchedulerControl.VisibleIntervals duration is longer than DataSource.FetchRange, the DataSource.FetchRange value is ignored. The default DataSource.FetchRange value is 1 month.

Use the DataSource.FetchRange property to prevent frequent database lookups. When the SchedulerControl.VisibleIntervals value changes within the initially calculated Interval, the Interval value is not recalculated, and the Scheduler does not load additional data.

For example, the DataSource.FetchRange property has the 6 days value. A user selects two intervals to display in the Scheduler: Day3 and Day5-Day6. The event’s Interval property is Day2-Day7. When a user selects other days within the Day2-Day7 interval, the Scheduler does not load additional data.

The interval example

Scheduler Items

Use the FetchDataEventArgs.AsyncResult property to specify the list of Scheduler items to load from the data source. The AsyncResult property can accept data objects or unbound Scheduler items.

Data Objects

To pass data objects to the AsyncResult property, set the DataSource.FetchMode property to Bound. Specify the AppointmentMappings/TimeRegionMappings property and map the Id. Refer to the following help topic for more information: Mappings.

The FetchDataEventArgs.GetFetchExpression method allows you to generate an expression that obtains appointments from the data source. If the data source supports the IQueryable interface, use the following code sample to fetch data asynchronously:

public void FetchAppointments(FetchDataEventArgs args) {
    args.AsyncResult = dbContext.AppointmentEntities
        .Where(args.GetFetchExpression<AppointmentEntity>())
        .ToArrayAsync<object>();
}

To load a recurrent pattern for the requested Interval, load all the changed and deleted occurrences for this pattern. To load recurrences accurately, use the item’s QueryStart and QueryEnd properties.

Add two data source fields of the System.DateTime type in your data source. Specify the QueryStart and QueryEnd mappings for appointments/time regions to handle the DataSource.FetchAppointments/DataSource.FetchTimeRegions events. The Scheduler sets and updates asynchronously the values for these data properties.

Scheduler item’s QueryStart and QueryEnd properties allow you to calculate the correct interval that is used in a SELECT query when you handle the DataSource.FetchAppointments/DataSource.FetchTimeRegions events. The use of the TimeRegionMappings.Start and TimeRegionMappings.End properties is not recommended in this scenario because such an interval may not include time region patterns and the corresponding exceptions.

The example below illustrates how to fetch appointments from a DbContext source.

public class SchedulingDataContext : DbContext {
    public SchedulingDataContext() : base(CreateConnection(), true) { }
    static DbConnection CreateConnection() {
        //...
    }
    public DbSet<AppointmentEntity> AppointmentEntities { get; set; }

    //...
}
void dataSource_FetchAppointments(object sender, DevExpress.Xpf.Scheduling.FetchDataEventArgs e) {
    e.AsyncResult = dbContext.AppointmentEntities
        .Where(x => x.QueryStart <= e.Interval.End
            && x.QueryEnd >= e.Interval.Start)
        .ToArrayAsync<object>();
}

Migrate Existing Projects to Load Data on Demand

You can migrate your project and use data loading on demand if your Scheduler is bound to a database. Perform the following step to implement data loading on demand:

The Scheduler calculates and updates the QueryStart and QueryEnd values at runtime. To initialize the QueryStart and QueryEnd values in your database, load all database records to the Scheduler and then save changes to the data source:

dbContext.AppointmentEntities.Load();
scheduler.DataSource.AppointmentsSource = dbContext.AppointmentEntities.Local;
dbContext.SaveChanges();

Unbound Items

If you do not use DataSource.AppointmentMappings and DataSource.TimeRegionMappings to bind the Scheduler to a data source, load Scheduler items (AppointmentItem and TimeRegionItem instances) instead of data items. To pass Scheduler items to the Result property, set the DataSource.FetchMode property to Unbound.

void FetchAppointments(object sender, FetchDataEventArgs e) {
    e.AsyncResult = MyCalendarService
        .GetEvents(e.Interval.Start, e.Interval.End)
        .ContinueWith(t => t.Result.Select(Convert).ToArray<object>());
}
AppointmentItem Convert(Event x) {
    var appt = new AppointmentItem() { Id = x.Id };
    //..
    return appt;
}

Save Changes

If you handle the DataSource.FetchAppointments event, you need to handle the following events to save the changes to the data source:

You can use the AppointmentCRUDEventArgs class to implement a single event handler for all four events:

<dxsch:SchedulerControl
    AppointmentAdded="ProcessChanges"
    AppointmentEdited="ProcessChanges"
    AppointmentRemoved="ProcessChanges"
    AppointmentRestored="ProcessChanges"/>    
void ProcessChanges(object sender, AppointmentCRUDEventArgs e) {
    db.Appointments.AddRange(e.AddToSource.Select(x => (Appointment)x.SourceObject));
    db.Appointments.RemoveRange(e.DeleteFromSource.Select(x => (Appointment)x.SourceObject));
    db.SaveChanges();
} 

One DbContext per Request

If you use the One DbContext per request approach, initialize the DbContext in the DataSource.FetchAppointments event handler:

void FetchAppointments(object sender, FetchDataEventArgs e) {
    using(var db = new SchedulingContext()) {
        e.AsyncResult = db.Appointments
            .Where(e.GetFetchExpression<Appointment>())
            .ToArrayAsync<object>();
    }
}

In this scenario, use the event’s UpdateInSource property to save the changes to the existing appointments. You also need to search appointments by their Id to update or delete them:

void ProcessChanges(object sender, AppointmentCRUDEventArgs e) {
    using(var db = new SchedulingContext()) {
        db.Appointments.AddRange(e.AddToSource.Select(x => (Appointment)x.SourceObject));
        foreach(var appt in e.UpdateInSource.Select(x => (Appointment)x.SourceObject))
            AppointmentHelper.CopyProperties(appt, db.Appointments.Find(appt.Id));
        foreach(var appt in e.DeleteFromSource.Select(x => (Appointment)x.SourceObject))
            db.Appointments.Remove(db.Appointments.Find(appt.Id));
        db.SaveChanges();
    }
}
public class AppointmentHelper {
    public static void CopyProperties(Appointment source, Appointment target) {
        target.AllDay = source.AllDay;
        target.AppointmentType = source.AppointmentType;
        target.Description = source.Description;
        target.End = source.End;
        target.Label = source.Label;
        target.Location = source.Location;
        target.QueryEnd = source.QueryEnd;
        target.QueryStart = source.QueryStart;
        target.RecurrenceInfo = source.RecurrenceInfo;
        target.ReminderInfo = source.ReminderInfo;
        target.ResourceId = source.ResourceId;
        target.Start = source.Start;
        target.Status = source.Status;
        target.Subject = source.Subject;
    }
}

Reload and Refresh Data

Use the ReloadAppointments/ReloadTimeRegions methods to reload specified appointments and time regions. Use the event’s FetchDataEventArgs.Ids property to obtain the identifiers of the items that need to be updated in the Scheduler. If the fetch event has not been fired by the ReloadAppointments/ReloadTimeRegions methods, the FetchDataEventArgs.Ids property returns null.

The code snippet below illustrates the FetchAppointments event implementation.

scheduler.ReloadAppointment(new[] { id1, id2 });
//...
public void FetchAppointments(FetchDataEventArgs args) {
    if(args.Ids == null)
    // event has been fired by the scheduler's initialization or navigation
        args.AsyncResult = dbContext.AppointmentEntities
            .Where(x => x.QueryStart <= args.Interval.End && x.QueryEnd >= args.Interval.Start)
            .ToArrayAsync<object>();
    else {
    // event has been fired by the ReloadAppointments method
        var ids = args.Ids.OfType<int>().ToArray();
        args.AsyncResult = dbContext.AppointmentEntities
            .Where((x) => ids.Contains(x.Id))
            .ToArrayAsync<object>();
    }
}

The RefreshData() method clears the cached appointments/time regions and fires the DataSource.FetchAppointments and DataSource.FetchTimeRegions events. You can use the CancellationToken property to cancel the data load.

To set up retries after an exception is thrown, you can use the AutoRetryFetchTimeOut and AutoRetryFetchMaxCount properties. When the AutoRetryFetchTimeOut or AutoRetryFetchMaxCount values are exceeded, the Retry window appears.

Limitations

Example

View Example

This example consists of three parts.

  • Shared. The project includes the data and the view that are the same for both examples.
  • CommonDbContext. The project uses a single DbContext for the application and has its own SchedulingViewModel.
  • DbContextPerRequest. This project follows the One DbContext per request approach and has its own SchedulingViewModel.