Back to Devexpress

Bind to XPO Data Sources

wpf-6091-controls-and-libraries-data-grid-bind-to-data-server-mode-and-instant-feedback-bind-to-xpo-data-sources.md

latest33.3 KB
Original Source

Bind to XPO Data Sources

  • Feb 14, 2023
  • 15 minutes to read

XPO is the DevExpress ORM library that helps you access and process data stored in-memory or within traditional database engines. This article describes how to use XPO 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 XPO project includes the common Issues folder. Files in this folder define the XPO-related logic:

OutlookDataGenerator.cs Generates data.ConnectionHelper.cs Connects to a database.DemoDataHelper.cs Loads generated data to the data source.Issues.cs and Customer.cs Define entities.

OutlookDataGenerator.cs / OutlookDataGenerator.vb

csharp
using System;

namespace XPOIssues.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

ConnectionHelper.cs / ConnectionHelper.vb

csharp
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using DevExpress.Xpo.Metadata;
using System;
using System.Configuration;

namespace XPOIssues.Issues {
   public static class ConnectionHelper {

       static readonly Type[] PersistentTypes = new Type[]{
           typeof(Issue),
           typeof(User)
       };

       public static void Connect() {
           XpoDefault.DataLayer = CreateDataLayer(true);
       }

       static IDataLayer CreateDataLayer(bool threadSafe) {
           string connStr = ConfigurationManager.ConnectionStrings["XpoTutorial"]?.ConnectionString ?? "XpoProvider=InMemoryDataStore";
           // Uncomment this line if you use a database server like SQL Server, Oracle, PostgreSql etc.
           //connStr = XpoDefault.GetConnectionPoolString(connStr);
           ReflectionDictionary dictionary = new ReflectionDictionary();
           // Pass all of your persistent object types to this method.
           dictionary.GetDataStoreSchema(PersistentTypes);
           // Use AutoCreateOption.DatabaseAndSchema if the database or tables do not exist.
           // Use AutoCreateOption.SchemaAlreadyExists if the database already exists.
           AutoCreateOption autoCreateOption = AutoCreateOption.DatabaseAndSchema;
           IDataStore provider = XpoDefault.GetConnectionProvider(connStr, autoCreateOption);
           return threadSafe ? (IDataLayer)new ThreadSafeDataLayer(dictionary, provider) : new SimpleDataLayer(dictionary, provider);
       }
   }
}
vb
Imports DevExpress.Xpo
Imports DevExpress.Xpo.DB
Imports DevExpress.Xpo.Metadata
Imports System
Imports System.Configuration

Namespace Issues
   Public Module ConnectionHelper
       Private ReadOnly PersistentTypes As Type() = New Type() {GetType(Issue), GetType(User)}

       Public Sub Connect()
           XpoDefault.DataLayer = CreateDataLayer(True)
       End Sub

       Private Function CreateDataLayer(ByVal threadSafe As Boolean) As IDataLayer
           Dim connStr As String = If(ConfigurationManager.ConnectionStrings("XpoTutorial")?.ConnectionString, "XpoProvider=InMemoryDataStore")
           ' Uncomment this line if you use a database server like SQL Server, Oracle, PostgreSql etc.
           'connStr = XpoDefault.GetConnectionPoolString(connStr);
           Dim dictionary As ReflectionDictionary = New ReflectionDictionary()
           ' Pass all of your persistent object types to this method.
           dictionary.GetDataStoreSchema(PersistentTypes)
           ' Use AutoCreateOption.DatabaseAndSchema if the database or tables do not exist.
           ' Use AutoCreateOption.SchemaAlreadyExists if the database already exists.
           Dim autoCreateOption As AutoCreateOption = AutoCreateOption.DatabaseAndSchema
           Dim provider As IDataStore = XpoDefault.GetConnectionProvider(connStr, autoCreateOption)
           Return If(threadSafe, CType(New ThreadSafeDataLayer(dictionary, provider), IDataLayer), New SimpleDataLayer(dictionary, provider))
       End Function
   End Module
