如何按照约定首先删除代码中外键字段的下划线

Posted

技术标签:

【中文标题】如何按照约定首先删除代码中外键字段的下划线【英文标题】:How do I remove underscore of foreign key fields in code first by convention 【发布时间】:2013-03-19 01:13:58 【问题描述】:

我的项目中有多个课程(包括 TPT)。每个 POCO 都有一个BaseClass,其中有一个GUID(称为GlobalKey)作为主键。

首先我使用DataAnnotations 创建正确的外键。但是我在将相应的 GUID 与对象本身同步时遇到了问题。

现在我只想拥有一个虚拟导航属性,以便数据库中的 GUID 字段由NamingConvention 创建。但是字段名称总是添加一个下划线,后跟单词GlobalKey(这是正确的)。当我想删除下划线时,我不想通过 fluent API 中的所有 POCO 来执行此操作:

// Remove underscore from Navigation-Field     
modelBuilder.Entity<Person>()
            .HasOptional(x => x.Address)
            .WithMany()
            .Map(a => a.MapKey("AddressGlobalKey"));

有什么想法可以通过覆盖约定来为所有 POCOS 执行此操作?

提前致谢。

安德烈亚斯

【问题讨论】:

你还不能这样做。有一个 customizable conventions for code-first development feature 正在为 EF 6.0 工作,但现在 - 你必须自己手动处理这个...... 我实际上正在使用 EF6 alpha 2。是否可以以这种方式使用它? Check out this blog post - 这个家伙似乎在做一些与你想要的类似的事情。 谢谢马克。这几乎符合我的要求,但是当我对 NavigationProperties 进行编码并且不想添加 [ForeignKey]-DataAnnotation 时,我可以使用它。但我没有编码任何 Id 或 Guid 属性。我唯一的导航属性是对象本身。因此 EF 创建了一个名为 i.E. 的附加属性。地址_全局密钥。这个名字是我想在约定中操纵的。我找不到如何做到这一点的博客。 请标记最佳答案。 【参考方案1】:

我终于通过编写自定义约定找到了答案。这个约定在 EF 6.0 RC1(上周的代码)中有效,所以我认为在 EF 6.0 发布后它可能会继续有效。

使用这种方法,标准 EF 约定识别独立关联 (IA),然后为外键字段创建 EdmProperty。然后这个约定出现并重命名外键字段。

/// <summary>
/// Provides a convention for fixing the independent association (IA) foreign key column names.
/// </summary>
public class ForeignKeyNamingConvention : IStoreModelConvention<AssociationType>


    public void Apply(AssociationType association, DbModel model)
    
        // Identify a ForeignKey properties (including IAs)
        if (association.IsForeignKey)
        
            // rename FK columns
            var constraint = association.Constraint;
            if (DoPropertiesHaveDefaultNames(constraint.FromProperties, constraint.ToRole.Name, constraint.ToProperties))
            
                NormalizeForeignKeyProperties(constraint.FromProperties);
            
            if (DoPropertiesHaveDefaultNames(constraint.ToProperties, constraint.FromRole.Name, constraint.FromProperties))
            
                NormalizeForeignKeyProperties(constraint.ToProperties);
            
        
    

    private bool DoPropertiesHaveDefaultNames(ReadOnlyMetadataCollection<EdmProperty> properties, string roleName, ReadOnlyMetadataCollection<EdmProperty> otherEndProperties)
    
        if (properties.Count != otherEndProperties.Count)
        
            return false;
        

        for (int i = 0; i < properties.Count; ++i)
        
            if (!properties[i].Name.EndsWith("_" + otherEndProperties[i].Name))
            
                return false;
            
        
        return true;
    

    private void NormalizeForeignKeyProperties(ReadOnlyMetadataCollection<EdmProperty> properties)
    
        for (int i = 0; i < properties.Count; ++i)
        
            string defaultPropertyName = properties[i].Name;
            int ichUnderscore = defaultPropertyName.IndexOf('_');
            if (ichUnderscore <= 0)
            
                continue;
            
            string navigationPropertyName = defaultPropertyName.Substring(0, ichUnderscore);
            string targetKey = defaultPropertyName.Substring(ichUnderscore + 1);

            string newPropertyName;
            if (targetKey.StartsWith(navigationPropertyName))
            
                newPropertyName = targetKey;
            
            else
            
                newPropertyName = navigationPropertyName + targetKey;
            
            properties[i].Name = newPropertyName;
        
    


