在EF Core中重新创建EF6的命名约定

4
我们正在将一个中等规模的应用程序从Entity Framework 6.2迁移到Entity Framework Core 2.0。虽然许多事情比我所希望的要容易,但我遇到了一个问题,我怀疑我不是第一个(或第一千个)遇到这个问题的人,但找不到一个简洁的解决方案: 命名约定。
具体来说,关系属性的命名约定。
在我们的应用程序中,典型的一对多实体对看起来像这样:
public class Foo
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int FooId { get; set; }

    [Required]
    public string Bar { get; set; }

    [InverseProperty(nameof(Baz.Foo))]
    public ICollection<Baz> Bazzes { get; set; }
}

public class Baz
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int BazId { get; set; }

    [Required]
    public string Qux { get; set; }

    [Required]
    public Foo Foo { get; set; }
}

在我们现有的数据库中,EF6将其映射到以下表:
Foos                        Bazzes -- named after the DbSets on the DbContext
****                        ******
FooId : int                 BazId : int
Bar : nvarchar(max)         Qux : nvarchar(max)
                            Foo_FooId : int

使用EF Core的默认命名约定时,转换后的上下文似乎会查找列FooId而不是Foo_FooId
我尝试了一些描述在这里的修改方法,但我无法弄清楚如何对属性元数据施加仅更改导航属性的条件。(我还有一些复合类型,使用.Entity<Owner>().OwnsOne(o => o.Owned)进行配置,因此解决方案需要捕获该部分。)
是否没有现成的解决方案,可以使此过渡更容易?
我已经发布了一个此问题的最小可重现示例(MVCE): https://github.com/tlycken/MCVE_EfCoreNavigationalProperties

据我所知,没有。当我们迁移到EF Core 1.0时,我们对数据库进行了脚手架搭建,并相应地调整了代码。我找不到任何帖子表明这方面有任何改进。 - Tubs
2个回答

3
据我所知,您无法更改影子属性的名称,但可以更改表列的名称。像这样:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{

    base.OnModelCreating(modelBuilder);

    //modelBuilder.Model.FindEntityType(typeof(Baz)).FindProperty("FooId").Relational().ColumnName = "Foo_FooId";
    foreach( var m in modelBuilder.Model.GetEntityTypes())
    {
        foreach (var sp in m.GetProperties().Where( p => p.IsShadowProperty ))
        {
            var fk = sp.GetContainingForeignKeys().Where(f => f.Properties.Count == 1).FirstOrDefault();
            if (fk != null && fk.PrincipalKey.Properties.Count == 1)
            {
                sp.Relational().ColumnName = fk.PrincipalEntityType.Relational().TableName + "_" + fk.PrincipalKey.Properties.Single().Relational().ColumnName;
            }
        }
    }

}

当然,如果您的模型中有外键属性,您就不需要这样做。

谢谢。我现在已经能够尝试这个了,但是由于以下几个原因,我无法让它正常工作:你的代码假定前缀是其他实体的表名,但实际上应该是_this_实体上的属性名称(例如,如果我们有一个类型为Foo的实体,具有类型为Bar的导航属性,命名为Baz,则Foos表中的列应该是Baz_Bar),而我无法找到属性名称。 - Tomas Aschan
我也尝试使用[ForeignKey("TheDesiredColumnName")]注释导航属性,这似乎使名称正确解析,但是Include停止工作(我得到所有关系参数的null)。数据库中有一个外键,但似乎没有帮助。 - Tomas Aschan
另外,我在这里放了一个最小化的示例应用程序,演示了问题:https://github.com/tlycken/MCVE_EfCoreNavigationalProperties - Tomas Aschan
1
像这样吗?sp.Relational().ColumnName = fk.DependentToPrincipal.Name + "_" + fk.PrincipalKey.Properties.Single().Relational().ColumnName; - David Browne - Microsoft

0

以下是我如何在EF Core 5中重新创建EF6的命名约定。

TableNameId的FK名称更改为TableName_Id

最常见的情况(据我所知)是当您的模型中的主键仅设置为Id时,例如:

public class Foo
{
    public int Id { get; set; }
    ...
}

EF Core 5 的外键默认列名为 TableNameId(例如 FooId)。要像 EF6 一样创建它们,使用 TableName_Id(例如 Foo_Id)的格式进行配置:

public class TestContext : DbContext
{
    public DbSet<Foo> Foos { get; set; }
    public DbSet<Baz> Bazzes { get; set; }

    // ...

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        AddConfigurations(modelBuilder);

        RenameForeignKeys(modelBuilder);

        base.OnModelCreating(modelBuilder);
    }

    private static void AddConfigurations(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new FooConfiguration());
    }

    private static void RenameForeignKeys(ModelBuilder modelBuilder)
    {
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            var properties = entityType.GetProperties()
                .Where(property => property.IsForeignKey() && !property.DeclaringEntityType.IsOwned());

            foreach (var property in properties)
            {
                var newName = GetNewColumnName(property.Name);

                property.SetColumnName(newName);
            }
        }
    }

    private static string GetNewColumnName(string name)
    {
        if (name.Length > 2 && name[^2..] == "Id" && name[^3..] != "_Id")
        {
            return $"{name[0..^2]}_Id";
        }

        return name;
    }
}

public class FooConfiguration : IEntityTypeConfiguration<Foo>
{
    public void Configure(EntityTypeBuilder<Foo> builder)
    {
    }
}

TableNameId的FK名称更改为TableName_TableNameId

在OP的情况下,模型中将主键设置为TableNameId(例如FooId)。为了将外键创建为TableName_TableNameId(例如Foo_FooId),只需将前面示例中的这两个方法替换即可:

private static void RenameForeignKeys(ModelBuilder modelBuilder)
{
    foreach (var entityType in modelBuilder.Model.GetEntityTypes())
    {
        var properties = entityType.GetProperties()
            .Where(property => property.IsForeignKey() && !property.DeclaringEntityType.IsOwned());

        foreach (var property in properties)
        {
            var foreignKey = property.GetContainingForeignKeys().Single();

            var principalEntityName = foreignKey.PrincipalEntityType.FullName();

            var newName = GetNewColumnName(property.Name, principalEntityName);

            property.SetColumnName(newName);
        }
    }
}

private static string GetNewColumnName(string name, string tableName)
{
    if (name.Length > 2 && name[^2..] == "Id" && !name.Contains("_"))
    {
        return $"{name[0..^2]}_{tableName}Id";
    }

    return name;
}

注意:

  • 我在 property.GetContainingForeignKeys().Single() 中使用了 .Single(),因为我没有发现该方法未返回仅有一个条目的情况。
  • !name.Contains("_") 的存在是为了防止覆盖您可能已经手动设置的任何外键配置。

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