KB Article K18482
Visible to All Users

How to create persistent metadata on the fly and load data from an arbitrary table

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 Basic
Dim 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 Basic
Public 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 Basic
Dim 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

Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.