Back to Devexpress

Bind to Entity Framework Sources

wpf-403740-controls-and-libraries-data-grid-bind-to-data-bind-to-entity-framework-sources.md

latest24.4 KB
Original Source

Bind to Entity Framework Sources

  • Oct 06, 2022
  • 11 minutes to read

Entity Framework is an ORM library that helps you access and process data stored in-memory or within traditional database engines. This article describes how to use Entity Framework with the DevExpress WPF Data Grid.

Main Example

This example includes multiple solutions that demonstrate:

  • Different binding mechanisms: virtual sources, server mode sources, and local data.
  • MVVM and code-behind patterns.

View Example: Bind the WPF Data Grid to Data

The example uses the Issues Service database that stores data for two entities: users and issues assigned to these users.

Each Entity Framework project includes the common Issues folder. Files in this folder define the Entity Framework-related logic:

OutlookDataGenerator.cs Generates data.IssueContext.cs Defines the DbContext.IssueContextInitializer.cs Initializes the database with generated data.Issue.cs and User.cs Define entities.

OutlookDataGenerator.cs / OutlookDataGenerator.vb

csharp
using System;

namespace EntityFrameworkIssues.Issues {
   public static class OutlookDataGenerator {
       static Random rnd = new Random(0);
       static string[] Subjects = new string[] { "Developer Express MasterView. Integrating the control into an Accounting System.",
                                               "Web Edition: Data Entry Page. There is an issue with date validation.",
                                               "Payables Due Calculator is ready for testing.",
                                               "Web Edition: Search Page is ready for testing.",
                                               "Main Menu: Duplicate Items. Somebody has to review all menu items in the system.",
                                               "Receivables Calculator. Where can I find the complete specs?",
                                               "Ledger: Inconsistency. Please fix it.",
                                               "Receivables Printing module is ready for testing.",
                                               "Screen Redraw. Somebody has to look at it.",
                                               "Email System. What library are we going to use?",
                                               "Cannot add new vendor. This module doesn't work!",
                                               "History. Will we track sales history in our system?",
                                               "Main Menu: Add a File menu. File menu item is missing.",
                                               "Currency Mask. The current currency mask in completely unusable.",
                                               "Drag & Drop operations are not available in the scheduler module.",
                                               "Data Import. What database types will we support?",
                                               "Reports. The list of incomplete reports.",
                                               "Data Archiving. We still don't have this features in our application.",
                                               "Email Attachments. Is it possible to add multiple attachments? I haven't found a way to do this.",
                                               "Check Register. We are using different paths for different modules.",
                                               "Data Export. Our customers asked us for export to Microsoft Excel"};

       public static readonly string[] Users = new string[] {
           "Peter Dolan",
           "Ryan Fischer",
           "Richard Fisher",
           "Tom Hamlett",
           "Mark Hamilton",
           "Steve Lee",
           "Jimmy Lewis",
           "Jeffrey McClain",
           "Andrew Miller",
           "Dave Murrel",
           "Bert Parkins",
           "Mike Roller",
           "Ray Shipman",
           "Paul Bailey",
           "Brad Barnes",
           "Carl Lucas",
           "Jerry Campbell",
       };

       public static string GetSubject() {
           return Subjects[rnd.Next(Subjects.Length - 1)];
       }

       public static string GetFrom() {
           return Users[rnd.Next(Users.Length)];
       }
       public static Priority GetPriority() {
           return (Priority)rnd.Next(5);
       }
   }
}
vb
Imports System