请注意,约定在您的DbContext.OnModelCreating 覆盖中添加到您的DbContext,使用:

modelBuilder.Conventions.Add(new ForeignKeyNamingConvention());

【讨论】:

可以在 ForeignKeyIndexConvention 之前添加而不是最后添加约定,以将命名约定应用于外键索引名称 --- modelBuilder.Conventions.AddBefore&lt;System.Data.Entity.ModelConfiguration.Conventions.ForeignKeyIndexConvention&gt;(new ForeignKeyNamingConvention()); 这段代码不太适合我,因为这个测试总是错误的: if (!properties[i].Name.EndsWith("_" + otherEndProperties[i].Name)) 事实上,名称本身已经包含下划线。所以我跳过了整个测试,只是总是调用 NormalizeForeignKeyProperties。 嘿看!微软“借用”了我对 MSDN 的回答:msdn.microsoft.com/en-us/data/dn469439.aspx#example2 由于他们对 EF 和 ASP.NET 等开源项目非常出色,我很高兴让它滑落…… 我在这里可能会迟到,但我已经创建了一个要点来改进@crimbo 的答案:gist.github.com/rexcfnghk/3a6becedb3e09819a4e0 我只是偶然发现了这个,还发现它没有按预期工作。在我的情况下,检查if (!properties[i].Name.EndsWith("_" + otherEndProperties[i].Name)) 是比较从端(例如“Project_Id”)到返回假的结束端(例如“ProjectId”),因此不修复从端。这似乎是因为我也有一个 PrimaryKeyConvention 从名称中删除下划线。因此,确保它不与其他约定冲突的检查似乎实际上导致它与其他约定冲突。【参考方案2】:

您可以做以下两件事之一:

    在命名外键时遵循 EF 约定,即如果您有虚拟 Address,请将您的键属性定义为 AddressId

    明确告诉 EF 使用什么。一种方法是使用 Fluent API,就像您目前正在做的那样。不过,您也可以使用数据注释:

    [ForeignKey("Address")]
    public int? AddressGlobalKey  get; set; 
    
    public virtual Address Address  get; set; 
    

这是你唯一的选择。

【讨论】:

我以前做过这个,但是我遇到了同步问题。所以我正在寻找一种在 EF6 中执行此操作的方法。 您遇到了什么错误?这是非常基本的功能,在 EF6 中仍然可以使用(除非出现问题,这当然是预发布软件的可能性)。 我在尝试调用 SaveChanges() 时遇到 PK-Constraint 错误。即在这种情况下,当我尝试保存包含对地址对象的引用的人员对象之前创建并保存的地址对象时。 Navigation-Property 是正确的,而相应的 Guid-Property 不是(null 或 Guid.Empty),这通常没有问题。 (续...) 在这种情况下地址对象的状态已添加(应该已附加)但我无法更改它,因为我在不同的类中使用静态通用方法来保存实体。现在,当我删除额外的 AddressGlobalKey-Property 时,一切正常。唯一的就是数据库中列的名称。【参考方案3】:

我知道这有点老了,但这里是我如何通过我的流利配置 (OnModelCreating) 指定映射列的示例:

modelBuilder.Entity<Application>()
            .HasOptional(c => c.Account)
                .WithMany()
                .Map(c => c.MapKey("AccountId"));

希望这会有所帮助,

【讨论】:

@ProfK 我没有投票,但我猜是因为 OP 想要一个全面适用的约定。【参考方案4】:

当字段的类型关闭时,我也看到了同样的问题。仔细检查字段的类型 Ex:

public string StateId get;set;

指向以 int 作为 State.Id 类型的域对象。确保您的类型相同。

【讨论】:

【参考方案5】:

