Monday, February 13, 2012

Mapping-by-Code - entity-level mappings

We're finally reaching the end of mapping-by-code series. In the last post about mapping possibilities I will cover all options mapped directly at the entity level, that do not map to a column in the database. They define different entity behaviors and customization options and majority of them translates to elements or attributes in XML <class> mapping. Let's go through these possibilities.

Table("tableName");
Schema("schemaName");
Catalog("catalogName");

These are pretty obvious ones - they define where in the database the entity is represented.

EntityName("entityName");
DynamicInsert(true);
DynamicUpdate(true);
BatchSize(10);
Lazy(true);
SelectBeforeUpdate(true);
Mutable(true);
Synchronize("table1", "table2");
SchemaAction(NHibernate.Mapping.ByCode.SchemaAction.All); // or .Drop, .Export, .None, .Update, .Validate
  • EntityName allows to define an alias for the entity - useful when we're mapping the same class more than once.
  • DynamicInsert and DynamicUpdate decide whether to restrict constructed SQL queries only to properties modified explicitly (by default - with these options turned off - NHibernate inserts/updates all properties everytime).
  • BatchSize is an last resort solution for Select N+1 problem - it sets up batching when loading entities in a loop.
  • Lazy(false) completely turns off lazy loading for the entity (no proxy is created).
  • SelectBeforeUpdate is to fetch the data before update, surprisingly - another way of dealing with concurrency.
  • Mutable(false) reduces the NHibernate's infrastructure when the entity is read-only for our application (i.e. when mapped to a database view or to a lookup-only table).
  • Synchronize informs NHibernate that the entity is derived from another and it should care not to return stale data when querying the derived entity.
  • SchemaAction is for hbm2ddl tool to decide what should it do when creating session factory.
Cache(c =>
{
c.Usage(CacheUsage.NonstrictReadWrite); // or .ReadOnly, .ReadWrite, .Transactional
c.Include(CacheInclude.All); // or .NonLazy
c.Region("regionName");
});

Cache is for second-level caching. We need to turn it on explicitly for every entity we want to cache by defining Usage model at least.

Filter("filterName", f => f.Condition("filter SQL condition"));
Where("SQL condition");

Filter and Where are two different ways of narrowing the scope of entities loaded and managed by NHibernate.

SqlInsert("custom SQL");
SqlUpdate("custom SQL");
SqlDelete("custom SQL");
Subselect("custom SQL");

These four are for customizing CRUD operations on the entity. Useful i.e. when access to the table should be done through the stored procedures only.

Loader("loaderReference");
Persister<CustomPersister>();
Proxy(typeof(ProxyType));

And these three are even more low-level possibilities for customization. Haven't explored it thoroughly, but I don't think there are lot of scenarios when they are needed.

There are some XML attributes that do not have an equivalent in mapping-by-code yet. These are tuplizer, resultset, abstract, polymorphism, optimistic-lock, check, rowid, node and import. Apart from optimistic-lock, they don't look important.

Fluent NHibernate's equivalents

FNH offers a bit different subset of entity-level options. Some of mapping-by-code deficiencies are covered (like OptimisticLock, Polymorphism, ImportType, CheckConstraint, Tuplizer), some features are missing instead (like Catalog, Synchronize, Loader). Here are the options available:

Table("tableName");
Schema("schemaName");

EntityName("entityName");
DynamicInsert();
DynamicUpdate();
BatchSize(10);
LazyLoad();
OptimisticLock.All(); // or .Dirty, .None, .Version
SelectBeforeUpdate();
ReadOnly();
Polymorphism.Explicit(); // or .Implicit
SchemaAction.All(); // or .Drop, .Export, .Update, .Validate, .Custom

Cache.IncludeAll() // or .IncludeNonLazy
.Region("regionName")
.NonStrictReadWrite(); // or .ReadOnly, .ReadWrite, .Transactional, .CustomUsage<>

ApplyFilter("filterName", "SQL condition");
Where("SQL condition");

CheckConstraint("check");
ImportType<Import>().As("alternativeName");

SqlInsert("sql query").Check.None(); // or .RowCount
SqlUpdate("sql query");
SqlDelete("sql query");
SqlDeleteAll("sql query");
Subselect("sql query");
StoredProcedure("command", "sql query").Check.None();

Persister<CustomPersister>();
Proxy(typeof(ProxyType));
Tuplizer(TuplizerMode.Xml, typeof(Tuplizer));

There are some name changes when compared to XML attributes or elements names. LazyLoad is for lazy, CheckConstraint for check, ReadOnly for mutable="false" and ApplyFilter covers filter element. StoredProcedure is used internally within SqlXYZ elements - I don't know why this method is in public API. There is also one elements that should not be there - SqlDeleteAll do not have an equivalent in XML mapping and can't be used.


And that's all for mappings exploration! I'll probably summarize the series in a separate post or two.

3 comments:

  1. I just found your blog, from one of your NH / FNH answers on Stack Overflow.

    Very nice job - not only do you describe the NH MbC with nice examples, but also the FNH equivalents, which I've never seen collected in one place before (you might want to tag them all with "FNH", however - I almost missed them because I'm not currently interested in NH MbC).

    -Tom Bushell

    ReplyDelete
  2. Excellent job Adam .. The only complete work on Mapping by Code I have found on the internet ... You saved me a lot of working hours trying to determnine how MbC works :) :) :) !

    ReplyDelete
  3. Hi Adam..firstly thanks a lot for this blog without which there was no way for me to get into it.

    I have a problem I am dealing with right now where there is a very complex object structure.

    I created a table per class hierarchy and same entity with exact same columns are used by multiple tables in my database.

    I created multiple class mapping files pointing to same entity but a different table but it is failing as with error stating entity is already mapped. Os there any way you can think of wlse I would have to create 12 empty implementation classes for every table to make it work. Please advise

    ReplyDelete

Note: Only a member of this blog may post a comment.