Namespace Issues
   Public Module OutlookDataGenerator
       Private rnd As Random = New Random(0)
       Private Subjects As String() = New String() {"Developer Express MasterView. Integrating the control into an Accounting System.", "Web Edition: Data Entry Page. There is an issue with date validation.", "Payables Due Calculator is ready for testing.", "Web Edition: Search Page is ready for testing.", "Main Menu: Duplicate Items. Somebody has to review all menu items in the system.", "Receivables Calculator. Where can I find the complete specs?", "Ledger: Inconsistency. Please fix it.", "Receivables Printing module is ready for testing.", "Screen Redraw. Somebody has to look at it.", "Email System. What library are we going to use?", "Cannot add new vendor. This module doesn't work!", "History. Will we track sales history in our system?", "Main Menu: Add a File menu. File menu item is missing.", "Currency Mask. The current currency mask in completely unusable.", "Drag & Drop operations are not available in the scheduler module.", "Data Import. What database types will we support?", "Reports. The list of incomplete reports.", "Data Archiving. We still don't have this features in our application.", "Email Attachments. Is it possible to add multiple attachments? I haven't found a way to do this.", "Check Register. We are using different paths for different modules.", "Data Export. Our customers asked us for export to Microsoft Excel"}
       Public ReadOnly Users As String() = New String() {"Peter Dolan", "Ryan Fischer", "Richard Fisher", "Tom Hamlett", "Mark Hamilton", "Steve Lee", "Jimmy Lewis", "Jeffrey McClain", "Andrew Miller", "Dave Murrel", "Bert Parkins", "Mike Roller", "Ray Shipman", "Paul Bailey", "Brad Barnes", "Carl Lucas", "Jerry Campbell"}

       Public Function GetSubject() As String
           Return Subjects(rnd.Next(Subjects.Length - 1))
       End Function

       Public Function GetFrom() As String
           Return Users(rnd.Next(Users.Length))
       End Function

       Public Function GetPriority() As Priority
           Return CType(rnd.Next(5), Priority)
       End Function
   End Module
End Namespace

IssuesContext.cs / IssuesContext.vb

csharp
using System.Data.Entity;

namespace EntityFrameworkIssues.Issues {
   public class IssuesContext : DbContext {
       static IssuesContext() {
           Database.SetInitializer(new IssuesContextInitializer());
       }
       public IssuesContext() { }

       protected override void OnModelCreating(DbModelBuilder modelBuilder) {
           base.OnModelCreating(modelBuilder);

           modelBuilder.Entity<Issue>()
               .HasIndex(x => x.Created);

           modelBuilder.Entity<Issue>()
               .HasIndex(x => x.Votes);
       }

       public DbSet<Issue> Issues { get; set; }
       public DbSet<User> Users { get; set; }
   }
}
vb
Imports System.Data.Entity

Namespace Issues
   Public Class IssuesContext
       Inherits DbContext

       Shared Sub New()
           Database.SetInitializer(New IssuesContextInitializer())
       End Sub

       Public Sub New()
       End Sub

       Protected Overrides Sub OnModelCreating(ByVal modelBuilder As DbModelBuilder)
           MyBase.OnModelCreating(modelBuilder)
           modelBuilder.Entity(Of Issue)().HasIndex(Function(x) x.Created)
           modelBuilder.Entity(Of Issue)().HasIndex(Function(x) x.Votes)
       End Sub

       Public Property Issues As DbSet(Of Issue)
       Public Property Users As DbSet(Of User)
   End Class
End Namespace

IssuesContextInitializer.cs / IssuesContextInitializer.vb

csharp
using System;
using System.Data.Entity;
using System.Linq;

namespace EntityFrameworkIssues.Issues {
   public class IssuesContextInitializer
       : DropCreateDatabaseIfModelChanges<IssuesContext> {
       //: DropCreateDatabaseAlways<IssuesContext> { 

       public static void ResetData() {
           using(var context = new IssuesContext()) {
               context.Users.Load();
               context.Users.RemoveRange(context.Users);
               context.SaveChanges();
               CreateData(context);
           }
       }

       protected override void Seed(IssuesContext context) {
           base.Seed(context);
           CreateData(context);
       }

       static void CreateData(IssuesContext context) {
           var users = OutlookDataGenerator.Users
               .Select(x =>
               {
                   var split = x.Split(' ');
                   return new User()
                   {
                       FirstName = split[0],
                       LastName = split[1]
                   };
               })
               .ToArray();
           context.Users.AddRange(users);
           context.SaveChanges();

           var rnd = new Random(0);
           var issues = Enumerable.Range(0, 1000)
               .Select(i => new Issue()
               {
                   Subject = OutlookDataGenerator.GetSubject(),
                   UserId = users[rnd.Next(users.Length)].Id,
                   Created = DateTime.Today.AddDays(-rnd.Next(30)),
                   Priority = OutlookDataGenerator.GetPriority(),
                   Votes = rnd.Next(100),
               })
               .ToArray();
           context.Issues.AddRange(issues);

           context.SaveChanges();
       }
   }
}
vb
Imports System
Imports System.Data.Entity
Imports System.Linq

