Search code examples
ef-code-firstentity-framework-6.1

How can I change the default max length of string properties in Entity Framework 6?


By default convention, strings properties in an entity model that are not explicitly given a max length are set to nvarchar(max) in the database. We wish to override this convention and give strings a max length of nvarchar(100) if they are not already explicitly set otherwise.

I discovered the PropertyMaxLengthConvention built-in convention, which by its description and documentation would seem to be what I am looking for. However, it either doesn't work or I'm using it wrong or it just doesn't do what I think it does.

I've tried simply adding the convention:

modelBuilder.Conventions.Add(new PropertyMaxLengthConvention(100));

Then I thought maybe the default one is already being used, so I tried removing it first:

modelBuilder.Conventions.Remove<PropertyMaxLengthConvention>();
modelBuilder.Conventions.Add(new PropertyMaxLengthConvention(100));

I even tried explictly adding the convention before and after the default one:

modelBuilder.Conventions.AddBefore<PropertyMaxLengthConvention>(new PropertyMaxLengthConvention(100));
modelBuilder.Conventions.AddAfter<PropertyMaxLengthConvention>(new PropertyMaxLengthConvention(100));

No joy. When I add migrations, the columns are still created as nvarchar(max).

Is there a way to use that convention to do what I want? If not, can I write a custom convention that will default string properties to nvarchar(100) but will still allow me to explicitly set them to a different value including maxlength?


Solution

  • After tracking down the source code for the aforementioned convention, I discovered that it only sets the default max length for properties that are specified to have fixed length. (Bizarre!)

    So I took the source code and modified it to create my own convention. Now string properties with unspecified max length will have a default max length instead of being nvarchar(max). The only downside is there doesn't appear to be a way to detect when the IsMaxLength() configuration is explicitly applied. So if I have a column that I do want to have created as nvarchar(max) I can't use IsMaxLength() to do it.

    To address this, I created an extension method for StringPropertyConfiguration called ForceMaxLength() that configures the property with HasMaxLength(int.MaxValue) - ordinarily an invalid value, but one for which I can easily test in my custom convention. When I detect it, I simply set the MaxLength back to null and set the IsMaxLength to true and let the property configuration continue as normal.

    Here's the custom convention:

    using System;
    using System.Collections.Generic;
    using System.Data.Entity.Core.Metadata.Edm;
    using System.Data.Entity.Infrastructure;
    using System.Data.Entity.ModelConfiguration.Conventions;
    
    namespace MyProject.CustomConventions
    {
        public class CustomPropertyMaxLengthConvention : IConceptualModelConvention<EntityType>, IConceptualModelConvention<ComplexType>
        {
            private const int DefaultLength = 128;
            private readonly int length;
            public CustomPropertyMaxLengthConvention()
                : this(DefaultLength)
            {
            }
            public CustomPropertyMaxLengthConvention(int length)
            {
                if (length <= 0)
                {
                    throw new ArgumentOutOfRangeException("length", "Invalid Max Length Size");
                }
                this.length = length;
            }
            public virtual void Apply(EntityType item, DbModel model)
            {
                SetLength(item.DeclaredProperties);
            }
            public virtual void Apply(ComplexType item, DbModel model)
            {
                SetLength(item.Properties);
            }
            private void SetLength(IEnumerable<EdmProperty> properties)
            {
                foreach (EdmProperty current in properties)
                {
                    if (current.IsPrimitiveType)
                    {
                        if (current.PrimitiveType == PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String))
                        {
                            SetStringDefaults(current);
                        }
                        if (current.PrimitiveType == PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Binary))
                        {
                            SetBinaryDefaults(current);
                        }
                    }
                }
            }
            private void SetStringDefaults(EdmProperty property)
            {
                if (property.IsUnicode == null)
                {
                    property.IsUnicode = true;
                }
                SetBinaryDefaults(property);
            }
            private void SetBinaryDefaults(EdmProperty property)
            {
    
                if (property.MaxLength == int.MaxValue)
                {
                    property.MaxLength = null;
                    property.IsMaxLength = true;
                }
                else if (property.MaxLength == null || !property.IsMaxLength)
                {
                    property.MaxLength = length;
                }
    
            }
        }
    }
    

    Here's the extension method:

    using System.Data.Entity.ModelConfiguration.Configuration;
    
    namespace MyProject.Model.Mapping
    {
        public static class MappingExtensions
        {
            public static void ForceMaxLength(this StringPropertyConfiguration obj)
            {
                obj.HasMaxLength(int.MaxValue);
            }
        }
    }
    

    Here's how it's used:

    using System.Data.Entity.ModelConfiguration;
    
    namespace MyProject.Model.Mapping
    {
        public class MyEntityMap : EntityTypeConfiguration<MyEntity>
        {
            public MyEntityMap()
            {
                Property(v => v.StringValue).ForceMaxLength();
            }
        }
    }