Monday, January 9, 2012

Mapping-by-Code - Property

I am spending quite a lot of time recently with NHibernate 3.2 mapping-by-code feature and as I've already mentioned, my most serious obstacle with this evaluation is that there is virtually no resources on the net. If these answers are everything the Web has to offer, there's a lot of space for improvement.

I am planning to prepare a series of posts covering the most important NHibernate's mapping features implemented using mapping-by-code. I'll probably follow Ayende's great series about XML mappings, at least in terms of ordering. I'm not going to describe the features itself, I'll rather focus on mapping interface. For features description - refer to Ayende's posts.

So let's start with <property>. Its equivalent in mapping-by-code is... Property - isn't it surprising? Well, in Fluent NHibernate it is called Map. There's no rocket science here and everything is pretty simple.

Property(x => x.Property, m =>
{
m.Column("columnName");
// or
m.Column(c =>
{
c.Name("columnName");
c.Default("defaultValue");

c.SqlType("varchar(max)");
c.Length(SqlClientDriver.MaxSizeForLengthLimitedString + 1);

c.NotNullable(true);
c.Check("len(columnName) > 1");
c.Precision(2);
c.Scale(2);
c.Index("column_idx");
c.Unique(true);
c.UniqueKey("column_uniq");
});

m.Type<string>(); // or IUserType
m.Update(true);
m.Insert(true);
m.Formula("arbitrary SQL expression");

m.Access(Accessor.Field); // or Accessor.Property or Accessor.NoSetter
// or
m.Access(typeof(CustomAccessor));

m.OptimisticLock(false);
m.Generated(PropertyGeneration.Insert); // or PropertyGeneration.Always or PropertyGeneration.Never

m.Lazy(true);
});

Mapped property goes as lambda expression (or string property name, if it is hidden) in first parameter and this is the only obligatory element - everything else has its defaults and can be skipped (and most often, it should). All the other attributes from the XML have its corresponding method in configuration object in second parameter.

One thing to note is that column can be configured using Column method either with simple string (when customising only name) or with column configuration, that allows to set different DDL-level column properties. Not every feature is implemented in every provider and not every feature makes sense for every column type obviously - i.e. Precision and Scale are for numerics only.

Side note: In the example above I'm specifying SqlType explicitly to varchar(max), but this line

Property(x => x.Name, m => m.Length(SqlClientDriver.MaxSizeForLengthLimitedString + 1));

will also produce varchar(max) in SQL Server, and that solution seems to be nicer.

Fluent NHibernate's equivalent

Map(x => x.Property, "columnName")
.Default("defaultValue")
.CustomSqlType("varchar(max)")
.Length(SqlClientDriver.MaxSizeForLengthLimitedString + 1)
.Not.Nullable()
.Check("len(columnName) > 1")
.Precision(2)
.Scale(2)
.Index("column_idx")
.Unique()
.UniqueKey("column_uniq")
.CustomType<string>()
.Update()
.Insert()
.Formula("arbitrary SQL expression")
.Access.Field()
// or .Access.Using<CustomAccessor>()
.OptimisticLock()
.Generated.Insert()
.LazyLoad()
.ReadOnly();

The only difference apart from different naming for some properties and Property vs. Map name itself is the ReadOnly method available in FNH. It is just a shortcut for setting both .Not.Insert() and .Not.Update().

9 comments:

  1. Fantastic post, thank you.

    A question, copying part of your example above, as follows:

    c.SqlType("varchar(max)");
    c.Length(SqlClientDriver.MaxSizeForLengthLimitedString + 1);

    and then using this field to search on a table (using Criterian restrictions) displays the following parater type when viewing with SQL Profiler:

    @p0 nvarchar(max)

    Without defining the SqlType and Length, it gives

    @p0 nvarchar(4000)

    How can I get this to be:

    @p0 varchar(20)

    If I edit the SqlType (eg to c.SqlType("varchar(20)"); it always seems to revert to

    @p0 nvarchar(4000)

    Thank you...



    ReplyDelete
    Replies
    1. Hi Adam,

      Thanks for your reply! I wish that worked for me but it doesn't...
      I've tried multiple permutations for Length and SqlType and in summary, regardless of what I have in SqlType, or even if that's excluded, if I put a value less than or equal to 4000 in Length then the SQL Profiler displays the parameter as

      @p0 nvarchar(4000)

      and if I have a value greater than 4000 in Length, the SQL Profiler displays the parameter as

      @p0 nvarchar(max)

      So it's obviously doing something with the Length value but I cannot get it be less than 4000...

      If Length(20) works for you I'm wondering if there's something on my configuration (DB or .Net side) that is different, do you have any suggestions?

      Thanks,
      Callum


      Delete
    2. Ah, I think I've now discovered the root of the problem. Looking at the NHibernate souce code, the SqlClientDriver contains the following method:

      protected static void SetDefaultParameterSize(IDbDataParameter dbParam, SqlType sqlType)
      {
      switch (dbParam.DbType)
      {
      case DbType.String:
      case DbType.StringFixedLength:
      dbParam.Size = SqlClientDriver.IsText(dbParam, sqlType) ? 1073741823 : 4000;
      break;
      case DbType.AnsiStringFixedLength:
      case DbType.AnsiString:
      dbParam.Size = 8000;
      break;
      ....

      This gets called via GenerateCommand and I believe is what is overriding the length settings...

      Delete
    3. Well, this is quite strange, I admit :) I've found this article, it may be helpful.

      Delete