EF Code First - 全局设置 varchar 映射为 nvarchar

19

我有一个应该很简单的问题,但是我一直无法找到答案。

我正在使用EF4 CTP-5 Code First Model和手动生成的POCO。它将生成的SQL中的字符串比较处理为

WHERE N'Value' = Object.Property

我知道我可以使用以下方法覆盖这个功能:

[Column(TypeName = "varchar")]
public string Property {get;set;}

这解决了单个出现的问题,并且正确地生成以下SQL:

WHERE 'Value' = Object.Property

然而,我正在处理一个非常庞大的领域模型,并且逐个字符串字段设置TypeName = "varchar" 将非常繁琐。我希望指定EF应该将所有字符串视为varchar,因为这是该数据库的标准,nvarchar是异常情况。

想要纠正这一点的原因是查询执行效率。在SQL Server 2k5中,varchar和nvarchar之间的比较非常低效,其中varchar到varchar的比较几乎立即执行。

5个回答

9
在EF 4.1之前,您可以使用惯例并将以下惯例添加到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;
    }
}

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


更新:可插拔的约定已在4.1版本中取消。请查看我的博客了解替代方案。

1
有没有一种方法可以使用属性来实现所有这些? - Shimmy Weitzhandler
@DiegoMijelshon,抱歉:( - Shimmy Weitzhandler

5

对于任何想在EF Core(版本3及以上)中实现此操作的人,一个快速的方法是通过ModelBuilder.Model属性来实现; 它提供了访问模型内所有实体和属性的简便方式。

以下是一个"裸骨"实现:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // Apply configurations via regular modelBuilder code-first calls
    // ... 
    // ...


    // Override the configurations to force Unicode to false
    var entities = modelBuilder.Model.GetEntityTypes();
    foreach (var entity in entities)
    {
        foreach (var property in entity.GetProperties())
        {
            property.SetIsUnicode(false);
        }
    }
}

EF Core会忽略非字符串属性上的SetIsUnicode调用,因此您甚至不需要检查属性类型(但如果这让您感觉更好,您也可以轻松执行此操作 :))。

对于那些更喜欢显式的人,将where子句附加到GetProperties()调用上即可解决问题:

...
    var stringProperties = entity.GetProperties()
                                 .Where(e=> e.ClrType == typeof(string));
    foreach (var property in stringProperties)
    {
       property.SetIsUnicode(false);
    }
...

更新 - 实体框架核心6

现在,您可以使用 EF Core 6 的 预约定模型配置,直接进行此类型的全局映射。

下面展示了如何使用这个新功能实现此目标的示例:

configurationBuilder
    .DefaultTypeMapping<string>()
    .IsUnicode(false);

1
我是自解释代码的粉丝,所以我会过滤字符串属性。尽管如此,这对这个问题来说是一个有价值的补充。 - Gert Arnold
更加明确从来不会有坏处 @GertArnold - 已经更新了答案并提供了一个选项来实现。感谢您的反馈。 - RMD

4

我扩展了Marc Cals的回答(以及Diego的博客文章),根据问题全局设置所有实体上的所有字符串为非Unicode,而不是必须手动每个类别调用它。请参见下面。

/// <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());
}

最后,在你的OnModelCreating(DbModelBuilder modelBuilder)中调用它:
foreach (var entityType in GetEntityTypes())
    SetAllStringPropertiesAsNonUnicode(modelBuilder, entityType);

请注意,我们从代码库中删除了这段代码,因为它偶尔会导致崩溃并损坏 EF 元模型。我不知道为什么。 - Scott Stafford
我猜这些行可以留在foreach之外。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() }); - Ahuman

3

这里是 Sergey Barskiy 的一个项目,它扩展了 EF 以允许自定义约定,因此您可以使用自定义属性而不是流畅 API。

这里是来自 这里 的代码片段,演示了其实用性。 在这里看不到的是十进制精度属性和其他属性。所以根据您的问题,一旦将 Unicode 设置为 false,应该使用 varchar 而不是 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; }
}

阅读 thisthis 以获取详细信息。


1
通过Diego的博客帮助,使POCO的公共属性成为varchar而不使用注释的方法是:
    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);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接