Override for fluent NHibernate for long text strings nvarchar(MAX) not nvarchar(255)
Asked Answered
L

6

40

When ever you set a string value in fluent NHibernate it alwasy sets the DB vales to Nvarchar(255), I need to store quite a lot of long string which are based on user inputs and 255 is impractical.

Just to add this is an issue with the automapper as I am using fluent NHibernate to build the database.

Liebermann answered 26/2, 2010 at 16:32 Comment(0)
A
34

Adding this convention will set the default length for string properties to 10000. As others have noted, this will be a nvarchar(max) column.

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)
    {
        instance.Length(10000);
    }
}

Conventions can be added to an automap configuration like this:

Fluently.Configure()
    .Mappings( m =>
        m.AutoMappings.Add( AutoMap.AssemblyOf<Foo>()
        .Conventions.Add<StringColumnLengthConvention >()))

For more information, see Conventions in the Fluent NHibernate wiki.

Atchison answered 26/2, 2010 at 16:47 Comment(1)
Just what I was looking for, oh and for anyone else you Add this line to the GetConventions() method in utoPersistenceModelGenerator file c.Add<StringColumnLengthConvention>();Liebermann
K
17

Setting the length to anything over 4001 will generate an NVarchar(MAX)...

.WithLengthOf(10000);

See here for more detail...

http://serialseb.blogspot.com/2009/01/fluent-nhibernate-and-nvarcharmax.html

Kweilin answered 26/2, 2010 at 16:34 Comment(1)
This looks like it would work for a single string value, But i need to do this to close to 300 data fields so am looking for an easy way to just override every nvarchar(255) and set it as an nvarchar(Max)Liebermann
M
6

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!

Mastoid answered 10/4, 2011 at 0:17 Comment(1)
I like this way so we can set specific max length for a string property but I saw someone says it break POCO but I dont care. I think you can make this better by using AttributePropertyConvention<T> and only 1 line of code leng = ((MyDomain.DBDecorations.StringLength)(myCustomAttrs[0])).Length; should be used instead.Breadnut
S
3

One of the consistent way that I found is:

Map(x => x.LongText, "LongText").CustomType<VarcharMax>().Nullable();

in which the VarcharMax and classes are

public class VarcharMax : BaseImmutableUserType<String>
{
    public override object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        return  (string)NHibernateUtil.String.NullSafeGet(rs, names[0]);
    }
    public override void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        //Change the size of the parameter
        ((IDbDataParameter)cmd.Parameters[index]).Size = int.MaxValue;
        NHibernateUtil.String.NullSafeSet(cmd, value, index);
    }
    public override SqlType[] SqlTypes
    {
        get { return new[] { new SqlType(DbType.String) }; }
    }
}

public abstract class BaseImmutableUserType<T> : NHibernate.UserTypes.IUserType
{
    public abstract object NullSafeGet(IDataReader rs, string[] names, object owner);
    public abstract void NullSafeSet(IDbCommand cmd, object value, int index);
    public abstract SqlType[] SqlTypes { get; }

    public new bool Equals(object x, object y)
    {
        if (ReferenceEquals(x, y))
        {
            return true;
        }
        if (x == null || y == null)
        {
            return false;
        }

        return x.Equals(y);
    }

    public int GetHashCode(object x)
    {
        return x.GetHashCode();
    }

    public object DeepCopy(object value)
    {
        return value;
    }

    public object Replace(object original, object target, object owner)
    {
        return original;
    }

    public object Assemble(object cached, object owner)
    {
        return DeepCopy(cached);
    }

    public object Disassemble(object value)
    {
        return DeepCopy(value);
    }

    public Type ReturnedType
    {
        get { return typeof(T); }
    }

    public bool IsMutable
    {
        get { return false; }
    }
}
Showpiece answered 28/6, 2012 at 14:1 Comment(0)
A
2

Hi I came across this Question, with the same problem. I have an slightly safer way of doing it as I don't want all string fields to have 10000 chars by default.

Firstly I register fluent nhibernate with some overrides

...//snip
....Mappings(m => m.AutoMappings.Add(
                    AutoMap.AssemblyOf<Account>()
                     //Use my mapping overrides here 
                    .UseOverridesFromAssemblyOf<MyMappingOverride>()
                    .Conventions.Add(new MyConventions()).IgnoreBase<Entity>
                ))

My Mapping override class looks like this:

public class MyMappingOverride : IAutoMappingOverride<MyClass> {
       public void Override(AutoMapping<MyClass> mapping) {
           mapping.Map(x => x.LongName).Length(765);
       }
}

This is only required for small subset of entities with long text values. Maybe some else will find this useful?

Allot answered 30/8, 2013 at 21:28 Comment(0)
M
1

Probably you are using "NHibernate validator" as well. If yes, Fluent NHibernate will consider all of the NHibernate validator related data annotations automatically, including string length, not null, etc.

Melodist answered 12/11, 2011 at 20:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.