End Namespace

DemoDataHelper.cs / DemoDataHelper.vb

csharp
using DevExpress.Xpo;
using System;
using System.Collections.Generic;
using System.Linq;

namespace XPOIssues.Issues {
   public static class DemoDataHelper {
       public static void Seed() {
           using(var uow = new DevExpress.Xpo.UnitOfWork()) {
               var users = OutlookDataGenerator.Users
                   .Select(x =>
                   {
                       var split = x.Split(' ');
                       return new User(uow)
                       {
                           FirstName = split[0],
                           LastName = split[1]
                       };
                   })
                   .ToArray();
               uow.CommitChanges();
               var rnd = new Random(0);
               var issues = Enumerable.Range(0, 1000)
                   .Select(i => new Issue(uow)
                   {
                       Subject = OutlookDataGenerator.GetSubject(),
                       UserId = users[rnd.Next(users.Length)].Oid,
                       Created = DateTime.Today.AddDays(-rnd.Next(30)),
                       Priority = OutlookDataGenerator.GetPriority(),
                       Votes = rnd.Next(100),
                   })
                   .ToArray();
               uow.CommitChanges();
           }
       }
   }
}
vb
Imports DevExpress.Xpo
Imports System
Imports System.Linq

Namespace Issues
   Public Module DemoDataHelper
       Public Sub Seed()
           Using uow = New DevExpress.Xpo.UnitOfWork()
               Dim users = OutlookDataGenerator.Users.[Select](Function(x)
                                                                   Dim split = x.Split(" "c)
                                                                   Return New User(uow) With {
                                                                       .FirstName = split(0),
                                                                       .LastName = split(1)
                                                                   }
                                                               End Function).ToArray()
               uow.CommitChanges()
               Dim rnd = New Random(0)
               Dim issues = Enumerable.Range(0, 1000).[Select](Function(i) New Issue(uow) With {
                   .Subject = OutlookDataGenerator.GetSubject(),
                   .UserId = users(rnd.Next(users.Length)).Oid,
                   .Created = Date.Today.AddDays(-rnd.Next(30)),
                   .Priority = OutlookDataGenerator.GetPriority(),
                   .Votes = rnd.Next(100)
               }).ToArray()
               uow.CommitChanges()
           End Using
       End Sub
   End Module
End Namespace

Issue.cs / Issue.vb

csharp
using System;
using DevExpress.Xpo;

namespace XPOIssues.Issues {
   public class Issue : XPObject {
       public Issue(Session session) : base(session) {
           Created = DateTime.Now;
       }
       string _Subject;
       [Size(200)]
       public string Subject
       {
           get { return _Subject; }
           set { SetPropertyValue(nameof(Subject), ref _Subject, value); }
       }

       int _UserId;
       public int UserId
       {
           get { return _UserId; }
           set { SetPropertyValue(nameof(UserId), ref _UserId, value); }
       }

       User _User;
       [Association("UserIssues")]
       public User User
       {
           get { return _User; }
           set { SetPropertyValue(nameof(User), ref _User, value); }
       }

       DateTime _Created;
       public DateTime Created
       {
           get { return _Created; }
           set { SetPropertyValue(nameof(Created), ref _Created, value); }
       }

       int _Votes;
       public int Votes
       {
           get { return _Votes; }
           set { SetPropertyValue(nameof(Votes), ref _Votes, value); }
       }

       Priority _Priority;
       public Priority Priority
       {
           get { return _Priority; }
           set { SetPropertyValue(nameof(Priority), ref _Priority, value); }
       }
   }

   public enum Priority { Low, BelowNormal, Normal, AboveNormal, High }
}
vb
Imports DevExpress.Xpo

