Цикл по сопоставлениям столбцов сущностей для преобразования имени столбца

Я хотел бы применить одно преобразование к большому количеству столбцов в Entity Framework 5 без необходимости явно вводить их все. В качестве примера я хотел бы сделать следующие более 50 столбцов (преобразовать PascalCase в UNDERSCORE_CASE).

modelBuilder.Entity<Department>() 
            .Property(t => t.DepartmentName) 
            .HasColumnName("DEPARTMENT_NAME");

Я обнаружил Dapper.FluentMap, который может предоставить эту функциональность, но он не работает при создании запроса.

Есть ли способ перебрать список свойств и указать имя столбца, следуя шаблону? Для справки, Dapper Transform указан как

public PropertyTransformConvention()
{
    Properties()
        .Configure(c => c.Transform(s => Regex.Replace(input: s, pattern: "([A-Z])([A-Z][a-z])|([a-z0-9])([A-Z])", replacement: "$1$3_$2$4")));
}

РЕДАКТИРОВАТЬ: Это похоже на этот вопрос, но он не работает для меня. Возможно, это имеет разные требования к EF5.

Используя ответ от @Hopeless, я попытался выполнить следующую модификацию, но синтаксис не совсем правильный. Я новичок в EF, поэтому не знаю, как преобразовать старый синтаксис в новый.

modelBuilder.Entity<Job>()
            .Map(m =>
{
    m.Properties<Job>(e => e.HasColumnName(name => RegEx.Replace(name, "(?<=.)(?=[A-Z])", "_").ToUpper()));
});

2 ответа

Решение

Вы можете использовать Properties метод DbModelBuilder, Переведите шаблон с регистром Паскаля в шаблон подчеркивания, как это:

modelBuilder.Properties()
            .Configure(e => e.HasColumnName(Regex.Replace(e.ClrPropertyInfo.Name, "(?<=.)(?=[A-Z])", "_").ToUpper());

Шаблон также может быть таким (.)([A-Z]) и замена должна быть $1_$2,

Конечно, имя входа должно иметь форму SomeThing, Вы также можете взять шаблон в Dapper (опубликованный в вашем вопросе), который работает более точно для некоторых других редких случаев (даже включая этот формат). DDos (который будет преобразован в D_Dos). Дело в том, что он не переводится в верхний регистр для вас.

Редактировать:

Жаль что в EF5 модель Builder не имеет Properties() метод. Так что для конкретного типа сущности вы можете попробовать это:

//in your OnModelCreating scope
//names of navigation properties defined in Job should be passed 
//in TransformAllColumns method
new CapsUnderscorePropertiesConfig<Job>(modelBuilder).TransformAllColumns();

//a helper class
public class CapsUnderscorePropertiesConfig<T> where T : class
{
    EntityTypeConfiguration<T> _entityConfig;
    Dictionary<Type, MethodInfo> _propertyMethods = new Dictionary<Type,MethodInfo>();
    MethodInfo propertyForStruct;
    MethodInfo propertyForNullableStruct;
    public CapsUnderscorePropertiesConfig(DbModelBuilder modelBuilder)
    {
        _entityConfig = modelBuilder.Entity<T>();               
    }
    void config(PropertyInfo pInfo)
    {
        var p = Expression.Parameter(typeof(T));
        var expType = typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(typeof(T), pInfo.PropertyType));
        MethodInfo mi;
        _propertyMethods.TryGetValue(pInfo.PropertyType, out mi);
        if (mi == null)
        {
            if (pInfo.PropertyType.IsValueType)
            {
                //find the Property method for struct type having argument matching Expression<Func<TEntityType, T?>>
                //note the T? inside Func<...> (there is another overload but with T instead).
                if (propertyForStruct == null)
                {
                    foreach(var prop in _entityConfig.GetType().GetMethods().Where(m => m.Name == "Property" && m.IsGenericMethodDefinition)
                                                     .Select(e => new { genMethodDef = e, genMethod = e.MakeGenericMethod(pInfo.PropertyType) })){    
                        //there should be just 2 generic Property<T> methods filtered inhere.
                        //One is for nullable struct and the other is for struct.
                         var secondFuncArgType = prop.genMethodDef.GetParameters()[0].ParameterType.GetGenericArguments()[0].GetGenericArguments()[1];
                         if (secondFuncArgType.IsGenericType && secondFuncArgType.GetGenericTypeDefinition() == typeof(Nullable<>))
                             propertyForNullableStruct = prop.genMethodDef;
                         else propertyForStruct = prop.genMethodDef;
                    }
                }
                mi = pInfo.PropertyType.IsGenericType && pInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) ? 
                    propertyForNullableStruct.MakeGenericMethod(pInfo.PropertyType) :
                    propertyForStruct.MakeGenericMethod(pInfo.PropertyType);
            }
            else //possible property type is string, byte[] or geo type
            {
                mi = _entityConfig.GetType().GetMethods().Single(m => m.Name == "Property" && !m.IsGenericMethodDefinition 
                                                                 && m.GetParameters()[0].ParameterType == expType);
            }
            _propertyMethods[pInfo.PropertyType] = mi;
        }
        var propConfig = mi.Invoke(_entityConfig, new object[] { Expression.Lambda(Expression.Property(p, pInfo.Name), p) }) as PrimitivePropertyConfiguration;            
        propConfig.HasColumnName(Regex.Replace(pInfo.Name, "(?<=.)(?=[A-Z])", "_").ToUpper());
    }
    //at the time of configuring, the Metadataworkspace is not present
    //So we cannot know which properties are navigation properties
    //Those propertie can be excluded by passing their names in here
    public void TransformAllColumns(params string[] excludedNavProperties)
    {
        foreach (var prop in typeof(T).GetProperties().Where(p => !excludedNavProperties.Contains(p.Name)))
        {
            config(prop);
        }
    }
}