我发现键列自定义没有被 ForeignKeyNamingConvention 捕获。做出这个改变是为了抓住他们。

private bool DoPropertiesHaveDefaultNames(ReadOnlyMetadataCollection<EdmProperty> properties, string roleName, ReadOnlyMetadataCollection<EdmProperty> otherEndProperties)

    if (properties.Count == otherEndProperties.Count)
    
        for (int i = 0; i < properties.Count; ++i)
        
            if (properties[i].Name.EndsWith("_" + otherEndProperties[i].Name))
            
                return true;
            
            else
            
                var preferredNameProperty =
                    otherEndProperties[i]
                        .MetadataProperties
                        .SingleOrDefault(x => x.Name.Equals("PreferredName"));

                if (null != preferredNameProperty)
                
                    if (properties[i].Name.EndsWith("_" + preferredNameProperty.Value))
                    
                        return true;
                    
                
            
        
    
    return false;

【讨论】:

我认为如果您在属性集合中有多个列,那么您已经引入了一个错误,因为您在第一次成功时返回 true【参考方案6】:

我在将它与 EntityNameId 的 id 命名约定结合时遇到了问题。

当使用以下约定来确保 Customer 表具有 CustomerId 而不是简单的 Id。

modelBuilder.Properties()
                        .Where(p => p.Name == "Id")
                        .Configure(p => p.IsKey().HasColumnName(p.ClrPropertyInfo.ReflectedType == null ? "Id" : p.ClrPropertyInfo.ReflectedType.Name +"Id"));

外键命名约定需要更改为以下内容。

 /// <summary>
    /// Provides a convention for fixing the independent association (IA) foreign key column names.
    /// </summary>
    public class ForeignKeyNamingConvention : IStoreModelConvention<AssociationType>
     
        public void Apply(AssociationType association, DbModel model) 
         
            // Identify ForeignKey properties (including IAs)  
            if (!association.IsForeignKey) return;

            // rename FK columns  
            var constraint = association.Constraint; 
            if (DoPropertiesHaveDefaultNames(constraint.FromProperties, constraint.ToProperties)) 
             
                NormalizeForeignKeyProperties(constraint.FromProperties); 
             

            if (DoPropertiesHaveDefaultNames(constraint.ToProperties, constraint.FromProperties)) 
             
                NormalizeForeignKeyProperties(constraint.ToProperties); 
            
         

        private static bool DoPropertiesHaveDefaultNames(IReadOnlyList<EdmProperty> properties, IReadOnlyList<EdmProperty> otherEndProperties) 
         
            if (properties.Count != otherEndProperties.Count) 
             
                return false; 
             

            for (var i = 0; i < properties.Count; ++i)
            
                if (properties[i].Name.Replace("_", "") != otherEndProperties[i].Name) 
                 
                    return false; 
                 
             

            return true; 
         

        private void NormalizeForeignKeyProperties(ReadOnlyMetadataCollection<EdmProperty> properties) 
         
            for (var i = 0; i < properties.Count; ++i) 
             
                var underscoreIndex = properties[i].Name.IndexOf('_'); 
                if (underscoreIndex > 0) 
                 
                    properties[i].Name = properties[i].Name.Remove(underscoreIndex, 1); 
                                 
             
         
    

【讨论】:

【参考方案7】:

这些答案中的大多数都与独立关联(定义了“MyOtherTable”导航属性,但没有定义“int MyOtherTableId”)而不是外键关联(定义了两者)有关。

这很好,因为问题是关于 IA(它使用 MapKey),但我在寻找 FKA 相同问题的解决方案时遇到了这个问题。由于其他人可能出于同样的原因来到这里,我想我会分享我使用 ForeignKeyDiscoveryConvention 的解决方案。

https://***.com/a/43809004/799936

【讨论】:

以上是关于如何按照约定首先删除代码中外键字段的下划线的主要内容,如果未能解决你的问题,请参考以下文章

私有字段的命名约定

Lua词汇约定

约定表名(带下划线)

mysql中外键的创建与删除

想问word文档下划线怎么去掉

Flask-如何理解创建数据模型中外键关联