Description:
We cannot create persistent classes declaratively, because our users' requirement is the capability to view data from an arbitrary database. We do not know the structure of the database before the application starts. What is the best way to create such an application?
Search keywords: dynamically, dynamic, runtime, structure, custom, member, class, attribute, XPDictionary, ReflectionDictionary, XPClassInfo, XPMemberInfo, XPTypeInfo
Answer:
For this task, XPO provides the capability to define persistent classes on the fly, without defining any classes in code, except for the class that will be used as the base class for dynamically created metadata. This class should provide a constructor with the XPClassInfo parameter. The code below is quite enough for this:
C#[NonPersistent]
public class BasePersistentClass :XPLiteObject {
public BasePersistentClass(Session session) : base(session) { }
public BasePersistentClass(Session session, XPClassInfo classInfo) : base(session, classInfo) { }
}
Visual Basic<NonPersistent> _
Public Class BasePersistentClass
Inherits XPLiteObject
Public Sub New(ByVal session As Session)
MyBase.New(session)
End Sub
Public Sub New(ByVal session As Session, ByVal classInfo As XPClassInfo)
MyBase.New(session, classInfo)
End Sub
End Class
You can create virtual classes using the XPDictionary.CreateClass method. This method creates a new instance of XPClassInfo, and also adds it to the XPDictionary.Classes collection. The XPClassInfo.CreateMember method will create a new XPCustomMemberInfo instance, and add it to the XPClassInfo.Members collection. For example:
C#XPDictionary dictionary = new ReflectionDictionary();
XPClassInfo classInfo = dictionary.CreateClass(dictionary.GetClassInfo(typeof(BasePersistentClass)), "MyClass");
XPMemberInfo memberInfo = classInfo.CreateMember("MyProperty", typeof(string));
Visual BasicDim dictionary As XPDictionary = New ReflectionDictionary()
Dim classInfo As XPClassInfo = dictionary.CreateClass(dictionary.GetClassInfo(GetType(BasePersistentClass)), "MyClass")
Dim memberInfo As XPMemberInfo = classInfo.CreateMember("MyProperty", GetType(String))
The ConnectionProviderSql class provides two methods that can be used to retrieve the database schema. The ConnectionProviderSql.GetStorageTablesList method returns an array of string values, representing table names in the database. The table names can be passed as parameters to the ConnectionProviderSql.GetStorageTables method, which will return an array of DBTable instances, describing the schema of specified tables. Here is the code of the method that creates persistent metadata based on a DBTable instance:
C#public static XPClassInfo AddClass(XPDictionary dict, DBTable table) {
if (table.PrimaryKey.Columns.Count > 1)
throw new NotSupportedException("Compound primary keys are not supported");
XPClassInfo classInfo = dict.CreateClass(dict.GetClassInfo(typeof(BasePersistentClass)), table.Name.Replace('.', '_'));
classInfo.AddAttribute(new PersistentAttribute(table.Name));
DBColumnType primaryColumnType = table.GetColumn(table.PrimaryKey.Columns[0]).ColumnType;
classInfo.CreateMember(table.PrimaryKey.Columns[0], DBColumn.GetType(primaryColumnType),
new KeyAttribute(IsAutoGenerationSupported(primaryColumnType)));
foreach (DBColumn col in table.Columns)
if (!col.IsKey)
classInfo.CreateMember(col.Name, DBColumn.GetType(col.ColumnType));
return classInfo;
}
static bool IsAutoGenerationSupported(DBColumnType type) {
return type == DBColumnType.Guid || type == DBColumnType.Int16 || type == DBColumnType.Int32;
}
Visual BasicPublic Shared Function AddClass(ByVal dict As XPDictionary, ByVal table As DBTable) As XPClassInfo
If table.PrimaryKey.Columns.Count > 1 Then
Throw New NotSupportedException("Compound primary keys are not supported")
End If
Dim classInfo As XPClassInfo = dict.CreateClass(dict.GetClassInfo(GetType(BasePersistentClass)), table.Name.Replace("."c, "_"c))
classInfo.AddAttribute(New PersistentAttribute(table.Name))
Dim primaryColumnType As DBColumnType = table.GetColumn(table.PrimaryKey.Columns(0)).ColumnType
classInfo.CreateMember(table.PrimaryKey.Columns(0), DBColumn.GetType(primaryColumnType), New KeyAttribute(IsAutoGenerationSupported(primaryColumnType)))
For Each col As DBColumn In table.Columns
If (Not col.IsKey) Then
classInfo.CreateMember(col.Name, DBColumn.GetType(col.ColumnType))
End If
Next col
Return classInfo
End Function
Shared Function IsAutoGenerationSupported(ByVal type As DBColumnType) As Boolean
Return type = DBColumnType.Guid OrElse type = DBColumnType.Int16 OrElse type = DBColumnType.Int32
End Function
The implementation of the method is quite simple. The method simply iterates through the DBTable.Columns collection, and adds a persistent property to metadata based on each DBColumn instance. The key property is generated separately, to check some necessary conditions. For example, it is impossible to create runtime metadata mapped to a table with a compound key.
XPDictionary can be passed as a parameter to the constructors of the SimpleDataLayer and ThreadSafeDataLayer classes.
C#XPClassInfo classInfo = session1.GetClassInfo(string.Empty, "Categories");
gridControl1.DataSource = new XPCollection(session1, classInfo);
Visual BasicDim classInfo As XPClassInfo = session1.GetClassInfo(String.Empty, "Categories")
gridControl1.DataSource = New XPCollection(session1, classInfo)
See Also:
How to create an XPClassInfo descendant to dynamically build a persistent class structure
How to generate persistent classes at runtime based on a dataset
How to create collection properties with associations at runtime
How to dynamically create a read-only calculated (persistent alias) property
How to create persistent classes mapped to tables with a composite primary key at runtime
How to get a list of a persistent object's properties
eXpressApp Framework > Concepts > Business Model Design > Types Info Subsystem > Customize Business Object's Metadata