Bind to XPO Data Sources
- 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.
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.
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);
}
}
}
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);
}
}
}
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();
}
}
}
}
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 }
}
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)); }
}
}
}
Local Data
If you store data in a local database, you can use XPCollection as a data source and bind it to the GridControl.
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;
}
}
}
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:
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();
}
}
}
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:
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();
}
}
}
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.
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;
// ...
}
}
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.
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();
}
// ...
}
}
You can also use PagedAsyncSource that displays data in pages:
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;
// ...
}
// ...
}
}
If you fetch new data from the database, use the ConvertToDetachedObjects(IEnumerable<T>) method to convert persistent objects to detached objects:
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);
}
});
}
// ...
}
}
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.