What is NHibernate.Mapping.Attributes?
NHibernate.Mapping.Attributes is an add-in for NHibernate contributed by Pierre Henri Kuaté (aka KPixel); the former implementation was made by John Morris. NHibernate require mapping streams to bind your domain model to your database. Usually, they are written (and maintained) in separated hbm.xml files.
With NHibernate.Mapping.Attributes, you can use .NET attributes to decorate your entities and this attributes will be used to generate these mapping .hbm.xml (as files or streams). So you will no longer have to bother with this nasty xml files ;).
Content of this library.
NHibernate.Mapping.Attributes: That the only project you need (as end-user)
Test: a working sample using attributes and HbmSerializer as NUnit TestFixture
Generator: The program used to generate attributes and HbmWriter
Refly : Thanks to Jonathan de Halleux for this library which make it so easy to generate code
This library is generated using the file /src/NHibernate.Mapping.Attributes/nhibernate-mapping-2.0.xsd (which is embedded in the assembly to be able to validate generated XML streams). As this file can change at each new release of NHibernate, you should regenerate it before using it with a different version (open the Generator solution, compile and run the Generator project). But, no test has been done with versions prior to 0.8.
The end-user class is NHibernate.Mapping.Attributes.HbmSerializer. This class serialize your domain model to mapping streams. You can either serialize classes one by one or an assembly. Look at NHibernate.Mapping.Attributes.Test project for a working sample.
The first step is to decorate your entities with attributes; you can use: [Class], [Subclass], [JoinedSubclass] or [Component]. Then, you decorate your members (fields/properties); they can take as many attributes as required by your mapping. Eg:
[NHibernate.Mapping.Attributes.Class]
public class Example
{
[NHibernate.Mapping.Attributes.Property]
public string Name;
}After this step, you use NHibernate.Mapping.Attributes.HbmSerializer: (here, we use Default which is an instance you can use if you don't need/want to create it yourself).
System.IO.MemoryStream stream = new System.IO.MemoryStream(); // where the xml will be written
NHibernate.Mapping.Attributes.HbmSerializer.Default.Validate = true; // Enable validation (optional)
// Here, we serialize all decorated classes (but you can also do it class by class)
NHibernate.Mapping.Attributes.HbmSerializer.Default.Serialize(
stream, System.Reflection.Assembly.GetExecutingAssembly() );
stream.Position = 0; // Rewind
NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration();
cfg.Configure();
cfg.AddInputStream(stream); // Use the stream here
stream.Close();
// Now you can use this configuration to build your SessionFactory...As you can see here: NHibernate.Mapping.Attributes is not (really) intrusive. Setting attributes on your objects doesn't force you to use them with NHibernate and doesn't break any constraint on your architecture. Attributes are purely informative!
Use HbmSerializer.Validate to enable/disable the validation of generated xml streams (against NHibernate mapping schema); this is useful to quickly find errors (they are written in StringBuilder HbmSerializer.Error). If the error is due to this library then see if it is a know issue and report it; you can contribute a solution if you solve the problem :)
Your classes, fields and properties (members) can be private; just make sure that you have the permission to access private members using reflection (ReflectionPermissionFlag.MemberAccess).
Members of a mapped classes are also seek in its base classes (until we reach mapped base class). So you can decorate some members of a (not mapped) base class and use it in its (mapped) sub class(es).
For a Name taking a System.Type, set the type with Name="xxx" (as string) or NameType=typeof(xxx); (add "Type" to "Name")
By default, .NET attributes don't keep the order of attributes; so you need to set it yourself when the order matter (using the first parameter of each attribute); it is highly recommended to set it when you have more than one attribute on the same member.
As long as there is no ambiguity, you can decorate a member with many unrelated attributes. A good example is to put class-related attributes (like <discriminator>) on the identifier member. But don't forget that the order matters (the <discriminator> must be after the <id>). The order used comes from the order of elements in the NHibernate mapping schema. Personally, I prefer using negative numbers for these attributes (if they come before!).
You can add [HibernateMapping] on your classes to specify <hibernate-mapping> attributes (used when serializing the class in its stream). You can also use HbmSerializer.Hbm* properties (used when serializing an assembly or a type that is not decorated with [HibernateMapping]).
Instead of using a string for DiscriminatorValue (in [Class] and [Subclass]), you can use any object you want. Example:
[Subclass(DiscriminatorValueEnumFormat="d", DiscriminatorValueObject=DiscEnum.Val1)]
Here, the object is an Enum, and you can set the format you want (the default value is "g"). Note that you must put it before! For others types, It simply use the ToString() method of the object.
If you are using members of the type Nullables.NullableXXX (from the library Nullables), then they will be mapped to Nullables.NHibernate.NullableXXXType automatically; don't set Type="..." in [Property] (leave it null). Thanks to Michael Third for the idea :)
Each stream generated by NHibernate.Mapping.Attributes can contain a comment with the date of the generation; You may enable/disable this by using the method WriteDateComment.
If you forget to provide a required xml attribute, it will obviously throw an exception while generating the mapping.
The recommended and easiest way to map [Component] is to use [ComponentProperty]. The first step is to put [Component] on the component class and map its fields/properties. Note that you shouldn't set the Name in [Component]. Then, on each member in your classes, add [ComponentProperty]. But you can't override Access, Update or Insert for each member.
There is a working example in NHibernate.Mapping.Attributes.Test (look for the class CompAddress and its usage in others classes).
One last thing: ComponentPropertyAttribute inherits from DynamicComponentAttribute to easily write it just after <component> elements in the XML stream.
Another way to map [Component] is to use the way this library works: If a mapped class contains a mapped component, then this component will be include in the class. NHibernate.Mapping.Attributes.Test contains the classes JoinedBaz and Stuff which both use the component Address.
Basically, it is done by adding
[Component(Name = "MyComp")] private class SubComp : Comp {}in each class. One of the advantages is that you can override Access, Update or Insert for each member. But you have to add the component subclass in each class (and it can not be inherited).
About customization. HbmSerializer uses HbmWriter to serialize each kind of attributes. Their methods are virtual; so you can create a subclass and override any method you want (to change its default behavior).
Use the property HbmSerializer.HbmWriter to change the writer used (you may set a subclass of HbmWriter).
Example using some this tips: (0, 1 and 2 are position indexes)
[NHibernate.Mapping.Attributes.Id(0, TypeType=typeof(int))] // Don't put it after [ManyToOne] !!!
[NHibernate.Mapping.Attributes.Generator(1, Class="uuid.hex")]
[NHibernate.Mapping.Attributes.ManyToOne(2, ClassType=typeof(Foo), OuterJoin=OuterJoinStrategy.True)]
private Foo Entity;
Generates:
<id type="Int32">
<generator class="uuid.hex" />
</id>
<many-to-one name="Entity" class="Namespaces.Foo, SampleAssembly" outer-join="true" />
First, read TODOs in the source code ;)
A Position property has been added to all attributes to order them. But there is still a problem:
When a parent element "p" has a child element "x" that is also the child element of another child element "c" of "p" (preceding "x") :D Illustration:
<p>
<c>
<x />
</c>
<x />
</p>
In this case, when writing:
[Attributes.P(0)]
[Attributes.C(1)]
[Attributes.X(2)]
[Attributes.X(3)]
public MyType MyProperty;X(3) will always belong to C(1) ! (as X(2)).
It is the case for <dynamic-component> and <nested-composite-element>.
Another bad news is that, currently, XML elements coming after this elements can not be included in them. Eg: There is no way put a collection in <dynamic-component>. The reason is that the file nhibernate-mapping-2.0.xsd tells how elements are built and in which order, and NHibernate.Mapping.Attributes use this order.
Anyway, the solution would be to add a int ParentNode property to BaseAttribute so that you can create a real graph...
Actually, there is no other know issue nor planned modification. This library should be stable and complete; but if you find a bug or think of an useful improvement, contact us!
On side note, it would be nice to write a better TestFixture than NHibernate.Mapping.Attributes.Test :D
Any change to the schema (nhibernate-mapping-2.0.xsd) implies:
Checking if there is any change to do in the Generator (like updating KnowEnums / AllowMultipleValue / IsRoot / IsSystemType / IsSystemEnum / CanContainItself)
Updating /src/NHibernate.Mapping.Attributes/nhibernate-mapping-2.0.xsd (copy/paste) and running the Generator again (even if it wasn't modified)
Running the Test project and make sure that no exception is thrown. A class/property should be modified/added in this project to be sure that any new breaking change will be caught (=> update the reference hbm.xml files and/or the project NHibernate.Mapping.Attributes-1.1.csproj)
This implementation is based on NHibernate mapping schema; so there is probably lot of "standard schema features" that are not supported...
The version of NHibernate.Mapping.Attributes should be the version of the NHibernate schema used to generate it (=> the version of NHibernate library).
In the design of this project, performance is a (very) minor goal :) Easier implementation and maintenance are far more important.