Genome fluent mapping extensions (GFMX)

Genome fluent mapping extensions (GFMX)

Introduction

Genome Fluent Mapping Extensions (GFMX) allow you to map your persistent classes and their members with C# (or other .NET language) code. This makes the mapping more type-safe and provides better integration for different refactoring tools (e.g. rename property). The extension pack can also be used as an example of how different mapping features can be automated.

The compiled version of the extension pack (TechTalk.Genome.Extensions.FluentMapping.dll) can be found in the bin folder of the Genome installation.

The fluent mapping still validated compile-time, so using GFMX does not increase the application load time. Also, it can be fully combined with the classical XML mapping (even for a class) providing full backwards compatibility and possibility for smooth changing to GFMX.

The GFMX sample ({Genome installation folder}\Samples\CSharp\Extensions.FluentMapping) uses the same domain entity classes and mapping as the GenomeShop sample ({Genome installation folder}\Samples\CSharp\GenomeShop), but the mapping is provided through GFMX. The sample also shows how the classic XML-based mapping can be combined with GFMX (UserMappingExtensions.xml).

Setup project to use fluent mapping

To provide fluent mapping for business entities, special mapper classes have to be implemented, that implement the IFluentMapper interface (in namespace TechTalk.Genome.Extensions.FluentMapping). By implementing the Map() method of that interface, you can provide the mapping for one or more business entities.

public class UserMapper : IFluentMapper

{

    public void Map(IFluentMapping fluentMapping)

    {

        fluentMapping.MapType<User>()

            .AsPersistent()

                .MapMember(u => u.Id).AsPersistentField().WithIdGenerator()

                .MapMember(u => u.FirstName).AsPersistentField()

                .MapMember(u => u.LastName).AsPersistentField()

                .MapMember(u => u.Name).AsLinq(u =>

                    u.FirstName + " " + u.LastName)

        ;

    }

}

As this method will be executed runtime it makes sense to isolate it from production code. It is recommended to create a class library project and implement all fluent mappings there (e.g. GenomeShop.Schema.FluentMapping).

You can create one or more mapper classes. We recommend creating one for each entity class or entity class group. In the sample we have created three classes to map the three main entity groups of the application (UserMapper, ProductMapper, OrderMapper).

Once these classes have been implemented, they have to be registered for Genome to be invoked during compilation. For this the GFMX framework contains a custom mapping extension that has to be specified in the normal schema project as XML mapping (this is the only portion where XML mapping file has to be used). In this extension, you basically have to list the type names of your mapper classes, much like the following example shows:

<?xml version="1.0" ?>

<Mapping xmlns="urn:TechTalk:TT.OODAL.XmlMapping">

  <Using namespace="TechTalk.Genome.Extensions.FluentMapping" />

      <!-- the namespace for the mapping extension has to be specified -->

 

  <FluentMapping mapperType="GenomeShop.Schema.FluentMapping.UserMapper" />

  <FluentMapping mapperType="GenomeShop.Schema.FluentMapping.ProductMapper" />

  <FluentMapping mapperType="GenomeShop.Schema.FluentMapping.OrderMapper" />

 

</Mapping>

In order to compile the schema project, you have to add a reference to the project or projects where the mapper classes have been implemented as well as to the TechTalk.Genome.Extensions.FluentMapping.dll.

Mapping features supported by GFMX

The current version of the fluent mapping extensions supports the majority of the mapping features provided by Genome. Most of them are named the same or similar way than the related XML-based mapping features, but in some cases we have used different naming that we thought would fit better to the idea of fluent interfaces. Also, in comparison to the XML-based mapping, we have provided more (or different) defaults with GFMX. The following tables contain a summary of the fluent mapping features available.

Type mappings

Mapping feature

XML variant

Base scope when the mapping can be used

Syntax of the mapping feature

Comments

Persistent type

n/a

MapType<Customer>()

AsPersistent()

Sets the method chain scope for persistent type mappings

Applies defaults: <RootInheritance>, <Sealed>, <CodeGeneratedProxy> and sets the PK to "Id" if there is such a property in the class (case insensitive)

Root inheritance

<RootInheritance>

AsPersistent()

RootInheritance()

RootInheritance("customTableName")

AsPersistent() sets it as default (without custom table name)

