[Study Note] NHibernate in Action 20100522

Part 2 NHibernate deep dive

3 Writing and mapping classes

business objects / entities

domain model

mapping metadata

3.1 The CaveatEmptor application

3.1.1 Analyzing the business domain

A software development effor begins with analysis of the problem domain.

Developers of object-oriented software analyze the business model and create an object model, still at the conceptual level (no C# code).

domain model – an object-oriented model of entities from the problem domain. It’s an abstract view of the real word.

3.1.2 The CaveatEmptor domain model

The entities in a domain model should encapsulate state and behavior.

rich object model with complex associations, interactions, and inheritance relationships.

Patterns of Enterprise Application Architecture [Fowler 2003]

Domain-Driven Design [Evans 2004]

3.2 Implementing the domain model

automated / transparent

3.2.1 Addressing leakage of concerns

The domain-model implementation is such an important piece of code that it shouldn’t depend on other .NET APIs.

domain model should be “concerned” only with modeling the business domain.

cross-cutting concerns

transparent persistence

3.2.2 Transparent and automated persistence

NHibernate can automatically persist your changes in a way that is transparent to your domain model.

  • NHibernate doesn’t require that any special base classes or interfaces be inherited or implemented by persistent class. Nor are any special classes used to implement properties or associations.
  • Persistent class may be reused outside the context of persistence.
  • In a system with transparent persistence, objects aren’t aware of the underlying data store; they need not even be aware that they’re being persisted or retrieved. Persistence concerns are externalized to a generic persistence manager interface -  in the case of NHibernate, the ISession and IQuery interface.

Testability is a basic requirement for applications with rich domain models.

3.2.3 Writing POCOs

POCO, Plain Old CLR Object

A POCO declares business methods, which define behavior, and properties, which represent state.

NHibernate doesn't require persistent classes to be serializable.

NHibernate requires a default parameterless contructor for every persistent class.

Note that .NET automatically adds a public paramenterless constructor to classes if you haven't writen one in the code.

The properties of the POCO implement the attributes of your business entities.

NHibernate doesn’t require that properties be declared public; it can easily use private ones too.

3.2.4 Implementing POCO associations

scaffolding code

single-valued end / many-valued end

NHibernate requires interfaces for collection-typed attributes. … At runtime, NHibernate wraps the collection instance with an instance of one of NHibernate’s own classes.

grouping of operations

many-to-many association

3.2.5 Adding logic to properties

custom type

dirty checking

NHibernate automatically detects objectstate changes in order to synchronize the updated state with the database.

NHibernate compares the objects by values – not by object identity – to determine whether the property’s persistent state needs to be updated.

Collections are compared by identity!

Note that collections shouldn’t have a setter at all.

3.3 Defining the mapping metadata

ORM tools require a metadata format for the application to specify the mapping between classes and tables, properties and columns, associations and foreign keys, .NET types and SQL types. This information is called the object/relational mapping metadata. It defines the transformation between the different data type systems and relationship representations.

3.3.1 Mapping using XML

backlash against the overuse of XML

  1. Many existing metadata formats weren’t designed to be readable and easy to edit by hand. … lack of sensible defaults for attribute and element values
  2. Metadata-based solutions were often used inappropriately.
  3. Good XML editors, especially in IDEs, aren’t as common as good .NET coding environments.

object identity

NHibernate will use .NET reflection to discover the type of this property and deduce how to map it to the SQL column, assuming they have compatible types.

mapping data type

3.3.2 Attribute-oriented programming

3.4 Basic property and class mappings

3.4.1 Property mapping overview

A typical NHibernate property mapping defines a property name, a database column name, and the name of an NHibernate type. It maps a .NET property to a table column.

NHibernate uses reflection to determine the .NET type of the property.

3.4.2 Using derived properties

The value of a derived property is calculated at runtime by evaluating an expression. … define the expression using the formula attribute.

3.4.3 Property access strategies

nosetter.* | field.camelcase-underscore

<hibernate-mapping default-access=”…” >

HbmSerializer.HbmDefaultAccess

customized property access strategy

NHibernate.Property.IPropertyAccessor

[Property(AccessType=typeof(MyPrpertyAccessor))]

3.4.4 Taking advantage of the reflection optimizer

disable reflection optimizer

<property name=”hibernate.use_reflection_optimizer”>false</property>

Environment.UseReflectionOptimizer = false;

select the CodeDom provider

<property name=”hibernate.bytecode.provider”>codedom</property>

null | lcg

Environment.BytecodeProvider

Environment.BuildBytecodeProvider()

NHibernate.Bytecode.IBytecodeProvider

3.4.5 Controlling insertion and updates

<property name=”Name” column=”Name” type=”String” insert=”false” update=”false” />

immutable classes is that you’ve decided should never be updated after they’ve been created.

dynamic-insert / dynamic-update

Enabling either of these settings causes NHibernate to generate SQL at runtime instead of using the SQL cached at startup time. The performance and memory cost of doing this is unually small.

3.4.6 Using quoted SQL identifiers

quote a table or column name with backticks (`) in the mapping document, NHibernate always quotes this identifier in the generated SQL.

3.4.7 Naming convertions

INamingStrategy

Configuration cfg = new Configuration();

cfg.NamingStrategy = new CENamingStrategy();

ISessionFactory sessionFactory = cfg.configure().BuildSessionFactory();

3.4.8 SQL schema

… specify a default schema using hibernate.default_schema configuration option, doing so offers some small performance benefits.

3.4.9 Declaring class name

Configuration.SetDefaultNamespace()

Configuration.SetDefaultAssembly()

3.4.10 Manipulating metadata at runtime

NHibernate.Mapping – Column, SimpleValue, and Property

NHibernate.Type – StringType

… adding a property to an existing persistent class mapping is easy …

Once an ISessionFactory is created, its mappings are immutable.

ISessionFactory – GetClassMetadata(), GetCollectionMetadata()

3.5 Understanding object identify

3.5.1 Identity versus equality

Object identity, object.ReferenceEquals(), is a notion defined by the CLR environment. Two object references are identical if they point to the same memory location.

Object equality is a notion defined by classes that implement the Equals() method ( or the operator == ), sometimes also referred to as equivalence.

  • Object identity – Objects are identical if they occupy the same memory location. This can be checked by using object.ReferenceEquals().
  • Object equality – Objects are equal if they have the same value, as defined by the Equals(object o) method. Classes that don’t explicitly override this method inherit the implementation defined by System.Object, which compares object identity.
  • Database identity – Objects stored in a relational database are identical if they represent the same row or, equivalenty, share the same table and primary key value.
3.5.2 Database identity with NHibernate
  • The value of the identifier property of a persistent instance
  • The value returned by ISession.GetIdentifier(object o)
3.5.3 Choosing primary keys

primary key

A candidate key is a column or set of columns that uniquely identifies a specific row of the table.

  1. The value or values are never null.
  2. Each row has a unique value or values.
  3. The value or values of a particular row never change.

A natural key is a key with business meaning: an attribute or combination of attributes that is unique by virtue of its business semantics.

if a candidate key attribute has meaning outside the database context, it’s a natural key, whether or not it’s automatically generated.

Experience has shown that natural keys almost alway cause problems in the long run.

A good primary key must be unique, constant, and required (never null or unknown).

Surrogate keys ( also called synthetic identifiers ) have no business meaning -  they’re unique values generated by the database or application.

IIdentifierCenerator

composite keys , natural keys composed of multiple table columns.

其实如果有机会的话,在Oracle的数据环境下,我倾向于使用 GUID 来作为主键的生成方式,这样似乎可以减少访问数据库的次数。手头的项目暂时只能把主键都切换到 sequence 生成方式(我以为,这种生成方式在插入数据的时候需要先查询一次 sequence,可能会稍微损失一点性能)。

3.6 Fine-grained object model

A major objective of the NHibernate project is support for fine-grained object  models, which we isolated as the most important requirement for a rich domain model.

… fine-grained means “more classes than tables” …

3.6.1 Entity and value types

In .NET, all classes are of equal standing: all objects have their own identity and lifecycle, and all class instances are passed by reference. Only primitive types are passed by value.

  • An object of entity type has its own database identity (primary-key value). An object reference to an entity is persisted as a reference in the database ( a foreign-key value). An entity has its own lifecycle; it may exist independently of any other entity.
  • An object of value type has no database identity; it belongs to an entity, and its persistent state is embedded in the table row of the owning entity. Value types don’t have identifier or identifier properties. The lifespan of a value-type instance is bounded by the lifespan of the owning entity.
3.6.2 Using components

… in the context of ORM, a composed class is often a candidate value type.

.NET has no concept of composition -  a class or attribute can’t be marked as a component or composition.

[RawXml] Attribute

<parent> element

  • Shared reference aren’t possible
  • There is no elegant way to represent a null reference to an Address.

3.7 Introducing associations

3.7.1 Unidirectional associations
3.7.2 Mutiplicity

from Bid to  Item is a many-to-one association, from Item to Bid is a one-to-many association.

from Employee to Department is a many-to-one association, from Department to Employee  is one-to-many association.

我觉得这个关系有点像主表和字典表之间的对应。

3.7.3 The Simplest possible association

<many-to-one name="Item" column="ITEM_ID" class="Item" not-null="true" />

<many-to-one … not-found=”ignore|exception” />

3.7.4 Making the association bidiretional

The inverse attribute tells NHIbernate that the collection is a mirror image of the many-to-one association on the other side.

联系实际,如果在单位类映射文件里面,在雇员集合上设定:inverse=true,如此一来,雇员的单位属性修改的时候会触发update,而仅仅对单位的雇员们集合修改的时候不触发。

cascading saves

cascading deletes

3.7.5 A parent/child relationship

parent/child relationship the parent entity is responsible for the lifecycle of its associated child entities.

cascade=”all-delete-orphan”

  • Any newly instantiated Bid becomes persistent if the Bid is referenced by a persistent Item. ( as is also the case with cascade=”save-update”). Any persistent Bid should be deleted if it’s referenced by an Item when the item is deleted.
  • Any persistent Bid should be deleted if it’s removed from the bids collection of a persistent Item. (NHibernate will assume that it was only referenced by this item and consider it an orphan.)

Keeping your association mappings simple and using NHibernate queries for more complex tasks.

3.8 Mapping class inheritance

catalogued by Scott Ambler [Ambler 2002]

  • Table per concrete class – Discard polymorphism and inheritance relationships from the relation model.
  • Table per class hierarchy – Enable polymorphism by denormalizing the relational model and using a type a type-discriminator column to hold type information.
  • Table per subclass – Represent “is a” (inheritance) relationships as “has a”(foreign key) relationships.
3.8.1 Table per concrete class

polymorphic queries queries that return objects of all classes that match the interface of the queries class.

This approach (only) for the top level of your class hierarchy, where polymorphism isn’t usually required.

3.8.2 Table per class hierarchy

type discriminator

This mapping strategy is a winner in terms of both performance and simplicity.

major problem: NOT NULL constraints

3.8.3 Table per subclass

represent inheritance relationships as relational foreign-key associaations. Every subclass that declares persistent properties – including abstract classes and even interface – has its own table.

<joined-subclass name=”CreditCard” table=”CREDIT_CARD”>

    <key column=”CREDIT_CARD_ID”>

    <property name=”Type” column=”Type” />

</joined-subclass>

Even though this mapping strategy is deceptively simply, … performance may be unacceptable for complex class hierarchies.

3.8.4 Choosing a strategy

NHibernate won’t try to instantiate an abstract class, even if you query or load it.

  • If you don’t require polymorphic associations or queries, lean toward the table-per-class strategy. If you require polymorphic associations (an association to a base class, hence to all classes in the hierarchy with dynamic resolution of the concrete class at runtiime) or queries, and subclasses declare relatively few properties  (particularly if the main difference between subclass is in their behavior), lean toward the table-per-class-hierarchy model.
  • If you require polymorhpic associations or queries, and subclass declare many properties (subclasses differ mainly by the data they hold), lean toward the table-per-subclass approach.

By default, choose table-per-class-hierarchy for simple problems.

Note that you may also use <subclass> and <joined-subclass> mapping elements in a separate mapping file (as a top-level element, instead of <class>).

3.9 Summary

posted on 2010-05-24 02:23  zhaorui  阅读(488)  评论(0编辑  收藏  举报

导航