Technology evolves and we do our best to support everyone to the best of our abilities. In XAF's ASP.NET Blazor Server UI, we support pure XPO, EF Core and non-persistent/POCO classes only.
Persistent interfaces, formerly known as DC, are deprecated and not recommended for new projects. We have asked users to migrate away from DC since 2016. If you choose not to migrate, your DC-based projects will continue to work for WinForms and ASP.NET WebForms – you don’t have to switch to Blazor. In other words, if you are happy with DC and do not need new features, you can continue using DC as long as you wish. Otherwise, we suggest that you consider migrating to pure XPO classes.
If you choose to use Blazor, you can convert your existing DC interfaces to XPO classes in many scenarios. Yes, it’s not a simple process but many organizations have migrated successfully. We are more than happy to help you in this migration process.
NOTE: Backup your XAF solution sources and database before going further, because .NET code and database modifications are to be done.
DC Source Code Migration
Consider one of the following code migration options below.
Option 1. Reuse the XPO Classes Generated for DC Interfaces
Since XAF internally generates standard XPO classes for DC interfaces, you can copy-paste their source code to *.cs files and use it for further modification.
1. Consider the following source DC interfaces and their registration:
C#[DomainComponent]
public interface IMaster {
string Name { get; set; }
IList<IDetail> Details { get; }
}
[DomainComponent]
public interface IDetail {
string Name { get; set; }
}
XafTypesInfo.Instance.RegisterEntity("Master", typeof(IMaster));
XafTypesInfo.Instance.RegisterEntity("Detail", typeof(IDetail));
2. Run your DC-based app in Release mode and without the debugger attached (Control+F5 in Visual Studio). Notice the DcAssembly.dll file created near the executable file.
3. Open DcAssembly.dll using the free ILSpy (or Reflector and similar .NET decompiler tools) and see the generated C# code for runtime classes corresponding to your registered domain components. You may see the following result in ILSpy for the Master class:
NOTE: Instead of decompiling this assembly, you can put a breakpoint into the DevExpress.ExpressApp.Utils.CodeGeneration.CSCodeCompiler.Compile method, run the application and check the method's sourceCode parameter using our PDBs: How can I debug DevExpress .NET source code using PDB files.
Option 2. Generate XPO Classes with ORM Data Model Wizard
You can use the database that was created by your Domain Components to generate a new XPO data model as described in the following help topic: How to: Generate XPO Business Classes for Existing Data Tables.
Option 2. Design XPO Classes from Scratch
It is possible that designing XPO classes from scratch can be faster than decompiling or generating their code, as shown above. This is likely true in cases when you do not need to migrate all the data members as per #1. The use of tools like CodeRush and its shortcuts for XPO feature can be helpful in this case.
Common Considerations
Once you get your XPO class code using one of the options above, you will have to copy this code into a new code file and modify it. Note that code for scalar properties can be reused "as is". You will mainly need to rework the code for reference and collection properties (for instance, see the selected fragment) and associated classes. For more information, refer to the following article: Object Relationships. Additionally, you will need to rework the class declaration and service DC classes and properties like PersistentInterfaceData
, Instance
. For more information, see the attached Class1.cs file with the modified code of the Master and Detail classes from DcAssembly.dll and also inspect the DcAssemblyScenario5SharedParts.dll file.
DC Attribute Migration
To create XPO classes easier, the following table lists attributes from the DevExpress.ExpressApp.DC
namespace and their replacements from the DevExpress.Xpo
namespace.
DC Attribute | XPO Attribute |
---|---|
NonPersistentDcAttribute | NonPersistentAttribute |
PersistentDcAttribute | PersistentAttribute |
AggregatedAttribute | AggregatedAttribute |
CalculatedAttribute | PersistentAliasAttribute |
FieldSizeAttribute | SizeAttribute |
BackReferencePropertyAttribute | AssociationAttribute |
DomainComponentAttribute | No Replacement |
DomainLogicAttribute | No Replacement |
CreateInstanceAttribute | No Replacement |
For the full list of XAF and XPO attributes, refer to Data Annotations in XAF Data Model and XPO Built-In Attributes.
Domain Logic Code Migration
To migrate your business logic in domain logic containers, consider one of the following options:
1. Reuse your existing domain logic classes inside XPO classes
1.1. Remove DomainLogicAttribute from your logic classes and replace the IObjectSpace
with Session
and IYourBusinessObject
with YourBusinessObject
in logic method parameters.
1.2. Plug in your logic classes into your XPO class as shown in the generated C# code for runtime DC classes (open DcAssembly.dll using .NET decompiler tools as discussed above). For this, you can declare a private field of your domain logic type (for instance, private IProduct_Logic logic_IProduct_Logic;
) and then initialize it from your XPO class constructor (see the CreateLogics
method in the screenshot below).
1.3. Replace the IObjectSpace
parameters in logic method calls with this.Session
inside your XPO class. Take special note that if you call your logic from property setters or the overridden OnChanged
methods of XPO classes, check the IsLoading
and IsSaving
property values before your logic calls. For examples, see XPO Best Practices | How to: Calculate a Property Value Based on Values from a Detail Collection.
Consider the example below:
2. Implement ORM-agnostic logic containers
This option was a predecessor of DC domain logic classes back in 2005 and it is still valid today. You can see examples within the XAF sources in the following files:
- …\DevExpress.Persistent\DevExpress.Persistent.BaseImpl\Task.cs;
- …\DevExpress.Persistent\DevExpress.Persistent.Base\General\TaskImpl.cs.
This options allows you to create easily testable logic classes not concerned with ORM persistence. This logic can also be easily reused within XAF business classes and controllers as well as non-XAF apps.
3. Implement business logic inside XAF Controllers
You can handle View and IObjectSpace events to call required business logic. Examples: IObjectSpace.ObjectChanged | Add an Action with Option Selection. To organize this logic for different classes and apps, you may want to implement logic container classes as in option #2 above.
DC Database Migration
The database table structure for DC interfaces is different than the table structure used for pure XPO classes because service shared part and association tables are created to mimic multiple inheritance. To see all the differences between databases for plain XPO classes and DC interfaces, please open SQL Server Management Studio (SSMS) and compare table and column structures for DC-based and XPO-based apps. For instance, check how one-to-many and inheritance relationships for the Contact, Person, Party and PhoneNumber XPO classes from our MainDemo app are reflected in the underlying database. You can compare this XPO data model and its table structure with a similar scenario in your DC-based app. Our MainDemo app also demonstrates other popular scenarios with many-to-many, inheritance, and one-to-to relationships.
If you want to reuse data models and databases from your existing DC-based apps in new XPO-based apps, you can consider one of the following strategies:
- Strategy 1. If you have one app that uses DC with one database and want to create a Blazor app that will use new XPO classes, you can also create a separate database for your Blazor app, because DC-based and XPO-based databases are incompatible.
- Strategy 2. If you have one app that uses DC and want to create a Blazor app that will use new XPO classes, but want both apps to use the same database, you can manually migrate from DC interfaces to new XPO classes in both apps and also migrate your existing database structure and data (for instance, using SQL scripts) to comply with the new XPO data model.
Consider the following data migration scenarios depending on your original DC data model:
Scenario 1: Plain data model structures with no inheritance and relationships
C#[DefaultClassOptions]
[DomainComponent]
public interface IDetail {
string DetailProperty { get; set; }
}
XafTypesInfo.Instance.RegisterEntity("iDetail", typeof(IDetail));
To migrate this simplest data model and database, do the following:
- Create a new XPO
Detail
class manually or as described in sections "Reuse the source code generated for DC". - Either rename the table to
Detail
or decorate your XPO class with the[Persistent("iDetail")]
attribute (if you are happy with theiDetail
table name). - Locate the corresponding record in the XPObjectType table with TypeName = "DevExpress.ExpressApp.DC.GeneratedClasses.iDetail" and change TypeName to "YourNamespace.Detail" manually or automatically using the ModuleUpdater descendant (inside the YourSolutionName.Module/Updater.xx file). For this, call the following line in the
UpdateDatabaseAfterUpdateSchema
method:((XPObjectSpace) ObjectSpace).Session.CreateObjectTypeRecords();
- this will update records in theXPObjectType
table based on your new XPO classes.
Scenario 2. Plain data model structures with no inheritance and a M-N relationship (master back reference property is explicit)
C#[DefaultClassOptions]
[DomainComponent]
public interface IMaster {
string MasterProperty { get; set; }
IList<IDetail> Details { get; }
}
[DefaultClassOptions]
[DomainComponent]
public interface IDetail {
IMaster MasterReference { get; set; }
string DetailProperty { get; set; }
}
XafTypesInfo.Instance.RegisterEntity("iMaster", typeof(IMaster));
XafTypesInfo.Instance.RegisterEntity("iDetail", typeof(IDetail));
To migrate this simplest data model and database, use the steps from Scenario #1 for IDetail
and IMaster
.
Scenario 3. Plain data model structures with no inheritance and with a M-N relationship (master back reference property is implicit)
C#[DefaultClassOptions]
[DomainComponent]
public interface IMaster {
string MasterProperty { get; set; }
IList<IDetail> Details { get; }
}
[DefaultClassOptions]
[DomainComponent]
public interface IDetail {
string DetailProperty { get; set; }
}
XafTypesInfo.Instance.RegisterEntity("iMaster", typeof(IMaster));
XafTypesInfo.Instance.RegisterEntity("iDetail", typeof(IDetail));
To migrate this data model and database, do the following:
- In the
Detail
XPO class and table, create an back reference property/FK column in theMaster
class explicitly (MasterReference
as in Scenario #2). This FK column should refer to theOid
PK column of theMaster
table. - Populate this new
MasterReference
property/FK column with values from theIDetail_Implicit_IMaster_Details_List_Link
column in theIMaster_Details
table. - Remove the
IMaster_Details
table and execute additional steps from Scenario #2.
Scenario 4. Complex data model structures with a single inheritance and no shared parts (a simple base class entity)
C#[DefaultClassOptions]
[DomainComponent]
public interface IDetail: ISharedPart1 {
string DetailProperty { get; set; }
}
[DomainComponent]
public interface ISharedPart1 {
string SharedProperty1 { get; set; }
}
XafTypesInfo.Instance.RegisterEntity("iDetail", typeof(IDetail));
XafTypesInfo.Instance.RegisterEntity("iSharedPart1", typeof(ISharedPart1));
To migrate this simplest data model and database, do the following:
- Inherit the
Detail
XPO class from theSharedPart1
XPO class. Take special note that for inheritance (public class Detail : SharedPart1 { ... }
) the additionalObjectType
column exists in the base class table and its values refer to theOid
column in the XPObjectType table (the former record with TypeName = "DevExpress.ExpressApp.DC.GeneratedClasses.iDetail" or TypeName = "YourNamespace.Detail" after the upgrade). - Use the steps from Scenario #1 for
IDetail
andISharedPart1
. - If you used automatic XPObjectType table update from Scenario #1 (
((XPObjectSpace) ObjectSpace).Session.CreateObjectTypeRecords();
), manually update theObjectType
column in corresponding base tables based on the automatically created type records in theXPObjectType
table (they will have different Oid).
Scenario 5: Complex data model structures with multiple inheritance and shared parts
C#[DefaultClassOptions]
[DomainComponent]
public interface IDetail: ISharedPart1, ISharedPart2 {
string DetailProperty { get; set; }
}
[DomainComponent]
public interface ISharedPart1 {
string SharedProperty1 { get; set; }
}
[DomainComponent]
public interface ISharedPart2 {
string SharedProperty2 { get; set; }
}
XafTypesInfo.Instance.RegisterEntity("iDetail", typeof(IDetail));
XafTypesInfo.Instance.RegisterSharedPart(typeof(ISharedPart1));
XafTypesInfo.Instance.RegisterSharedPart(typeof(ISharedPart2));
The underlying DC data model and database structure for shared parts is very complex. This structure consists of a base XPO class and descendant for each shared part (ISharedPart1\_Data\_iDetail : ISharedPart1\_Data
), which store their data (SharedPropertyX
) in the ISharedPartX_Data
tables. Inspect the DcAssemblyScenario5SharedParts.dll file and the corresponding table structure for more information. Unfortunately, this structure cannot be migrated to plain XPO classes and database cannot be reused easily.
To migrate this data model and database, do the following:
- Rethink the structure of data models that used shared parts and implement XPO classes from scratch. Implementation options include:
A) Declare data properties from shared parts (SharedPropertyX
) in theDetail
XPO class directly with or without using base class inheritance. This will help you achieve the best SQL query performance, because only plain data will be queried without joining other data tables.
B) Declare base classes for shared parts (SharedPartX
) and use them in theDetail
class through an aggregated persistent reference property. Multiple reference properties may cause performance degradations in certain scenarios, so try to keep such shared part properties to the minimum or use option A instead.
C#[DefaultClassOptions]
public class Detail : BaseObject {
public Detail(Session s) : base(s) { }
public override void AfterConstruction() {
base.AfterConstruction();
SharedPart1 = new SharedPart1(Session);
SharedPart2 = new SharedPart2(Session);
}
private string _DetailProperty;
public string DetailProperty {
get { return _DetailProperty; }
set { SetPropertyValue(nameof(DetailProperty), ref _DetailProperty, value); }
}
private SharedPart1 _SharedPart1;
[DevExpress.Xpo.Aggregated, ExplicitLoading(0), ExpandObjectMembers(ExpandObjectMembers.Always)]
public SharedPart1 SharedPart1 {
get { return _SharedPart1; }
set { SetPropertyValue(nameof(SharedPart1), ref _SharedPart1, value); }
}
private SharedPart2 _SharedPart2;
[DevExpress.Xpo.Aggregated, ExplicitLoading(0), ExpandObjectMembers(ExpandObjectMembers.Always)]
public SharedPart2 SharedPart2 {
get { return _SharedPart2; }
set { SetPropertyValue(nameof(SharedPart2), ref _SharedPart2, value); }
}
}
public class SharedPart1 : BaseObject {
public SharedPart1(Session s) : base(s) { }
private string _SharedProperty1;
public string SharedProperty1 {
get { return _SharedProperty1; }
set { SetPropertyValue(nameof(SharedProperty1), ref _SharedProperty1, value); }
}
}
public class SharedPart2 : BaseObject {
public SharedPart2(Session s) : base(s) { }
private string _SharedProperty2;
public string SharedProperty2 {
get { return _SharedProperty2; }
set { SetPropertyValue(nameof(SharedProperty2), ref _SharedProperty2, value); }
}
}
- Manually migrate required column values from the
ISharedPartX_Data
tables to the newSharedPartX
orDetail
tables, if applicable. - Remove the
ISharedPartX_Data
tables and their corresponding records in theXPObjectType
table.