ПРИМЕЧАНИЕ: приведенный выше код работает только для прямых свойств, если у вас есть некоторые сложные свойства (возвращая некоторые ComplexType), тогда это не сработает. Технически вам необходимо исключить все свойства (возвращающие ComplexType) из свойств сущности, а затем, если возможно, объединить свойства ComplexType с прямыми свойствами объекта перед циклом и настройкой каждого из них.

PS: я не уверен, что Dapper.FluentMap поддерживает EF5, но из кода, который вы разместили, это может быть так же просто, как добавить ToUpper() метод как это:

public PropertyTransformConvention()
{
   Properties()
    .Configure(c => c.Transform(s => Regex.Replace(input: s, pattern: "([A-Z])([A-Z][a-z])|([a-z0-9])([A-Z])", replacement: "$1$3_$2$4").ToUpper()));
}

Я пытался зайти на домашнюю страницу Dapper.FluentMap и похоже, что у него есть несколько классов, основанных на Convention (если это от EF, оно поддерживается только с EF6). Так что я не уверен, работает ли код Даппера в EF5. Если это работает, вы должны попробовать приведенный выше код для удобства.

Вот как я это решил. Моя цель состояла в том, чтобы соглашение camelCase применялось только к конкретной таблице / сущности (таблица "Клиент").

   modelBuilder.Properties().Configure(p => p.HasColumnName(GetDBName(p, p.ClrPropertyInfo.Name)));


private string GetDBName(ConventionPrimitivePropertyConfiguration p, string name)
{
                var result = name;
                var entityName = p.ClrPropertyInfo.ReflectedType.UnderlyingSystemType.Name;
                if (entityName == "Client")
                    result = Helper.CamelCaseParaSnakeCaseOracle(name);
                return result;
}

static public string CamelCaseParaSnakeCaseOracle(string input)
{
                return Regex.Replace(input,
                  @"(?:\b|(?<=([A-Za-z])))([A-Z][a-z]*)",
                  m => string.Format(@"{0}{1}",
                    (m.Groups[1].Value.Length > 0) ? "_" : "", m.Groups[2].Value.ToUpper()));
}
Другие вопросы по тегам