Namespace Issues
   Public Class Issue
       Inherits XPObject

       Public Sub New(ByVal session As Session)
           MyBase.New(session)
           Created = Date.Now
       End Sub

       Private _Subject As String

       <Size(200)>
       Public Property Subject As String
           Get
               Return _Subject
           End Get
           Set(ByVal value As String)
               SetPropertyValue(NameOf(Issue.Subject), _Subject, value)
           End Set
       End Property

       Private _UserId As Integer

       Public Property UserId As Integer
           Get
               Return _UserId
           End Get
           Set(ByVal value As Integer)
               SetPropertyValue(NameOf(Issue.UserId), _UserId, value)
           End Set
       End Property

       Private _User As User

       <Association("UserIssues")>
       Public Property User As User
           Get
               Return _User
           End Get
           Set(ByVal value As User)
               SetPropertyValue(NameOf(Issue.User), _User, value)
           End Set
       End Property

       Private _Created As Date

       Public Property Created As Date
           Get
               Return _Created
           End Get
           Set(ByVal value As Date)
               SetPropertyValue(NameOf(Issue.Created), _Created, value)
           End Set
       End Property

       Private _Votes As Integer

       Public Property Votes As Integer
           Get
               Return _Votes
           End Get
           Set(ByVal value As Integer)
               SetPropertyValue(NameOf(Issue.Votes), _Votes, value)
           End Set
       End Property

       Private _Priority As Priority

       Public Property Priority As Priority
           Get
               Return _Priority
           End Get
           Set(ByVal value As Priority)
               SetPropertyValue(NameOf(Issue.Priority), _Priority, value)
           End Set
       End Property
   End Class

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

Customer.cs / Customer.vb

csharp
using DevExpress.Xpo;

namespace XPOIssues.Issues {
   public class User : XPObject {
       public User(Session session) : base(session) { }

       string _FirstName;
       public string FirstName
       {
           get { return _FirstName; }
           set { SetPropertyValue(nameof(FirstName), ref _FirstName, value); }
       }

       string _LastName;
       public string LastName
       {
           get { return _LastName; }
           set { SetPropertyValue(nameof(LastName), ref _LastName, value); }
       }

       [Association("UserIssues")]
       public XPCollection<Issue> Issues
       {
           get { return GetCollection<Issue>(nameof(Issues)); }
       }
   }
}
vb
Imports DevExpress.Xpo

Namespace Issues
   Public Class User
       Inherits XPObject

       Public Sub New(ByVal session As Session)
           MyBase.New(session)
       End Sub

       Private _FirstName As String

       Public Property FirstName As String
           Get
               Return _FirstName
           End Get
           Set(ByVal value As String)
               SetPropertyValue(NameOf(User.FirstName), _FirstName, value)
           End Set
       End Property

       Private _LastName As String

       Public Property LastName As String
           Get
               Return _LastName
           End Get
           Set(ByVal value As String)
               SetPropertyValue(NameOf(User.LastName), _LastName, value)
           End Set
       End Property

       <Association("UserIssues")>
       Public ReadOnly Property Issues As XPCollection(Of Issue)
           Get
               Return GetCollection(Of Issue)(NameOf(User.Issues))
           End Get
       End Property
   End Class
End Namespace

Local Data

If you store data in a local database, you can use XPCollection as a data source and bind it to the GridControl.

cs
using System.Windows;
using XPOIssues.Issues;
using DevExpress.Xpo;
using System.Linq;

namespace XPOIssues {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            LoadData();
        }
        UnitOfWork _UnitOfWork;

        void LoadData() {
            _UnitOfWork = new UnitOfWork();
            var xpCollection = new XPCollection<User>(_UnitOfWork);
            xpCollection.Sorting.Add(new SortProperty(nameof(User.Oid), DevExpress.Xpo.DB.SortingDirection.Ascending));
            grid.ItemsSource = xpCollection;
        }
    }
}
vb
Imports XPOIssues.Issues
Imports DevExpress.Xpo
Imports System.Linq
Class MainWindow
    Public Sub New()
        InitializeComponent()
        LoadData()
    End Sub
    Private _UnitOfWork As UnitOfWork

    Private Sub LoadData()
        _UnitOfWork = New UnitOfWork()
        Dim xpCollection = New XPCollection(Of User)(_UnitOfWork)
        xpCollection.Sorting.Add(New SortProperty(NameOf(User.Oid), DevExpress.Xpo.DB.SortingDirection.Ascending))
        grid.ItemsSource = xpCollection
    End Sub

