views:

327

answers:

3

Okay, so yesterday I managed to get the latest trunk builds of NHibernate and FluentNHibernate to work with my latest little project. (I'm working on a bug tracking application.) I created a nice data access layer using the Repository pattern.

I decided that my entities are nothing special, and also that with the current maturity of ORMs, I don't want to hand-craft the database. So, I chose to use FluentNHibernate's auto mapping feature with NHibernate's "hbm2ddl.auto" property set to "create".

It really works like a charm. I put the NHibernate configuration in my app domain's config file, set it up, and started playing with it. (For the time being, I created some unit tests only.) It created all tables in the database, and everything I need for it. It even mapped my many-to-many relationships correctly.

However, there are a few small glitches:

  • All of the columns created in the DB allow null. I understand that it can't predict which properties should allow null and which shouldn't, but at least I'd like to tell it that it should allow null only for those types for which null makes sense in .NET (eg. non-nullable value types shouldn't allow null).
  • All of the nvarchar and varbinary columns it created, have a default length of 255. I would prefer to have them on max instead of that.

Is there a way to tell the auto mapper about the two simple rules above?

If the answer is no, will it work correctly if I modify the tables it created? (So, if I set some columns not to allow null, and change the allowed length for some other, will it correctly work with them?)

FINAL EDIT: Big Thanks to everyone who dropped by and helped out. All of my issues with Fluent are solved now.

+3  A: 

You can use Auto Mapping Overrides to change how the Auto Mapper work, and you can also define Conventions, that will be used instead by the auto mapper.

Here is an example on how to use both the conventions and the overrides:

var mappings = new AutoPersistenceModel();
mappings.Conventions.Setup(s => s.Add<ColumnNullabilityConvention>());
mappings.UseOverridesFromAssemblyOf<AssemblyName>();

// This convention will set all properties to be not nullable

public class ColumnNullabilityConvention: IPropertyConvention, IPropertyConventionAcceptance
{
   public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
   {
       criteria.Expect(x => x.Nullable, Is.Not.Set);
   }

   public void Apply(IPropertyInstance instance)
   {
       instance.Not.Nullable();
   }
}

// This override will change "string" to use "text" instead of "varchar(255)".
// Also set the property to be not nullable

public class SomeOverrideInTheSameAssembly : IAutoMappingOverride<TypeName>
{
   public void Override(AutoMapping<TypeName> mapping)
   {
       mapping.Map(x => x.Property).CustomType("StringClob").CustomSqlType("text");
       mapping.Map(x => x.Property).Not.Nullable();
   }
}    

Check these links for more examples:

SztupY
Thank you very much!I managed to get it work with the following code:persistenceModel.Conventions.Add(ConventionBuilder.Property.Always(x => x.Length(16000)));This is a bit hacky though, because in SQL Server, the 16000 will set it to MAX, but it may not be good for other database servers. Is there a generic way to do it?
Venemo
Not really. Generally you should set it to "StringClob" customtype, because that should mean to NHibernate that it's a 'text' type, but even so some NHibernate database providers (like PostgreSQL) will disregard it. That's why for example in my Override example I have used both the CustomType and the CustomSqlType declaration, to set it to "text".Alternatively if you change the database schema underneath then NHibernate won't really care whether it's varchar or something bigger (text,blob,etc). But this is not a good solution either because of a lot of obvious things.
SztupY
Okay, I'll just leave it as it is for the time being. BTW, it is nice to see another Hungarian in here. :)
Venemo
+1  A: 

It's not widely known, but you can set many conventions from the Mappings section in your Configure code e.g.

Fluently.Configure()
  .Database(/* database config */)
  .Mappings(m =>
  {
    m.FluentMappings
      .AddFromAssemblyOf<Entity>()
      .Conventions.Add(PrimaryKey.Name.Is(x => "ID"));
  })

to set a Primary Key convention.

Edit: Clarification of what the PrimaryKey convention does:

The PrimaryKey convention is used to specify what the column of the primary key is, not the property. Discovering the property is a pure automapping exercise, while conventions are applied to ClassMaps and automappings. – James Gregory

This is the list of supported conventions (from the wiki):

Table.Is(x => x.EntityType.Name + "Table")
PrimaryKey.Name.Is(x => "ID")
AutoImport.Never()
DefaultAccess.Field()
DefaultCascade.All()
DefaultLazy.Always()
DynamicInsert.AlwaysTrue()
DynamicUpdate.AlwaysTrue()
OptimisticLock.Is(x => x.Dirty())
Cache.Is(x => x.AsReadOnly())
ForeignKey.EndsWith("ID")

See The Simplest Conventions section on the FNH wiki.

Tom Bushell
Thank you very much for your answer. I managed to get it to work using some custom conventions. However, after looking over the wiki, there is one more thing that bugs me. I can't get PrimaryKey.Name.Is(x => "ID") to work properly.
Venemo
Sorry, I've never used the PrimaryKey convention - I just copied the example from the wiki. Might be a bug. I suggest you post a question to the FNH support forum - http://support.fluentnhibernate.org/
Tom Bushell
Will do, thank you.
Venemo
+2  A: 

For your Id woes, you need to change the FindIdentity setting. It's covered in the automapping wiki page, although albeit briefly.

It should go something like this:

AutoMap.AssemblyOf<Entity>() // your usual setup
  .Setup(s =>
  {
    s.FindIdentity = m => m.Name == "ID";
  });

What this does is instruct the automapper to use your new lambda (m => m.Name == "ID") when trying to discover Ids. m is the property/member, and this lambda is called for each property on each entity; whichever you return true for is the id.

James Gregory
@James - so why didn't the PrimaryKey convention work for him? Bug in FNH? Misuse of the method?
Tom Bushell
The `PrimaryKey` convention is used to specify what the *column* of the primary key is, not the property. Discovering the property is a pure automapping exercise, while conventions are applied to ClassMaps and automappings.
James Gregory
Thanks for the clarification - guess the the name fooled both Venemo and me. I'll edit my answer.
Tom Bushell
Thank you, James, for your clarification.
Venemo