EF Code First - Globally set varchar mapping over nvarchar

[亡魂溺海] 提交于 2019-11-28 21:24:37
Diego Mijelshon

Before EF 4.1, you could use conventions and add the following convention to your ModelBuilder:

using System;
using System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive;
using System.Data.Entity.ModelConfiguration.Conventions.Configuration;
using System.Reflection;

public class MakeAllStringsNonUnicode :
    IConfigurationConvention<PropertyInfo, StringPropertyConfiguration>
{
    public void Apply(PropertyInfo propertyInfo, 
                      Func<StringPropertyConfiguration> configuration)
    {
        configuration().IsUnicode = false;
    }
}

(Taken from http://blogs.msdn.com/b/adonet/archive/2011/01/10/ef-feature-ctp5-pluggable-conventions.aspx)


UPDATE: Pluggable conventions were dropped for the 4.1 release. Check my blog for an alternative approach)

I extended Marc Cals' answer (and Diego's blog post) to globally set all strings on all entities as non-unicode as per the question, rather than having to call it manually per-class. See below.

/// <summary>
/// Change the "default" of all string properties for a given entity to varchar instead of nvarchar.
/// </summary>
/// <param name="modelBuilder"></param>
/// <param name="entityType"></param>
protected void SetAllStringPropertiesAsNonUnicode(
    DbModelBuilder modelBuilder,
    Type entityType)
{
    var stringProperties = entityType.GetProperties().Where(
        c => c.PropertyType == typeof(string)
           && c.PropertyType.IsPublic 
           && c.CanWrite
           && !Attribute.IsDefined(c, typeof(NotMappedAttribute)));

    foreach (PropertyInfo propertyInfo in stringProperties)
    {
        dynamic propertyExpression = GetPropertyExpression(propertyInfo);

        MethodInfo entityMethod = typeof(DbModelBuilder).GetMethod("Entity");
        MethodInfo genericEntityMethod = entityMethod.MakeGenericMethod(entityType);
        object entityTypeConfiguration = genericEntityMethod.Invoke(modelBuilder, null);

        MethodInfo propertyMethod = entityTypeConfiguration.GetType().GetMethod(
            "Property", new Type[] { propertyExpression.GetType() });

        StringPropertyConfiguration property = (StringPropertyConfiguration)propertyMethod.Invoke(
            entityTypeConfiguration, new object[] { propertyExpression });
        property.IsUnicode(false);
    }
}

private static LambdaExpression GetPropertyExpression(PropertyInfo propertyInfo)
{
    var parameter = Expression.Parameter(propertyInfo.ReflectedType);
    return Expression.Lambda(Expression.Property(parameter, propertyInfo), parameter);
}

/// <summary>
/// Return an enumerable of all DbSet entity types in "this" context.
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
private IEnumerable<Type> GetEntityTypes()
{
    return this
        .GetType().GetProperties()
        .Where(a => a.CanWrite && a.PropertyType.IsGenericType && a.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
        .Select(a => a.PropertyType.GetGenericArguments().Single());
}

Finally, call it from your OnModelCreating(DbModelBuilder modelBuilder):

foreach (var entityType in GetEntityTypes())
    SetAllStringPropertiesAsNonUnicode(modelBuilder, entityType);

Here is a project from Sergey Barskiy that extends EF to allow custom conventions, which as a result, you can make custom attributes instead of the fluent API.

Here is a code snippet from here that demonstrates the utility in action. What you don't see here is the decimal precision attribute and others. So per your question, once you set Unicode to false, it should be varchar as opposed to nvarchar.

public class Product
{
    public int ProductId { get; set; }
    [Indexed("Main", 0)]
    public string ProductNumber { get; set; }
    [Indexed("Main", 1)]
    [Indexed("Second", direction: IndexDirection.Ascending)]
    [Indexed("Third", direction: IndexDirection.Ascending)]
    public string ProductName { get; set; }
    [String(4, 12, false)] //minLength, maxLength, isUnicode
    public string Instructions { get; set; }
    [Indexed("Third", 1, direction: IndexDirection.Descending)]
    public bool IsActive { get; set; }
    [Default("0")]
    public decimal? Price { get; set; }
    [Default("GetDate()")]
    public DateTime? DateAdded { get; set; }
    [Default("20")]
    public int Count { get; set; }
}

Read this and this for detail.

Marc Cals

With Diego's blog help, to make the public properties of a POCO varchar without using anotations is :

    private void SetStringPropertiesAsNonUnicode<e>(DbModelBuilder _modelBuilder) where e:class
    {
        //Indiquem a totes les propietats string que no són unicode per a que es crein com a varchar
        List<PropertyInfo> stringProperties = typeof(e).GetProperties().Where(c => c.PropertyType == typeof(string) && c.PropertyType.IsPublic).ToList();
        foreach (PropertyInfo propertyInfo in stringProperties)
        {
            dynamic propertyExpression = GetPropertyExpression(propertyInfo);
            _modelBuilder.Entity<e>().Property(propertyExpression).IsUnicode(false);
        }
    }

    // Edit: Also stole this from referenced blog post (Scott)
    static LambdaExpression GetPropertyExpression(PropertyInfo propertyInfo)
    {
       var parameter = Expression.Parameter(propertyInfo.ReflectedType);
       return Expression.Lambda(Expression.Property(parameter, propertyInfo), parameter);
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!