End Class

Server Mode

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

cs
using System.Windows;
using XPOIssues.Issues;
using DevExpress.Data.Filtering;
using DevExpress.Xpo;
using System.Linq;
using DevExpress.Xpf.Grid;

namespace XPOIssues {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            var properties = new ServerViewProperty[] {
                new ServerViewProperty("Subject", SortDirection.None, new OperandProperty("Subject")),
                new ServerViewProperty("UserId", SortDirection.None, new OperandProperty("UserId")),
                new ServerViewProperty("Created", SortDirection.None, new OperandProperty("Created")),
                new ServerViewProperty("Votes", SortDirection.None, new OperandProperty("Votes")),
                new ServerViewProperty("Priority", SortDirection.None, new OperandProperty("Priority")),
                new ServerViewProperty("Oid", SortDirection.Ascending, new OperandProperty("Oid"))
                };
            var session = new Session();
            var source = new XPServerModeView(session, typeof(Issue), null);
            source.Properties.AddRange(properties);
            grid.ItemsSource = source;
            LoadLookupData();
        }

        void LoadLookupData() {
            var session = new Session();
            usersLookup.ItemsSource = session.Query<User>().OrderBy(user => user.Oid).Select(user => new { Id = user.Oid, Name = user.FirstName + " " + user.LastName }).ToArray();
        }
    }
}
vb
Imports XPOIssues.Issues
Imports DevExpress.Data.Filtering
Imports DevExpress.Xpo
Imports System.Linq
Imports DevExpress.Xpf.Grid
Class MainWindow
    Public Sub New()
        InitializeComponent()
        Dim properties = New ServerViewProperty() {
            New ServerViewProperty("Subject", SortDirection.None, New OperandProperty("Subject")),
            New ServerViewProperty("UserId", SortDirection.None, New OperandProperty("UserId")),
            New ServerViewProperty("Created", SortDirection.None, New OperandProperty("Created")),
            New ServerViewProperty("Votes", SortDirection.None, New OperandProperty("Votes")),
            New ServerViewProperty("Priority", SortDirection.None, New OperandProperty("Priority")),
            New ServerViewProperty("Oid", SortDirection.Ascending, New OperandProperty("Oid"))
        }
        Dim session = New Session()
        Dim source = New XPServerModeView(session, GetType(Issue), Nothing)
        source.Properties.AddRange(properties)
        grid.ItemsSource = source
        LoadLookupData()
    End Sub

    Private Sub LoadLookupData()
        Dim session = New Session()
        usersLookup.ItemsSource = session.Query(Of User).OrderBy(Function(user) user.Oid).[Select](Function(user) New With {
            .Id = user.Oid,
            .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, you can use DevExpress.Xpo.XPInstantFeedbackView as a data source. Initialize the data source and bind it to the GridControl as follows:

cs
using System.Windows;
using XPOIssues.Issues;
using DevExpress.Data.Filtering;
using DevExpress.Xpo;
using System.Linq;
using DevExpress.Xpf.Grid;

namespace XPOIssues {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            var properties = new ServerViewProperty[] {
                new ServerViewProperty("Subject", SortDirection.None, new OperandProperty("Subject")),
                new ServerViewProperty("UserId", SortDirection.None, new OperandProperty("UserId")),
                new ServerViewProperty("Created", SortDirection.None, new OperandProperty("Created")),
                new ServerViewProperty("Votes", SortDirection.None, new OperandProperty("Votes")),
                new ServerViewProperty("Priority", SortDirection.None, new OperandProperty("Priority")),
                new ServerViewProperty("Oid", SortDirection.Ascending, new OperandProperty("Oid"))
                };
            var source = new XPInstantFeedbackView(typeof(Issue), properties, null);
            source.ResolveSession += (o, e) => {
                e.Session = new Session();
            };
            grid.ItemsSource = source;
            LoadLookupData();
        }

        void LoadLookupData() {
            var session = new Session();
            usersLookup.ItemsSource = session.Query<User>().OrderBy(user => user.Oid).Select(user => new { Id = user.Oid, Name = user.FirstName + " " + user.LastName }).ToArray();
        }
    }
}
vb
Imports XPOIssues.Issues
Imports DevExpress.Data.Filtering
Imports DevExpress.Xpo
Imports System.Linq
Imports DevExpress.Xpf.Grid
Class MainWindow
    Public Sub New()
        InitializeComponent()
        Dim properties = New ServerViewProperty() {
            New ServerViewProperty("Subject", SortDirection.None, New OperandProperty("Subject")),
            New ServerViewProperty("UserId", SortDirection.None, New OperandProperty("UserId")),
            New ServerViewProperty("Created", SortDirection.None, New OperandProperty("Created")),
            New ServerViewProperty("Votes", SortDirection.None, New OperandProperty("Votes")),
            New ServerViewProperty("Priority", SortDirection.None, New OperandProperty("Priority")),
            New ServerViewProperty("Oid", SortDirection.Ascending, New OperandProperty("Oid"))
        }
        Dim source = New XPInstantFeedbackView(GetType(Issue), properties, Nothing)
        AddHandler source.ResolveSession, Sub(o, e) e.Session = New Session()
        grid.ItemsSource = source
        LoadLookupData()
    End Sub

    Private Sub LoadLookupData()
        Dim session = New Session()
        usersLookup.ItemsSource = session.Query(Of User).OrderBy(Function(user) user.Oid).[Select](Function(user) New With {
            .Id = user.Oid,
            .Name = user.FirstName & " " + user.LastName
        }).ToArray()
    End Sub