Namespace Issues
   Public Class IssuesContextInitializer
       Inherits DropCreateDatabaseIfModelChanges(Of IssuesContext)

       ': DropCreateDatabaseAlways<IssuesContext> { 

       Public Shared Sub ResetData()
           Using context = New IssuesContext()
               context.Users.Load()
               context.Users.RemoveRange(context.Users)
               context.SaveChanges()
               CreateData(context)
           End Using
       End Sub

       Protected Overrides Sub Seed(ByVal context As IssuesContext)
           MyBase.Seed(context)
           CreateData(context)
       End Sub

       Private Shared Sub CreateData(ByVal context As IssuesContext)
           Dim users = OutlookDataGenerator.Users.[Select](Function(x)
                                                               Dim split = x.Split(" "c)
                                                               Return New User() With {
                                                                   .FirstName = split(0),
                                                                   .LastName = split(1)
                                                               }
                                                           End Function).ToArray()
           context.Users.AddRange(users)
           context.SaveChanges()
           Dim rnd = New Random(0)
           Dim issues = Enumerable.Range(0, 1000).[Select](Function(i) New Issue() With {
               .Subject = OutlookDataGenerator.GetSubject(),
               .UserId = users(rnd.Next(users.Length)).Id,
               .Created = Date.Today.AddDays(-rnd.Next(30)),
               .Priority = OutlookDataGenerator.GetPriority(),
               .Votes = rnd.Next(100)
           }).ToArray()
           context.Issues.AddRange(issues)
           context.SaveChanges()
       End Sub
   End Class
End Namespace

Issue.cs / Issue.vb

csharp
using System;

namespace EntityFrameworkIssues.Issues {
   public class Issue {
       public int Id { get; set; }
       public string Subject { get; set; }
       public int UserId { get; set; }
       public virtual User User { get; set; }
       public DateTime Created { get; set; }
       public int Votes { get; set; }
       public Priority Priority { get; set; }
       public Issue() {
           Created = DateTime.Now;
       }
   }
   public enum Priority { Low, BelowNormal, Normal, AboveNormal, High }
}
vb
Namespace Issues
   Public Class Issue
       Public Property Id As Integer
       Public Property Subject As String
       Public Property UserId As Integer
       Public Overridable Property User As User
       Public Property Created As Date
       Public Property Votes As Integer
       Public Property Priority As Priority

       Public Sub New()
           Created = Date.Now
       End Sub
   End Class

   Public Enum Priority
       Low
       BelowNormal
       Normal
       AboveNormal
       High
   End Enum
End Namespace

User.cs / User.vb

