With Fluent Nhibernate Automapper, one quickly realizes that the out-of-the-box behavior for varchar columns is less than ideal. First you discover that every string property was exported as varchar(255) and you need to make a column to be varchar(max). But ideally, you wouldn't have to make every string a varchar(max), right? So you head down that well-trodden path of finding the best way to exert control over the process without breaking out of the various elegant patterns at play...
If you want to have your resulting database varchar columns specified at different lengths, you look to convention classes to make it happen. You might try create name-specific conditions or generally use some naming pattern that you detected inside your convention class.
Neither is ideal. Overloading a name for the purpose of indicating an intended spec in another part of the code is unfortunate- your name should just be a name. Nor should you have to modify convention code every time you need to add or modify a limited-length class property. So how can you write a convention class that gives you control and provides that control in a simple and elegant way?
It'd be sweet if you could just decorate your property like I did for the Body property here:
using System;
using MyDomain.DBDecorations;
namespace MyDomain.Entities {
[Serializable]
public class Message
{
public virtual string MessageId { get; set; }
[StringLength(4000)] public virtual string Body { get; set; }
}
}
If this could work, we'd have the control over each string independently, and we'd be able to specify it directly in our entity.
Before I start a maelstrom over separation of database from application, let me point out that this is not specifically a database directive (I made a point of not calling the attribute 'Varchar'). I prefer to characterize this as an augmentation of the System.string, and in my own little universe I'm happy with that. Bottom line, I want a convenience!
To do this, we need to define the decoration we want to use:
using System;
namespace MyDomain.DBDecorations
{
[AttributeUsage(AttributeTargets.Property)]
public class StringLength : System.Attribute
{
public int Length = 0;
public StringLength(int taggedStrLength)
{
Length = taggedStrLength;
}
}
}
Finally, we need to use a string length convention to use the entity's property decoration. This part may not seem pretty, but it does the job, and the good news is that you won't have to look at it again!
StringColumnLengthConvention.cs:
using System.Reflection;
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.AcceptanceCriteria;
using FluentNHibernate.Conventions.Inspections;
using FluentNHibernate.Conventions.Instances;
namespace MyMappings
{
public class StringColumnLengthConvention : IPropertyConvention, IPropertyConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria) { criteria.Expect(x => x.Type == typeof(string)).Expect(x => x.Length == 0); }
public void Apply(IPropertyInstance instance)
{
int leng = 255;
MemberInfo[] myMemberInfos = ((PropertyInstance)(instance)).EntityType.GetMember(instance.Name);
if (myMemberInfos.Length > 0)
{
object[] myCustomAttrs = myMemberInfos[0].GetCustomAttributes(false);
if (myCustomAttrs.Length > 0)
{
if (myCustomAttrs[0] is MyDomain.DBDecorations.StringLength)
{
leng = ((MyDomain.DBDecorations.StringLength)(myCustomAttrs[0])).Length;
}
}
}
instance.Length(leng);
}
}
}
Add this convention to your automapping configuration and there you have it- whenever you want a specific length to result during ExportSchema, now you can just decorate the string property -and only that property- right in your entity!