-
Notifications
You must be signed in to change notification settings - Fork 934
/
Copy pathnhibernate_mapping_attributes.xml
307 lines (295 loc) · 20.3 KB
/
nhibernate_mapping_attributes.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
<!-- <!DOCTYPE chapter SYSTEM "../docbook-xml/docbookx.dtd"> -->
<chapter id="mapping-attributes">
<title>NHibernate.Mapping.Attributes</title>
<abstract id="mapping-attributes-abstract">
<title>What is NHibernate.Mapping.Attributes?</title>
<formalpara>
<title>NHibernate.Mapping.Attributes is an add-in for <ulink url="https://nhibernate.info/">NHibernate</ulink> contributed by Pierre Henri Kuaté
(aka <emphasis>KPixel</emphasis>); the former implementation was made by John Morris.</title>
<para>NHibernate require mapping streams to bind your domain model to your database. Usually, they are written (and maintained) in separated hbm.xml files.</para>
</formalpara>
<para>With NHibernate.Mapping.Attributes, you can use .NET attributes to decorate your entities and these attributes will be used to generate these mapping .hbm.xml (as files or streams). So you will no longer have to bother with these <emphasis>nasty</emphasis> xml files ;).</para>
<para>
<emphasis>Content of this library <ulink url="https://github.com/nhibernate/NHibernate.Mapping.Attributes">project</ulink>:</emphasis>
</para>
<para>
<orderedlist>
<listitem>
<para>
<emphasis role="strong">NHibernate.Mapping.Attributes</emphasis>: that is the only assembly you need (as an end-user).</para>
</listitem>
<listitem>
<para>
<emphasis role="strong">Test</emphasis>: a working sample using attributes and HbmSerializer for a NUnit TestFixture.</para>
</listitem>
<listitem>
<para>
<emphasis role="strong">Generator</emphasis>: the program used to generate the attributes and the HbmWriter of
the end-user assembly.</para>
</listitem>
<listitem>
<para>
<ulink url="http://mbunit.tigris.org/">
<emphasis role="strong">Refly</emphasis>
</ulink>: thanks to <ulink url="http://www.dotnetwiki.org/">Jonathan de Halleux</ulink> for this library which makes it so easy to generate code.</para>
</listitem>
</orderedlist>
</para>
<para>
<important>
<para>
This library is generated using the file <filename>/src/NHibernate.Mapping.Attributes/nhibernate-mapping.xsd</filename>
(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, a new release of NHibernate.Mapping.Attributes
should be regenerated before using it with a different version. It can be done by opening the Generator solution,
compiling and running the Generator project.
</para>
</important>
</para>
</abstract>
<section id="mapping-attributes-new">
<title>What's new?</title>
<orderedlist>
<listitem>
<para>It is possible to import classes by simply decorating them with <literal>[Import] class ImportedClass1 {}</literal>. Note that you must use <literal>HbmSerializer.Serialize(assembly)</literal>; The <literal><import/></literal> mapping will be added before the classes mapping. If you prefer to keep these imports in the class using them, you can specify them all on the class: <literal>[Import(ClassType=typeof(ImportedClass1))] class Query {}</literal>.</para>
</listitem>
<listitem>
<para><classname>[RawXmlAttribute]</classname> is a new attribute allowing to insert xml as-is in the mapping. This feature can be very useful to do complex mapping (eg: components). It may also be used to quickly move the mapping from xml files to attributes. Usage: <literal>[RawXml(After=typeof(ComponentAttribute), Content="<component name="...">...</component>")]</literal>. <methodname>After</methodname> tells after which kind of mapping the xml should be inserted (generally, it is the type of the mapping you are inserting); it is optional (in which case the xml is inserted on the top of the mapping). Note: At the moment, all raw xmls are prefixed by a <literal><!----></literal> (in the generated stream); this is a known side-effect.</para>
</listitem>
<listitem>
<para><classname>[AttributeIdentifierAttribute]</classname> is a new attribute allowing to provide the value of a defined "place holder". Eg: <programlisting>
public class Base {
[Id(..., Column="{{Id.Column}}")]
[AttributeIdentifier(Name="Id.Column", Value="ID")] // Default value
public int Id { ... }
}
[AttributeIdentifier(Name="Id.Column", Value="SUB_ID")]
[Class]
public class MappedSubClass : Base { }</programlisting>
The idea is that, when you have a mapping which is shared by many subclasses but which has minor differences (like different column names), you can put the mapping in the base class with place holders on these fields and give their values in subclasses. Note that this is possible for any mapping field taking a string (column, name, type, access, etc.). And, instead of <methodname>Value</methodname>, you can use <methodname>ValueType</methodname> or <methodname>ValueObject</methodname> (if you use an enum, you can control its formatting with <methodname>ValueObject</methodname>).
</para>
<para>The "place holder" is defined like this: <literal>{{XXX}}</literal>. If you don't want to use these double curly brackets, you can change them using the properties <methodname>StartQuote</methodname> and <methodname>EndQuote</methodname> of the class <classname>HbmWriter</classname>.</para>
</listitem>
<listitem>
<para>
It is possible to register patterns (using Regular Expressions) to automatically transform fully qualified names of properties types
into something else. Eg: <literal>HbmSerializer.Default.HbmWriter.Patterns.Add(@"Namespace\.(\S+), Assembly", "$1");</literal> will
map all properties with a not-qualified type name.
</para>
</listitem>
<listitem>
<para>
Two methods have been added to the <literal>HbmSerializer</literal> class, allowing generating mappings from a type or an assembly:
<literal>HbmSerializer.Default.Serialize(typeof(XXX))</literal> and
<literal>HbmSerializer.Default.Serialize(typeof(XXX).Assembly)</literal>. So it is no longer required to create a MemoryStream for
these simple cases. The output of these call can be directly added to your NHibernate <literal>Configuration</literal> instance:
<literal>cfg.AddInputStream(HbmSerializer.Default.Serialize(typeof(XXX)))</literal>.
</para>
</listitem>
<listitem>
<para>Two <methodname>WriteUserDefinedContent()</methodname> methods have been added to <classname>HbmWriter</classname>. They improve the extensibility of this library; it is now very easy to create a .NET attribute and integrate it in the mapping.</para>
</listitem>
<listitem>
<para>Attributes <classname>[(Jcs)Cache]</classname>, <classname>[Discriminator]</classname> and <classname>[Key]</classname> can be specified at class-level.</para>
</listitem>
<listitem>
<para>Interfaces can be mapped (just like classes and structs).</para>
</listitem>
<listitem>
<para>
A notable "bug" fix is the re-ordering of (joined-)subclasses. This operation may be required when a subclass extends another subclass.
In this case, the extended class mapping must come before the extending class mapping. Note that the re-ordering takes place only for
"top-level" classes (that is not nested in other mapped classes). Anyway, it is quite unusual to put an interdependent mapped subclasses
in a mapped class.
</para>
</listitem>
<listitem>
<para>There are also many other little changes: refer to the release notes for more details.</para>
</listitem>
</orderedlist>
</section>
<section id="mapping-attributes-howto">
<title>How to use it?</title>
<formalpara>
<title>The <emphasis>end-user class</emphasis> is <classname>NHibernate.Mapping.Attributes.HbmSerializer</classname></title>
<para>
This class <emphasis>serialize</emphasis> your domain model to mapping streams. You can either serialize classes one by one,
or serialize a whole assembly. Look at <classname>NHibernate.Mapping.Attributes.Test</classname> project for a working sample.
</para>
</formalpara>
<para>
The first step is to decorate your entities with attributes. You can use: <classname>[Class]</classname>,
<classname>[Subclass]</classname>, <classname>[JoinedSubclass]</classname> or <classname>[Component]</classname>. Then, you decorate
your members (fields/properties); they can take as many attributes as required by your mapping. Eg:
</para>
<programlisting>
[NHibernate.Mapping.Attributes.Class]
public class Example
{
[NHibernate.Mapping.Attributes.Property]
public string Name;
}</programlisting>
<para>
After this step, you use <classname>NHibernate.Mapping.Attributes.HbmSerializer</classname> (here, we use its
<methodname>Default</methodname> property, which is an instance you can use if you don't need/want to create it yourself):
</para>
<programlisting>var cfg = new NHibernate.Cfg.Configuration();
cfg.Configure();
// Enable validation (optional)
HbmSerializer.Default.Validate = true;
// Here, we serialize all decorated classes (but you can also do it class by class)
cfg.AddInputStream(HbmSerializer.Default.Serialize(
System.Reflection.Assembly.GetExecutingAssembly()));
// Now you can use this configuration to build your SessionFactory...</programlisting>
<note>
<para>
As you can see here, NHibernate.Mapping.Attributes is <emphasis role="strong">not</emphasis> (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 (like documentation)!
</para>
</note>
</section>
<section id="mapping-attributes-tips">
<title>Tips</title>
<orderedlist>
<listitem>
<para>
In production, you may want to generate a XML mapping file from NHibernate.Mapping.Attributes and use this file each time
the SessionFactory need to be built. Use: <literal>HbmSerializer.Default.Serialize(typeof(XXX).Assembly, "DomainModel.hbm.xml");</literal>.
It is slightly faster.
</para>
</listitem>
<listitem>
<para>
Use <methodname>HbmSerializer.Validate</methodname> to enable/disable the validation of generated xml streams (against
NHibernate mapping schema). This is useful to quickly find errors. (They are written in the StringBuilder property
<methodname>HbmSerializer.Error</methodname>.) If the error is due to this library, then see if it is a known issue and report it.
You are welcome to contribute a solution if you solve the trouble :).</para>
</listitem>
<listitem>
<para>Your classes, fields and properties (members) can be private; just make sure that you have the permission to access private members using reflection (<methodname>ReflectionPermissionFlag.MemberAccess</methodname>).</para>
</listitem>
<listitem>
<para>Members of a mapped classes are also seek in its base classes (until we reach <emphasis>mapped</emphasis> base class). So you can decorate some members of a (not mapped) base class and use it in its (mapped) sub class(es).</para>
</listitem>
<listitem>
<para>For a Name taking a <classname>System.Type</classname>, set the type with <methodname>Name</methodname><literal>="xxx"</literal> (as <classname>string</classname>) or <methodname>Name</methodname><literal>Type=typeof(xxx)</literal>; (add "<literal>Type</literal>" to "<methodname>Name</methodname>")</para>
</listitem>
<listitem>
<para>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 <emphasis>highly</emphasis> recommended to set it when you have more than one attribute on the same member.</para>
</listitem>
<listitem>
<para>
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 <literal><discriminator></literal>) on the identifier member. But don't forget
that the order matters (the <literal><discriminator></literal> must be after the <literal><id></literal>).
The order to use comes from the order of elements in the NHibernate mapping schema. Personally, I prefer using negative
numbers for these attributes (if they come first!).</para>
</listitem>
<listitem>
<para>You can add <classname>[HibernateMapping]</classname> on your classes to specify <literal><hibernate-mapping></literal> attributes (used when serializing the class in its stream). You can also use <methodname>HbmSerializer.Hbm*</methodname> properties (used when serializing an assembly or a type that is not decorated with <classname>[HibernateMapping]</classname>).</para>
</listitem>
<listitem>
<para>Instead of using a string for <methodname>DiscriminatorValue</methodname> (in <classname>[Class]</classname> and <classname>[Subclass]</classname>), you can use any object you want. Example: <programlisting>[Subclass(DiscriminatorValueEnumFormat="d", DiscriminatorValueObject=DiscEnum.Val1)]</programlisting> 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 <emphasis role="strong">before</emphasis>! For others types, It simply use the <methodname>ToString()</methodname> method of the object.</para>
</listitem>
<listitem>
<para>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 property <methodname>HbmSerializer.WriteDateComment</methodname>.</para>
</listitem>
<listitem>
<para>If you forget to provide a required xml attribute, it will obviously throw an exception while generating the mapping.</para>
</listitem>
<listitem>
<para>The recommended and easiest way to map <classname>[Component]</classname> is to use <classname>[ComponentProperty]</classname>. The first step is to put <classname>[Component]</classname> on the component class and map its fields/properties. Note that you shouldn't set the <methodname>Name</methodname> in <classname>[Component]</classname>. Then, on each member in your classes, add <classname>[ComponentProperty]</classname>. But you can't override <methodname>Access</methodname>, <methodname>Update</methodname> or <methodname>Insert</methodname> for each member.</para>
<para>There is a working example in <emphasis>NHibernate.Mapping.Attributes.Test</emphasis> (look for the class <classname>CompAddress</classname> and its usage in others classes).</para>
</listitem>
<listitem>
<para>Another way to map <classname>[Component]</classname> 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. <emphasis>NHibernate.Mapping.Attributes.Test</emphasis> contains the classes <classname>JoinedBaz</classname> and <classname>Stuff</classname> which both use the component <classname>Address</classname>.</para>
<para>Basically, it is done by adding <programlisting>[Component(Name = "MyComp")] private class SubComp : Comp {}</programlisting> in each class. One of the advantages is that you can override <methodname>Access</methodname>, <methodname>Update</methodname> or <methodname>Insert</methodname> for each member. But you have to add the component subclass in <emphasis role="strong">each</emphasis> class (and it can not be inherited). Another advantage is that you can use <classname>[AttributeIdentifier]</classname>.</para>
</listitem>
<listitem>
<para>Finally, whenever you think that it is easier to write the mapping in XML (this is often the case for <classname>[Component]</classname>), you can use <classname>[RawXml]</classname>.</para>
</listitem>
<listitem>
<formalpara><title>About customization</title>
<para><classname>HbmSerializer</classname> uses <classname>HbmWriter</classname> 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).</para>
</formalpara>
<para>Use the property <methodname>HbmSerializer.HbmWriter</methodname> to change the writer used (you may set a subclass of <classname>HbmWriter</classname>).</para>
</listitem>
</orderedlist>
<para>Example using some of these tips: (0, 1 and 2 are position indexes)
<programlisting>// Don't put it after [ManyToOne] !!!
[NHibernate.Mapping.Attributes.Id(0, TypeType=typeof(int))]
[NHibernate.Mapping.Attributes.Generator(1, Class="uuid.hex")]
[NHibernate.Mapping.Attributes.ManyToOne(2,
ClassType=typeof(Foo), OuterJoin=OuterJoinStrategy.True)]
private Foo Entity;</programlisting>
Generates:
<programlisting><![CDATA[
<id type="Int32">
<generator class="uuid.hex" />
</id>
<many-to-one name="Entity" class="Namespaces.Foo, SampleAssembly" outer-join="true" />
]]></programlisting>
</para>
</section>
<section id="mapping-attributes-todo">
<title>Known issues and TODOs</title>
<para>First, read TODOs in the source code ;)</para>
<para>A <methodname>Position</methodname> property has been added to all attributes to order them. But there is still a problem:</para>
<para>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:<programlisting><![CDATA[<p>
<c>
<x />
</c>
<x />
</p>]]></programlisting>
</para>
<para>In this case, when writing:
<programlisting>[Attributes.P(0)]
[Attributes.C(1)]
[Attributes.X(2)]
[Attributes.X(3)]
public MyType MyProperty;</programlisting>
X(3) will always belong to C(1) ! (as X(2)).
</para>
<para>It is the case for <literal><dynamic-component></literal> and <literal><nested-composite-element></literal>.</para>
<para>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 <literal><dynamic-component></literal>. The reason is that the file <filename>nhibernate-mapping.xsd</filename> tells how elements are built and in which order, and NHibernate.Mapping.Attributes use this order.</para>
<para>Anyway, the solution would be to add a <methodname>int ParentNode</methodname> property to BaseAttribute so that you can create a real graph...</para>
<para>For now, you can fallback on <classname>[RawXml]</classname>.</para>
<para>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!</para>
<para>As a side note, it would be nice to write a better TestFixture than <emphasis>NHibernate.Mapping.Attributes.Test</emphasis> :D.</para>
</section>
<section id="mapping-attributes-devnotes">
<title>Developer Notes</title>
<para>Any change to the schema (<filename>nhibernate-mapping.xsd</filename>) implies:</para>
<orderedlist>
<listitem>
<para>
Checking if there is any change to do in the Generator (like updating <literal>KnowEnums</literal> /
<literal>AllowMultipleValue</literal> / <literal>IsRoot</literal> / <literal>IsSystemType</literal> /
<literal>IsSystemEnum</literal> / <literal>CanContainItself</literal>).
</para>
</listitem>
<listitem>
<para>
Updating <filename>/src/NHibernate.Mapping.Attributes/nhibernate-mapping.xsd</filename> (copy/paste)
and running the Generator again (even if it wasn't modified).
</para>
</listitem>
<listitem>
<para>
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 <filename>NHibernate.Mapping.Attributes.csproj</filename>).
</para>
</listitem>
</orderedlist>
<para>This implementation is based on NHibernate mapping schema. So there is probably lot of "standard schema features" that are not supported...</para>
<para>The version of NHibernate.Mapping.Attributes should be the version of the NHibernate schema used to generate it (=> the version of NHibernate library).</para>
<para>
In the design of this project, performance is a (<emphasis>very</emphasis>) minor goal :). Easier implementation and maintenance
are far more important because you can use this library to generate statically the mapping files and use them instead in production.
(Cf. the first tip in <xref linkend="mapping-attributes-tips"/>.)
</para>
</section>
</chapter>