csharp
```csharp
using System.Collections.Generic;

namespace EntityFrameworkIssues.Issues {
   public class User {
       public int Id { get; set; }
       public string FirstName { get; set; }
       public string LastName { get; set; }
       public virtual ICollection<Issue> Issues { get; set; }
   }
}
vb
Imports System.Collections.Generic

Namespace Issues
   Public Class User
       Public Property Id As Integer
       Public Property FirstName As String
       Public Property LastName As String
       Public Overridable Property Issues As ICollection(Of Issue)
   End Class
End Namespace

Local Data

To bind in-memory data to the GridControl, convert the entity set to List<T> and assign this list to the ItemsSource property as follows:

cs
using System.Windows;
using EntityFrameworkIssues.Issues;
using System.Linq;

namespace EntityFrameworkIssues {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            LoadData();
        }
        IssuesContext _Context;

        void LoadData() {
            _Context = new IssuesContext();
            grid.ItemsSource = _Context.Users.ToList();
        }
    }
}
vb
Imports EntityFrameworkIssues.Issues
Imports System.Linq
Class MainWindow
    Public Sub New()
        InitializeComponent()
        LoadData()
    End Sub
    Private _Context As IssuesContext

    Private Sub LoadData()
        _Context = New IssuesContext()
        grid.ItemsSource = _Context.Users.ToList()
    End Sub

End Class

Server Mode

In Server Mode, the GridControl loads data in small portions on demand. To activate this mode, use EntityServerModeSource as a data source. Initialize the data source and bind it to the GridControl as follows:

cs
using System.Windows;
using EntityFrameworkIssues.Issues;
using DevExpress.Data.Linq;
using System.Data.Entity;
using System.Linq;
using DevExpress.Xpf.Grid;

namespace EntityFrameworkIssues {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            var context = new IssuesContext();
            var source = new EntityServerModeSource {
                KeyExpression = nameof(Issue.Id),
                QueryableSource = context.Issues.AsNoTracking()
            };
            grid.ItemsSource = source;
            LoadLookupData();
        }

        void LoadLookupData() {
            var context = new IssuesContext();
            usersLookup.ItemsSource = context.Users.Select(user => new { Id = user.Id, Name = user.FirstName + " " + user.LastName }).ToArray();
        }
    }
}
vb
Imports EntityFrameworkIssues.Issues
Imports DevExpress.Data.Linq
Imports System.Data.Entity
Imports System.Linq
Imports DevExpress.Xpf.Grid
Class MainWindow
    Public Sub New()
        InitializeComponent()
        Dim context = New IssuesContext()
        Dim source = New EntityServerModeSource With {
            .KeyExpression = NameOf(Issue.Id),
            .QueryableSource = context.Issues.AsNoTracking()
        }
        grid.ItemsSource = source
        LoadLookupData()
    End Sub

    Private Sub LoadLookupData()
        Dim context = New IssuesContext()
        usersLookup.ItemsSource = context.Users.[Select](Function(user) New With {
            .Id = user.Id,
            .Name = user.FirstName & " " + user.LastName
        }).ToArray()
    End Sub

End Class

Instant Feedback

In Instant Feedback Mode, the GridControl loads data in a background thread. To activate this mode, use EntityInstantFeedbackSource as a data source. Initialize the data source and bind it to the GridControl as follows:

cs
using System.Windows;
using EntityFrameworkIssues.Issues;
using DevExpress.Data.Linq;
using System.Data.Entity;
using System.Linq;
using DevExpress.Xpf.Grid;

namespace EntityFrameworkIssues {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            var source = new EntityInstantFeedbackSource {
                KeyExpression = nameof(Issue.Id)
            };
            source.GetQueryable += (sender, e) => {
                var context = new IssuesContext();
                e.QueryableSource = context.Issues.AsNoTracking();
            };
            grid.ItemsSource = source;
            LoadLookupData();
        }

        void LoadLookupData() {
            var context = new IssuesContext();
            usersLookup.ItemsSource = context.Users.Select(user => new { Id = user.Id, Name = user.FirstName + " " + user.LastName }).ToArray();
        }
    }
}
vb
Imports EntityFrameworkIssues.Issues
Imports DevExpress.Data.Linq
Imports System.Data.Entity
Imports System.Linq
Imports DevExpress.Xpf.Grid
Class MainWindow
    Public Sub New()
        InitializeComponent()
        Dim source = New EntityInstantFeedbackSource With {
            .KeyExpression = NameOf(Issue.Id)
        }
        AddHandler source.GetQueryable, Sub(sender, e)
                                            Dim context = New IssuesContext()
                                            e.QueryableSource = context.Issues.AsNoTracking()
                                        End Sub
        grid.ItemsSource = source
        LoadLookupData()
    End Sub

    Private Sub LoadLookupData()
        Dim context = New IssuesContext()
        usersLookup.ItemsSource = context.Users.[Select](Function(user) New With {
            .Id = user.Id,
            .Name = user.FirstName & " " + user.LastName
        }).ToArray()
    End Sub

End Class

Virtual Sources

Virtual Sources allow you to bind the GridControl to any data source, even if the total record count is unknown.

If you want to display data with infinite scrolling, use InfiniteAsyncSource as a data source. Initialize the data source and bind it to the GridControl as follows:

cs
namespace EntityFrameworkIssues {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            var source = new InfiniteAsyncSource {
                ElementType = typeof(Issue),
                KeyProperty = nameof(Issue.Id)
            };
            source.FetchRows += OnFetchRows;
            source.GetTotalSummaries += OnGetTotalSummaries;
            grid.ItemsSource = source;
            LoadLookupData();
        }

// ...
    }
    // ...
}
vb
Class MainWindow
    Public Sub New()
        InitializeComponent()
        Dim source = New InfiniteAsyncSource With {
            .ElementType = GetType(Issue),
            .KeyProperty = NameOf(Issue.Id)
        }
        AddHandler source.FetchRows, AddressOf OnFetchRows
        AddHandler source.GetTotalSummaries, AddressOf OnGetTotalSummaries
        grid.ItemsSource = source
        LoadLookupData()
    End Sub
    ' ...
End Class

You can also use PagedAsyncSource that displays data in pages:

cs
namespace EntityFrameworkIssues {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            var source = new PagedAsyncSource {
                ElementType = typeof(Issue),
                KeyProperty = nameof(Issue.Id),
                PageNavigationMode = PageNavigationMode.ArbitraryWithTotalPageCount
            };
            source.FetchPage += OnFetchPage;
            source.GetTotalSummaries += OnGetTotalSummaries;
            grid.ItemsSource = source;
            LoadLookupData();
        }

// ...
    }
}
vb
Class MainWindow
    Public Sub New()
        InitializeComponent()
        Dim source = New PagedAsyncSource With {
            .ElementType = GetType(Issue),
            .KeyProperty = NameOf(Issue.Id),
            .PageNavigationMode = PageNavigationMode.ArbitraryWithTotalPageCount
        }
        AddHandler source.FetchPage, AddressOf OnFetchPage
        AddHandler source.GetTotalSummaries, AddressOf OnGetTotalSummaries
        grid.ItemsSource = source
        LoadLookupData()
    End Sub
    ' ...
End Class

To fetch new data from the database, implement the FetchRows event handler as follows:

cs
public partial class MainWindow : Window {
// ...
    void OnFetchRows(object sender, FetchRowsAsyncEventArgs e) {
        e.Result = Task.Run<FetchRowsResult>(() => {
            var context = new IssuesContext();
            var queryable = context.Issues.AsNoTracking()
                .SortBy(e.SortOrder, defaultUniqueSortPropertyName: nameof(Issue.Id))
                .Where(MakeFilterExpression(e.Filter));
            return queryable.Skip(e.Skip).Take(e.Take ?? 100).ToArray();
        });
    }
    // ...
}
vb
Class MainWindow
' ...
    Private Sub OnFetchRows(ByVal sender As Object, ByVal e As FetchRowsAsyncEventArgs)
        e.Result = Task.Run(Of FetchRowsResult)(Function()
            Dim context = New IssuesContext()
            Dim queryable = context.Issues.AsNoTracking().SortBy(e.SortOrder, defaultUniqueSortPropertyName:=NameOf(Issue.Id)).Where(MakeFilterExpression(e.Filter))
            Return queryable.Skip(e.Skip).Take(If(e.Take, 100)).ToArray()
        End Function)
    End Sub
    ' ...
End Class

Next Steps

After you bind the Data Grid to a database, you can implement CRUD operations (create, read update, delete). Refer to the following topic for more information: CRUD Operations in a Data-Bound Grid.

View Example: Implement CRUD Operations in the WPF Data Grid

See Also

Bind the WPF Data Grid to Entity Framework Core Sources