All docs
V21.1
21.2 (EAP/Beta)
21.1
20.2
20.1
19.2
The page you are viewing does not exist in version 19.2. This link will take you to the root page.
19.1
The page you are viewing does not exist in version 19.1. This link will take you to the root page.
18.2
The page you are viewing does not exist in version 18.2. This link will take you to the root page.
18.1
The page you are viewing does not exist in version 18.1. This link will take you to the root page.
17.2
The page you are viewing does not exist in version 17.2. This link will take you to the root page.

How to: Implement CRUD Operations in a Data-Bound Grid

  • 7 minutes to read

The following example demonstrates how to implement CRUD operations (create, read, update, and delete) in the GridControl bound to a data source:

View Example

The solution in this example includes the following projects:

  • GridControlCRUDSimple - demonstrates a basic non-MVVM technique.
  • GridControlCRUDMVVM - demonstrates the MVVM technique.
  • GridControlCRUDMVVMAsync - demonstrates the MVVM technique with asynchronous operations.
  • GridControlCRUDMVVMInfiniteAsyncSource - demonstrates the MVVM technique with the InfiniteAsyncSource.

The projects follow the One DbContext per request pattern. This pattern has the following advantages:

  • Thread-safety.
  • Complete control over which data fields the client retrieves.
  • Complete control over data source queries.
  • The view model and view can work with any Object-Relational Mapping implementation.

Tip

If you want to implement the One DbContext per form pattern, modify the view model and data layer accordingly.

Basic Non-MVVM Technique

The GridControlCRUDSimple example illustrates a basic non-MVVM technique. The data sources for the grid and the combo box editor are set in the window’s constructor.

Bind Grid to Data

The grid displays two columns, so the SELECT query is configured to only fetch the corresponding data fields.

grid.ItemsSource = context
    .Products
    .Select(product => new ProductInfo {
        Id = product.Id,
        Name = product.Name,
        CategoryId = product.CategoryId
    })
.ToList();

The combo box editor requires only two data fields:

  • Id for item key values.
  • Name for display values.
<dxg:GridColumn FieldName="CategoryId" Header="Category" Width="*">
    <dxg:GridColumn.EditSettings>
        <dxe:ComboBoxEditSettings x:Name="categoriesLookup" ValueMember="Id" DisplayMember="Name"/>
    </dxg:GridColumn.EditSettings>
</dxg:GridColumn>
 categoriesLookup.ItemsSource = context
    .Categories
    .Select(category => new CategoryInfo {
        Id = category.Id,
        Name = category.Name,
    })
.ToList();

Synchronize Changes with Database

The ValidateRow event is handled to add or update data records.

If the view’s FocusedRowHandle property returns the NewItemRowHandle value, a new record is added to the database.

If the user updates properties in an existing data row instead, the Queryable.SingleOrDefault method checks if there is a data record whose Id matches the row’s Id. If no such record is found, an exception is thrown.

The DbContext.SaveChanges method saves changes to the database.

void tableView_ValidateRow(object sender, GridRowValidationEventArgs e) {
    var productInfo = (ProductInfo)e.Row;
    using(var context = new NorthwindContext()) {
        Product product;
        if(view.FocusedRowHandle == DataControlBase.NewItemRowHandle) {
            product = new Product();
            context.Products.Add(product);
        } else {
            product = context.Products.SingleOrDefault(p => p.Id == productInfo.Id);
            if(product == null) {
                throw new NotImplementedException
                ("The modified row no longer exists in the database. Handle this case according to your requirements.");
            }
        }
        product.Name = productInfo.Name;
        product.CategoryId = productInfo.CategoryId;
        context.SaveChanges();
        if(view.FocusedRowHandle == DataControlBase.NewItemRowHandle) {
            productInfo.Id = product.Id;
        }
    }
}

The KeyDown event is handled to implement row deletion. The DbSet.Find method checks if there is a data record whose Id matches the row’s Id value. The GridCommands.DeleteFocusedRow command deletes the row in the grid itself.

