Skip to main content

Define the Data Model and Set the Initial Data

  • 10 minutes to read

This topic explains how to implement entity classes for your application. It also describes the basics of automatic user interface construction based on a data model; this includes information on how to do the following:

  • Create entity classes.
  • Use migrations to propagate the data model structure changes to the database.
  • Populate the database with initial data.

Entity classes do not depend on the application UI. Implement them in a platform-independent module project. This code architecture allows XAF and non-XAF applications to share entities (for instance, with a Web API Service and .NET MAUI, JavaScript, or Blazor clients).

The application’s data model consists of two logical blocks:

  • Marketing: the Customer and Testimonial classes.
  • Planning: the Project, ProjectTask, and Employee classes.

Add Entity Classes for the Marketing Group

Go to the SimpleProjectManager.Module project and right-click the Business Objects folder. Choose Add | Class… in the context menu. Specify Customer.cs as the new class name and click Add. Replace auto-generated code with the following class declaration:

using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl.EF;

namespace SimpleProjectManager.Module.BusinessObjects
{
    // Use this attribute to place a navigation item that corresponds to the entity class in the specified navigation group.
    [NavigationItem("Marketing")]

    // Inherit your entity classes from the BaseObject class to support CRUD operations for the declared objects automatically.
    public class Customer : BaseObject
    {
        public virtual String FirstName { get; set; }

        public virtual String LastName { get; set; }

        // Use this attribute to specify the maximum number of characters that the property's editor can contain.
        [FieldSize(255)]
        public virtual String Email { get; set; }

        public virtual String Company { get; set; }

        public virtual String Occupation { get; set; }

        public virtual IList<Testimonial> Testimonials { get; set; } = 
        new ObservableCollection<Testimonial>();

        public String FullName
        {
            get { return ObjectFormatter.Format("{FirstName} {LastName} ({Company})", 
            this, EmptyEntriesMode.RemoveDelimiterWhenEntryIsEmpty); }
        }

        // Use this attribute to show or hide a column with the property's values in a List View.
        [VisibleInListView(false)]

        // Use this attribute to specify dimensions of an image property editor.
        [ImageEditor(ListViewImageEditorCustomHeight = 75, DetailViewImageEditorFixedHeight = 150)]

        public virtual MediaDataObject Photo { get; set; }

    }
}

In the same manner, create the Testimonial class. Replace the generated class declaration with the following code:

using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl.EF;

namespace SimpleProjectManager.Module.BusinessObjects
{

    [NavigationItem("Marketing")]
    public class Testimonial : BaseObject
    {
        public virtual string Quote { get; set; }

        [FieldSize(512)]
        public virtual string Highlight { get; set; }

        [VisibleInListView(false)]
        public virtual DateTime CreatedOn { get; set; }

        public virtual string Tags { get; set; }

        public virtual IList<Customer> Customers { get; set; } = new ObservableCollection<Customer>();

    }
}

The Testimonials collection property in the Customer class defines the first part of the many-to-many relationship between the Customer and Testimonial entities. The declaration of the Customers collection property in the Testimonial class completes the relationship.

Add Entity Classes to the Planning Group

In the SimpleProjectManager.Module\BusinessObjects folder, create the Project class. Replace the generated class declaration with the following code:

using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl.EF;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace SimpleProjectManager.Module.BusinessObjects
{
    [NavigationItem("Planning")]
    // Use this attribute to specify the property whose value is displayed in Detail View captions, the leftmost column of a List View, and in Lookup List Views.
    [DefaultProperty(nameof(Name))]
    public class Project : BaseObject
    {
        public virtual string Name { get; set; }

        public virtual Employee Manager { get; set; }

        [StringLength(4096)]
        public virtual string Description { get; set; }

        public virtual IList<ProjectTask> ProjectTasks { get; set; } = new ObservableCollection<ProjectTask>();

    }
}

In the same manner, create the ProjectTask and Employee entity classes. Replace the generated class declarations with the following code:

ProjectTask.cs

using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl.EF;
using System.ComponentModel.DataAnnotations;

namespace SimpleProjectManager.Module.BusinessObjects
{
    [NavigationItem("Planning")]
    public class ProjectTask : BaseObject
    {
        [FieldSize(255)]
        public virtual string Subject { get; set; }

        public virtual ProjectTaskStatus Status { get; set; }

        public virtual Employee AssignedTo { get; set; }

        public virtual DateTime? DueDate { get; set; }

        public virtual DateTime? StartDate { get; set; }

        public virtual DateTime? EndDate { get; set; }

        [StringLength(4096)]
        public virtual string Notes { get; set; }

        public virtual Project Project { get; set; }

    }