Joined inheritance

<JoinedInheritance>

AsPersistent()

JoinedInheritance()

JoinedInheritance("customTableName")

 

Shared inheritance

<SharedInheritance>

AsPersistent()

SharedInheritance()

 

Non-polymorphic type

<Sealed>

AsPersistent()

Sealed()

AsPersistent() sets it as default

Type discriminator

<TypeDiscriminator fieldName="DiscProp" />

AsPersistent()

TypeDiscriminator(c => c.DiscProp)

 

Type discriminator value

<Discriminator>

AsPersistent()

Discriminator(myValue)

 

Abstract type

Absence of <CodeGeneratedProxy>

AsPersistent()

Abstract()

 

Primary keys

<PrimaryKey>

AsPersistent()

PrimaryKeys(c => c.CustomerId)

PrimaryKeys(

    c => c.CompositeId1,

    c => c.CompositeId2)

 

Query provider

<QueryProvider>

MapType<Customer>()

AsQueryProvider()

 

 Member mappings

Mapping feature

XML variant

Base scope when the mapping can be used

Syntax of the mapping feature

Comments

Persistent field

<PersistentField>

MapMember(c => c.Property)

AsPersistentField()

AsPersistentField("customFieldName")

 

Id generation strategy

<NativeIdGenerator>, <AutoIncrement>, <NewGuid>, <SequentialGuid>

AsPersistentField()

WithIdGenerator()

WithIdGenerator(IdGeneratorStrategy.AutoIncrement)

The parameterless overload uses the native id generation strategy

Default value

<DefaultValue>

AsPersistentField()

WithDefaultValue(serverInitialized, "SQL-expr")

 

Near object reference

<NearObjectReference>

MapMember(c => c.Property)

AsNearObjectReference()

AsNearObjectReference("singleFKFieldName")

Sets "CheckIntegrity="true" by default

Can be followed by "WithForeignKey", "WithForeignKeyPrefix", "WithForeignKeys", "WithIntegrityCheck", "WithoutIntegrityCheck"

Simple FK field name

n/a

AsNearObjectReference()

WithForeignKey("fkFieldName")

Only if the referred class has singular PK

Prefixed FK field naming

<NearObjectReference

  fieldPrefix="fkPrefix">

AsNearObjectReference()

WithForeignKeyPrefix("fkPrefix")

 

Custom FK field naming

<NearObjectReference>

  <Map … >

</NearObjectReference>

AsNearObjectReference()

WithForeignKeys(new Dictionary<string, string> {

  { "PK1", "FK1"},

  { "PK2", "FK2"}

})

 

FK constraints

<CheckIntegrity>

AsNearObjectReference()

WithIntegrityCheck()

WithIntegrityCheck("fkConstraintName")

WithoutIntegrityCheck()

The integrity check is enabled by default by the AsNearObjectReference()

1-n collection

<OneToManyCollection>

MapMember(c => c.Property)

AsOneToManyCollection()

AsOneToManyCollection(c => c.ParentRef)

 

n-m collection

<ManyToManyCollection>

MapMember(c => c.Property)

AsManyToManyCollection()

AsManyToManyCollection(c => c.OtherColl)

The first overload configures it as "Automatic", the other as "Reverse".

Can be followed by "Automatic", "Reverse" or "Explicit"

Automatic association class

<Automatic>

AsManyToManyCollection()

WithAutomaticAssociationClass()

WithAutomaticAssociationClass("tableName")

 

Reverse association

<Reverse>

AsManyToManyCollection()

ReverseOf()

ReverseOf(c => c.OtherColl)

 

Explicit association

<Explicit>

AsManyToManyCollection()

WithAssociationClass<AssocClass>()

WithAssociationClass<AssocClass>(

    a => a.ParentRef, a => a.ChildRef))

 

LINQ expressions

<Linq>

MapMember(c => c.Property)

MapMember(c => c.MyMethod(Parameter.Of<int>()))

AsLinq(c => c.Property + "something")

AsLinq(o =>

    from oi in o.Items

    select oi.Product);

AsLinq((MyClass c, int somehting) => c.MyProp + myParam);

AsLinq("c# expression as string")

The delegate overload might not work in all cases. In these cases the string expression overload should be used.