End Class

Virtual Sources

Virtual Sources create a Session on each request. If the Session is disposed of, all Persistent Objects that it contains are also disposed of. As a result, you cannot retrieve the objects’ properties. Use the DetachedObjectsHelper class to store these properties and access them from the GridControl anytime.

The following code sample uses the GetClassInfo method to get Issue class’s metadata and saves it to the DetachedObjectsHelper instance.

cs
namespace XPOIssues {
    public partial class MainWindow : Window {
        public MainWindow() {
        // ...
            using(var session = new Session()) {
                var classInfo = session.GetClassInfo<Issue>();
                var properties = classInfo.Members
                    .Where(member => member.IsPublic && member.IsPersistent)
                    .Select(member => member.Name)
                    .ToArray();
                _DetachedObjectsHelper = DetachedObjectsHelper<Issue>.Create(classInfo.KeyProperty.Name, properties);
            }
            // ...
        }
        // ...
        DetachedObjectsHelper<Issue> _DetachedObjectsHelper;
        // ...
    }
}
vb
Class MainWindow
    Public Sub New()
        InitializeComponent()
        Using session = New Session()
            Dim classInfo = session.GetClassInfo(Of Issue)()
            Dim properties = classInfo.Members.Where(Function(member) member.IsPublic AndAlso member.IsPersistent).[Select](Function(member) member.Name).ToArray()
            _DetachedObjectsHelper = DetachedObjectsHelper(Of Issue).Create(classInfo.KeyProperty.Name, properties)
        End Using
        ' ...
    End Sub
    ' ...
    Private _DetachedObjectsHelper As DetachedObjectsHelper(Of Issue)
    ' ...
End Class

