EF Core - 为所有属性定义自定义列映射策略
EF Core - Define custom column mapping strategy for all properties
按照惯例,每个 属性 都将设置为映射到与 属性 同名的列。如果我想更改默认映射策略,我可以使用 Fluent API 或 Data Annotation 来完成。但是,我想为所有实体中的所有属性设置自定义映射策略到数据库列。我的数据库存在,列名如 ID、CUSTOMER_NAME、CREDIT_AMOUNT 等,因此列名不遵循 PascalCase 表示法。所有对象名称均为大写,单个单词以“_”符号分隔。对于整个数据库都是如此。我想将此命名映射到 class,如下所示:
public class Payment
{
public int ID { set; get; }
public string CustomerName { get; set; }
public decimal CreditAmount { get; set; }
}
数据库很大,我不想将每个 属性 和 class 名称映射到适当的数据库对象。有没有全局的方法来定义这种类型的映射?
CustomerName -> CUSTOMER_NAME,
CreditAmount -> CREDIT_AMOUNT 等等。
通过反射实现该约定的可能方式如下:
从 DbContext 的 DBSet 属性获取实体类型 class
然后获取实体类型的属性(列)
所以
在您的 DbContext class 中添加此行:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//see below for this extension method
this.ApplyCaseMappingRule(modelBuilder);
base.OnModelCreating(modelBuilder);
}
扩展方法来源:
public static class Extensions
{
public static void ApplyCaseMappingRule<TDbContext>(this TDbContext _, ModelBuilder modelBuilder) where TDbContext:DbContext
{
var ignoreType = typeof(NotMappedAttribute);
var dbSetProps = typeof(TDbContext).GetProperties();
foreach (var dbSetProp in dbSetProps)
{
if (dbSetProp.PropertyType.TryGetEntityTypeFromDbSetType(out var entityType))
{
modelBuilder.Entity(entityType, option =>
{
option.ToTable(Mutate(dbSetProp.Name));
var props = entityType.GetProperties();
foreach (var prop in props)
{
//check if prop has Ignore attribute
var hasIgnoreAttribute =
prop.PropertyType.CustomAttributes.Any(x => x.AttributeType == ignoreType);
if (hasIgnoreAttribute) continue;
option.Property(prop.PropertyType, prop.Name).HasColumnName(Mutate(prop.Name));
}
});
}
}
}
private static bool TryGetEntityTypeFromDbSetType(this Type dbSetType, out Type entityType)
{
entityType = null;
if (dbSetType.Name != "DbSet`1") return false;
if (dbSetType.GenericTypeArguments.Length != 1) return false;
entityType = dbSetType.GenericTypeArguments[0];
return true;
}
public static IEnumerable<string> SplitCamelCase(this string source)
{
const string pattern = @"[A-Z][a-z]*|[a-z]+|\d+";
var matches = Regex.Matches(source, pattern);
foreach (Match match in matches)
{
yield return match.Value;
}
}
public static string Mutate(string propName)
{
return string.Join("_", propName.SplitCamelCase().Select(x => x.ToUpperInvariant()));
}
使用 EF 5.0.0 在 .NET 5 上测试
您只需迭代 modelBuilder.Model
中的实体和属性,例如
string ToDatabaseIdentifier(string propertyName)
{
var sb = new System.Text.StringBuilder();
for (int i = 0; i < propertyName.Length; i++)
{
var c = propertyName[i];
if (i>0 && Char.IsUpper(c) && Char.IsLower(propertyName[i-1]))
{
sb.Append('_');
}
sb.Append(Char.ToUpper(c));
}
return sb.ToString();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var e in modelBuilder.Model.GetEntityTypes())
{
e.SetTableName(ToDatabaseIdentifier(e.Name));
foreach (var p in e.GetProperties())
{
p.SetColumnName(ToDatabaseIdentifier(p.Name));
}
}
base.OnModelCreating(modelBuilder);
}
按照惯例,每个 属性 都将设置为映射到与 属性 同名的列。如果我想更改默认映射策略,我可以使用 Fluent API 或 Data Annotation 来完成。但是,我想为所有实体中的所有属性设置自定义映射策略到数据库列。我的数据库存在,列名如 ID、CUSTOMER_NAME、CREDIT_AMOUNT 等,因此列名不遵循 PascalCase 表示法。所有对象名称均为大写,单个单词以“_”符号分隔。对于整个数据库都是如此。我想将此命名映射到 class,如下所示:
public class Payment
{
public int ID { set; get; }
public string CustomerName { get; set; }
public decimal CreditAmount { get; set; }
}
数据库很大,我不想将每个 属性 和 class 名称映射到适当的数据库对象。有没有全局的方法来定义这种类型的映射?
CustomerName -> CUSTOMER_NAME, CreditAmount -> CREDIT_AMOUNT 等等。
通过反射实现该约定的可能方式如下:
从 DbContext 的 DBSet 属性获取实体类型 class
然后获取实体类型的属性(列)
所以
在您的 DbContext class 中添加此行:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//see below for this extension method
this.ApplyCaseMappingRule(modelBuilder);
base.OnModelCreating(modelBuilder);
}
扩展方法来源:
public static class Extensions
{
public static void ApplyCaseMappingRule<TDbContext>(this TDbContext _, ModelBuilder modelBuilder) where TDbContext:DbContext
{
var ignoreType = typeof(NotMappedAttribute);
var dbSetProps = typeof(TDbContext).GetProperties();
foreach (var dbSetProp in dbSetProps)
{
if (dbSetProp.PropertyType.TryGetEntityTypeFromDbSetType(out var entityType))
{
modelBuilder.Entity(entityType, option =>
{
option.ToTable(Mutate(dbSetProp.Name));
var props = entityType.GetProperties();
foreach (var prop in props)
{
//check if prop has Ignore attribute
var hasIgnoreAttribute =
prop.PropertyType.CustomAttributes.Any(x => x.AttributeType == ignoreType);
if (hasIgnoreAttribute) continue;
option.Property(prop.PropertyType, prop.Name).HasColumnName(Mutate(prop.Name));
}
});
}
}
}
private static bool TryGetEntityTypeFromDbSetType(this Type dbSetType, out Type entityType)
{
entityType = null;
if (dbSetType.Name != "DbSet`1") return false;
if (dbSetType.GenericTypeArguments.Length != 1) return false;
entityType = dbSetType.GenericTypeArguments[0];
return true;
}
public static IEnumerable<string> SplitCamelCase(this string source)
{
const string pattern = @"[A-Z][a-z]*|[a-z]+|\d+";
var matches = Regex.Matches(source, pattern);
foreach (Match match in matches)
{
yield return match.Value;
}
}
public static string Mutate(string propName)
{
return string.Join("_", propName.SplitCamelCase().Select(x => x.ToUpperInvariant()));
}
使用 EF 5.0.0 在 .NET 5 上测试
您只需迭代 modelBuilder.Model
中的实体和属性,例如
string ToDatabaseIdentifier(string propertyName)
{
var sb = new System.Text.StringBuilder();
for (int i = 0; i < propertyName.Length; i++)
{
var c = propertyName[i];
if (i>0 && Char.IsUpper(c) && Char.IsLower(propertyName[i-1]))
{
sb.Append('_');
}
sb.Append(Char.ToUpper(c));
}
return sb.ToString();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var e in modelBuilder.Model.GetEntityTypes())
{
e.SetTableName(ToDatabaseIdentifier(e.Name));
foreach (var p in e.GetProperties())
{
p.SetColumnName(ToDatabaseIdentifier(p.Name));
}
}
base.OnModelCreating(modelBuilder);
}