    public enum ProjectTaskStatus
    {
        NotStarted = 0,
        InProgress = 1,
        Completed = 2,
        Deferred = 3
    }
}

Employee.cs

using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl.EF;
using System.ComponentModel;

namespace SimpleProjectManager.Module.BusinessObjects
{
    [DefaultProperty(nameof(FullName))]
    public class Employee : BaseObject
    {
        public virtual String FirstName { get; set; }
        public virtual String LastName { get; set; }
        public String FullName
        {
            get { return ObjectFormatter.Format("{FirstName} {LastName}", 
            this, EmptyEntriesMode.RemoveDelimiterWhenEntryIsEmpty); }
        }
    }
}

The reference property of the Project type in the ProjectTask class implements the “one” part of the one-to-many relationship between the Project and `ProjectTask entities. The ProjectTasks collection property in the Project class implements the “many” part of the relationship.

The most common relationship creation practice is to define properties on both ends of the relationship. At the same time, it is sufficient to add only the reference property (the “one” part). This property establishes the one-to-many relationship between entities, and Entity Framework Core automatically creates a foreign key to the related table in a database. You can see this technique in action in the code samples above — the reference property of the Employee type in the ProjectTask class creates the one-to-many relationship between these classes.

You can use either of these techniques, but when you omit the “many” part of the relationship, XAF does not create an editor for the omitted collection property in the Detail View of the entity class.

Go to the SimpleProjectManager.Module\BusinessObjects\MySolutionDbContext file and add the following properties to the SimpleProjectManagerEFCoreDbContext entity container class:

using SimpleProjectManager.Module.BusinessObjects;

namespace  SimpleProjectManager.Module.BusinessObjects {
    public class SimpleProjectManagerEFCoreDbContext : DbContext {
        //...
        public DbSet<Customer> Customers { get; set; }
        public DbSet<Testimonial> Testimonials { get; set; }
        public DbSet<Project> Projects { get; set; }
        public DbSet<ProjectTask> ProjectTasks { get; set; }
        public DbSet<Employee> Employees { get; set; }
    }
}    

These properties store collections of entity class objects. For each collection, XAF creates a table with the same name in the database and then maps the collection to the table.

Set up Migrations

Since this tutorial uses Entity Framework Core, changes to the application’s data model may cause database-related exceptions when you run the application. An exception occurs if the database structure does not correspond to the structure of the data model classes.

In this tutorial, we use migrations to update the database schema because this feature is native to EF Core and is quick to implement. Every time you change the data model of your application, create a migration and update the database. To do this, follow the steps below.

Important

Delete an existing database if there is one before you proceed, because Entity Framework Core does not take the existing database schema into consideration when it generates the first migration.

  1. Add the Microsoft.EntityFrameworkCore.Tools NuGet package to the SimpleProjectManager.Module project. Build the solution.

    Note

    The package’s version must match the version of Entity Framework Core supported in XAF.

    Currently, we support Entity Framework Core 8.x.x (CTP). To find out which precise version you have, check the Microsoft.EntityFrameworkCore package in the dependencies of the YourProjectName.Module project.

  2. In the SimpleProjectManager.Module project, go to the BusinessObjects folder and open the SimpleProjectManagerDbContext.cs file. Replace the declaration of the SimpleProjectManagerDesignTimeDbContextFactory class with the code below:

    namespace SimpleProjectManager.Module.BusinessObjects;
    //...
    public class SimpleProjectManagerDesignTimeDbContextFactory : IDesignTimeDbContextFactory<SimpleProjectManagerEFCoreDbContext> {
        public SimpleProjectManagerEFCoreDbContext CreateDbContext(string[] args) {
            // Throw new InvalidOperationException("Make sure that the database connection string and connection provider are correct. After that, uncomment the code below and remove this exception.");
            var optionsBuilder = new DbContextOptionsBuilder<SimpleProjectManagerEFCoreDbContext>();
            optionsBuilder.UseSqlServer("Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=SimpleProjectManager");
            // Automatically implements the INotifyPropertyChanged interface in the business objects
            optionsBuilder.UseChangeTrackingProxies();
            optionsBuilder.UseObjectSpaceLinkProxies();
            return new SimpleProjectManagerEFCoreDbContext(optionsBuilder.Options);
        }
    }
    

    In the above code sample, the optionsBuilder.UseChangeTrackingProxies method enables the change-tracking proxies extension so that the application’s UI correctly reflects changes in data model. Refer to the Change Tracking in EF Core DbContext and Performance Considerations article for more information on change tracking in XAF applications with Entity Framework Core data models.

  3. In Visual Studio, open the Package Manager Console and use the following command to add a migration:

    add-migration MyInitialMigrationName -StartupProject "SimpleProjectManager.Module" -Project "SimpleProjectManager.Module"
    
  4. Update the database with the following command:

    update-database -StartupProject "SimpleProjectManager.Module" -Project "SimpleProjectManager.Module"
    

You must update the database whenever you change the data model of your application, for example, when you add, rename, or delete a class or property. To do this, repeat steps 3 and 4 of this tutorial. Make sure to use a unique migration name for each new migration.

Populate the Database with Initial Data

Expand the SimpleProjectManager.Module project in the Solution Explorer and go to the DatabaseUpdate folder. Open the Updater.cs file and add the following code to the ModuleUpdater.UpdateDatabaseAfterUpdateSchema method:

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Updating;
using SimpleProjectManager.Module.BusinessObjects;
// ...
public class Updater : ModuleUpdater {
    //...
    public override void UpdateDatabaseAfterUpdateSchema() {
        base.UpdateDatabaseAfterUpdateSchema();

        Employee employee = ObjectSpace.FirstOrDefault<Employee>(x => 
        x.FirstName == "John" && x.LastName == "Nielsen");
        if (employee == null)
        {
            employee = ObjectSpace.CreateObject<Employee>();
            employee.FirstName = "John";
            employee.LastName = "Nielsen";
        }

        ProjectTask task = ObjectSpace.FirstOrDefault<ProjectTask>(t => 
        t.Subject == "TODO: Conditional UI Customization");
        if (task == null)
        {
            task = ObjectSpace.CreateObject<ProjectTask>();
            task.Subject = "TODO: Conditional UI Customization";
            task.Status = ProjectTaskStatus.InProgress;
            task.AssignedTo = ObjectSpace.FirstOrDefault<Employee>(p => 
            p.FirstName == "John" && p.LastName == "Nilsen");
            task.StartDate = new DateTime(2023, 1, 27);
            task.Notes = "OVERVIEW: http://www.devexpress.com/Products/NET/Application_Framework/features_appearance.xml";
        }

        Project project = ObjectSpace.FirstOrDefault<Project>(p => 
        p.Name == "DevExpress XAF Features Overview");
        if (project == null) {
            project = ObjectSpace.CreateObject<Project>();
            project.Name = "DevExpress XAF Features Overview";
            project.Manager = ObjectSpace.FirstOrDefault<Employee>(p => 
            p.FirstName == "John" && p.LastName == "Nilsen");
            project.ProjectTasks.Add(ObjectSpace.FirstOrDefault<ProjectTask>(t => 
            t.Subject == "TODO: Conditional UI Customization"));
        }

        Customer customer = ObjectSpace.FirstOrDefault<Customer>(c => 
        c.FirstName == "Ann" && c.LastName == "Devon");
        if (customer == null)
        {
            customer = ObjectSpace.CreateObject<Customer>();
            customer.FirstName = "Ann";
            customer.LastName = "Devon";
            customer.Company = "Eastern Connection";
        }

        ObjectSpace.CommitChanges(); // Uncomment this line to persist created objects.
    }

    // ...
}

XAF uses an Object Space to manage persistent objects. Refer to the following topic for detailed information: Create, Read, Update and Delete Data).

Note

For more information on how to populate the database with initial data, review the following lesson of the In-Depth .NET 6+ WinForms & Blazor UI Tutorial: Supply Initial Data.

Run the Application

Click Start Debugging or press F5.

XAF generates a user interface based on the specified data structures. The navigation control contains items that correspond to the entity classes you created. List and Detail Views support CRUD operations and other functionality, such as navigation, search, filter, or print. Detail Views contain editors that display different entity class properties. For more information about built-in editors, refer to the following topic: Data Types Supported by built-in Editors.

Lookup and collection editors display properties that constitute a relationship between entities. For example, the Testimonials group in the Customer Detail View is how XAF renders the Testimonials collection property. To create new objects in the Testimonials collection, use the New button in this tab. The Link button allows users to add references to existing Testimonial objects.

ASP.NET Core Blazor
ASP.NET Core Blazor Application Customer Detail View, DevExpress
Windows Forms
Windows Forms Application Customer Detail View, DevExpress

In the Project Task Detail View, XAF creates a lookup editor for the Project reference property. This editor uses a special View type called Lookup List View. The Lookup List View includes one column that displays default property values. In your application, these are the Name property values.

ASP.NET Core Blazor
ASP.NET Core Blazor Application Project Task Detail View, DevExpress
Windows Forms
Windows Forms Application Project Task Detail View, DevExpress

XAF creates a database based on the data model you specified. You can see your application’s database structure in the Object Explorer of Microsoft SQL Server Management Studio.

Database structure, DevExpress

Note

For additional information on UI generation, refer to the following topics: List View Column Generation and View Items Layout Generation.

Next Lesson

Customize the Application UI and Behavior

See Also