Initialize InfiniteAsyncSource and bind it to the GridControl. Assign DetachedObjectsHelper.Properties to CustomProperties to populate the GridControl with the persistent objects’ properties. DetachedObjectsHelper.Properties contains their descriptors.

cs
namespace XPOIssues {
    public partial class MainWindow : Window {
        public MainWindow() {
        // ...
            var source = new InfiniteAsyncSource {
                CustomProperties = _DetachedObjectsHelper.Properties,
                KeyProperty = nameof(Issue.Oid)
            };
            source.FetchRows += OnFetchRows;
            source.GetTotalSummaries += OnGetTotalSummaries;
            grid.ItemsSource = source;
            LoadLookupData();
        }
        // ...
    }
}
vb
Class MainWindow
    Public Sub New()
    ' ...
        Dim source = New InfiniteAsyncSource With {
            .CustomProperties = _DetachedObjectsHelper.Properties,
            .KeyProperty = nameof(Issue.Oid)
        }
        AddHandler source.FetchRows, AddressOf OnFetchRows
        AddHandler source.GetTotalSummaries, AddressOf OnGetTotalSummaries
        grid.ItemsSource = source
        ' ...
    End Sub
    ' ...
End Class

You can also use PagedAsyncSource that displays data in pages:

cs
namespace XPOIssues {
    public partial class MainWindow : Window {
        public MainWindow() {
        // ...
            var source = new PagedAsyncSource {
                CustomProperties = _DetachedObjectsHelper.Properties,
                KeyProperty = nameof(Issue.Oid),
                PageNavigationMode = PageNavigationMode.ArbitraryWithTotalPageCount
            };
            source.FetchPage += OnFetchPage;
            source.GetTotalSummaries += OnGetTotalSummaries;
            grid.ItemsSource = source;
            // ...
        }
        // ...
    }
}
vb
Class MainWindow
    Public Sub New()
    ' ...
        Dim source = New PagedAsyncSource With {
            .CustomProperties = _DetachedObjectsHelper.Properties,
            .KeyProperty = nameof(Issue.Oid),
            .PageNavigationMode = PageNavigationMode.ArbitraryWithTotalPageCount
        }
        AddHandler source.FetchPage, AddressOf OnFetchPage
        AddHandler source.GetTotalSummaries, AddressOf OnGetTotalSummaries
        grid.ItemsSource = source
        ' ...
    End Sub
    ' ...
End Class

If you fetch new data from the database, use the ConvertToDetachedObjects(IEnumerable<T>) method to convert persistent objects to detached objects:

cs
namespace XPOIssues {
    public partial class MainWindow : Window {
    // ...
        void OnFetchRows(object sender, FetchRowsAsyncEventArgs e) {
            e.Result = Task.Run<FetchRowsResult>(() => {
                using(var session = new Session()) {
                    var queryable = session.Query<Issue>().SortBy(e.SortOrder, defaultUniqueSortPropertyName: nameof(Issue.Oid)).Where(MakeFilterExpression(e.Filter));
                    var items = queryable.Skip(e.Skip).Take(e.Take ?? 100).ToArray();
                    return _DetachedObjectsHelper.ConvertToDetachedObjects(items);
                }
            });
        }
        // ...
    }
}
vb
Class MainWindow
' ...
    Private Sub OnFetchRows(ByVal sender As Object, ByVal e As FetchRowsAsyncEventArgs)
        e.Result = Task.Run(Of FetchRowsResult)(Function()
            Using session = New Session()
                Dim queryable = session.Query(Of Issue)().SortBy(e.SortOrder, defaultUniqueSortPropertyName:=NameOf(Issue.Oid)).Where(MakeFilterExpression(e.Filter))
                Dim items = queryable.Skip(e.Skip).Take(If(e.Take, 100)).ToArray()
                Return _DetachedObjectsHelper.ConvertToDetachedObjects(items)
            End Using
        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

WPF Best Practices