void grid_KeyDown(object sender, KeyEventArgs e) {
    if(e.Key == Key.Delete) {
        var productInfo = (ProductInfo)grid.SelectedItem;
        if(productInfo == null)
            return;
        if(DXMessageBox.Show(this, "Are you sure you want to delete this row?", "Delete Row", MessageBoxButton.OKCancel) == MessageBoxResult.Cancel)
            return;
        try {
            using(var context = new NorthwindContext()) {
                var result = context.Products.Find(productInfo.Id);
                if(result == null) {
                    throw new NotImplementedException
                    ("The modified row no longer exists in the database. Handle this case according to your requirements.");
                }
                context.Products.Remove(result);
                context.SaveChanges();
                view.Commands.DeleteFocusedRow.Execute(null);
            }
        } catch(Exception ex) {
            DXMessageBox.Show(ex.Message);
    }
}

MVVM Technique

The GridControlCRUDMVVM example illustrates the MVVM technique.

Model

The Common.DataModel folder contains generic data access interfaces.

public interface IDataProvider<T> where T : class {
    IList<T> Read();
    void Create(T obj);
    void Update(T obj);
    void Delete(T obj);
    TResult GetQueryableResult<TResult>(Func<IQueryable<T>, TResult> getResult); //used for virtual sources
    string KeyProperty { get; }
}

The DesignTimeDataProvider class implements the data access interfaces for use in design time.

The Northwind.DataModel folder contains the fully implemented model for the application.

View Model

The Common.ViewModel folder contains the generic view model - CollectionViewModel.

The CollectionViewModel is not an ObservableCollection. The GridControl handles all data updates and uses commands to pass data to the view model: OnCreateCommand, OnUpdateCommand, and OnDeleteCommand.

If the view model cannot retrieve data from the source, it raises an exception. The exception is handled by the grid, which shows the EntitiesErrorMessage text.

The Northwind.ViewModel folder contains the fully implemented view model for the application - ProductCollectionViewModel. The view model creates different data providers in design time and runtime to prevent the Visual Studio’s designer from fetching data from the database. The NorthwindDataStorageFactory class located in the Northwind.DataModel folder contains the data provider implementations.

Bind View Model Commands to Grid Events

The GridViewBase.ValidateRowCommand property specifies the command that creates and updates rows.

The GridControlDeleteRefreshBehavior binds commands that refresh and delete rows to the TableView events. The GridControlDeleteRefreshBehavior is a custom behavior located in the Common.View folder. You can use this behavior to connect the GridControl to any view model that provides the following commands:

  • OnRefreshCommand
  • OnDeleteCommand
<dxg:GridControl.View>
    <dxg:TableView x:Name="view" 
                   AutoWidth="True" 
                   ShowUpdateRowButtons="OnCellEditorOpen" 
                   NewItemRowPosition="Top" 
                   ValidateRowCommand="{Binding OnUpdateRowCommand}">
        <dxmvvm:Interaction.Behaviors>
            <view:GridControlDeleteRefreshBehavior x:Name="DeleteRefreshBehavior" 
                                                   NoRecordsErrorMessage="{Binding EntitiesErrorMessage}"
                                                   OnDeleteCommand="{Binding OnDeleteCommand}" 
                                                   OnRefreshCommand="{Binding OnRefreshCommand}"/>
        </dxmvvm:Interaction.Behaviors>
    </dxg:TableView>
</dxg:GridControl.View>

The GridControlDeleteRefreshBehavior is also used to bind the toolbar commands to the view model.

<dxb:ToolBarControl>
    <dxb:BarButtonItem Content="Refresh (F5)" Command="{Binding RefreshCommand, ElementName=DeleteRefreshBehavior}" BarItemDisplayMode="ContentAndGlyph" Glyph="{dx:DXImage SvgImages/Icon Builder/Actions_Refresh.svg}"/>
    <dxb:BarButtonItem Content="Delete (Del)" Command="{Binding DeleteCommand, ElementName=DeleteRefreshBehavior}" BarItemDisplayMode="ContentAndGlyph" Glyph="{dx:DXImage SvgImages/Icon Builder/Actions_Delete.svg}"/>
</dxb:ToolBarControl>

MVVM Technique with Asynchronous Operations

The GridControlCRUDMVVMAsync example illustrates the MVVM technique with asynchronous operations.

The project in this example is similar to the GridControlCRUDMVVM project. The example uses the AsyncCollectionViewModel that includes asynchronous CRUD Task operations. These operations are defined in the DataProviderAsyncExtensions class located in the Common.ViewModel folder.

MVVM Technique with InfiniteAsyncSource

The GridControlCRUDMVVMInfiniteAsyncSource example illustrates the MVVM technique with the InfiniteAsyncSource.

Main concepts of the example are listed below:

  • The ViewModel does not contain a specified data source. Instead, the ViewModel includes a command that fetches portions of data, summaries, and unique values used to filter rows.

  • All data operations use the IQueryable interface. This allows you to connect the GridControl to any data source: REST Services, NoSQL Databases, Custom WCF Services, EntityFramework or any other ORM.

    public interface IDataProvider<T> where T : class {
        IList<T> Read();
        void Create(T obj);
        void Update(T obj);
        void Delete(T obj);
        TResult GetQueryableResult<TResult>(Func<IQueryable<T>, TResult> getResult);
        string KeyProperty { get; }
    }
    
  • The example uses the modal edit form to add and edit GridControl rows. The VirtualSourceEditFormBehavior binds the ViewModel commands that create and update rows to the TableView events.