The primary purpose of this component is to dynamically generate data types based on some metadata. The metadata describes the properties of the types and how the type maps to the underlying data store. The metadata is defined by an end user via a WPF application and is stored in a database via an intermediary WCF service that manages the metadata.
Metadata
Reflection Type Generation
Given metadata for a Type to be generated at runtime, take for example the following metadata definition:EntityDescriptor entityMetadata = new EntityDescriptor() { BaseType = typeof(IEntity), EntityName = "GeneratedEntity", EntityProperties = new List<EntityPropertyDescriptor>() { new EntityPropertyDescriptor() { ColumnName = "Id", IsIdentity = true, IsNullable = false, Name = "Identifier", Type = typeof(Guid) }, new EntityPropertyDescriptor() { ColumnName = "Comment", IsIdentity = false, IsNullable = false, Name = "Comment", Type = typeof(String) },
TableName = "TestTable" } };
The first step in generating the dynamic types is to create an instance of an AssemblyBuilder and a ModuleBuilder into which the Type, based on metadata will be emitted.
ModuleBuilder moduleBuilder; AssemblyBuilder assemblyBuilder;
DynamicTypeGenerator.Instance.CreateDynamicAssembly("TestAssembly", out assemblyBuilder, out moduleBuilder);
Now we have an AssemblyBuilder instance and an instance of a ModuleBuilder which will be used in subsequent calls to the DynamicTypeGenerator instance to emit types into. For the purpose of this example only one Type is to be emitted into the Assembly.
Given the above entity metadata a call to the CreateLinqToSqlType method of the DynamicTypeGenerator instance passing the type metadata generates the underlying Type for the metadata description.
DynamicTypeGenerator.Instance.CreateLinqToSqlType("TestAssembly", assemblyBuilder, moduleBuilder, entityMetadata);
The CreateLinqToSqlType as expected generates a simple LinqToSql data type entity that has attributes applied to its properties to support the mapping of the Type to an underlying database. There is not anything extremely complicated about this method. It uses a number of the ReflectionEmit constructs to generate the Types and the ILGenerator class to emit IL into the get and setters of the Types properties.
Given the entityMetadata above the following is the generated C# code extracted using ilSpy
Looking a bit closer at the GeneratedEntity Type above we see the following:
- A Table attribute has been applied to the Type
- Each of the Types properties has a Column attribute applied
- The identity property has the IsPrimaryKey set to true
- The generated Type derives from an interface IEntity
So what does this give us?
- A type that can be used via LinqToSql to query, update and delete entities in the underlying data store
- An abstraction from the underlying data stores table and column names. It is not a requirement for the table and generated type names to match. This also applies to the properties
- A type that derives from a statically types known interface type called IEntity. This allows code that consumes these types to cast to a known defined type. Thus reducing the amount of reflection based referencing required to manipulate these generated types
What doesn't this give us?
- There is no abstraction of the underlying types from the data store. So if the underlying column is of type nvarchar then the property of the generated entity must be a String. The generator does not support type coercion or conversion which can get messy. The WCF service that allows the definition of the types metadata ensures that this cannot occur
- There is no abstraction from the underlying data stores foreign key relationships. So if a type is mapped to a table that requires a foreign key value to be specified which is not exposed via the generated type then new rows cannot be inserted
- There is no support for nested relationships. i.e a Thing contains a list of Parts. For the use cases of the overall project this is not a key requirement and can be added with more elaborate metadata and a change to the type generator
So we now have one of the key building blocks of the configurable OData service. In the next post, Part 3 – Custom Data Service Provider, we will see how this fits into place.