Как сначала удалить подчеркивание полей внешнего ключа в коде по соглашению

У меня есть несколько классов (включая TPT) в моем проекте. Каждый POCO имеет BaseClass, который имеет GUID (называемый GlobalKey) в качестве первичного ключа.

Сначала я использовал DataAnnotations для создания правильных внешних ключей. Но тогда у меня проблемы с синхронизацией соответствующего GUID с самим объектом.

Теперь я хочу иметь только одно свойство виртуальной навигации, чтобы поле GUID в базе данных создавалось NamingConvention. Но к имени поля всегда добавляется символ подчеркивания, за которым следует слово GlobalKey (что правильно). Когда я хочу удалить подчеркивание, я не хочу проходить через все свои POCO в свободном API, чтобы сделать это:

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

Любые идеи сделать это для всех POCOS, перезаписав соглашение?

Заранее спасибо.

Андреас


person Andreas Geier    schedule 28.03.2013    source источник
comment
Вы не можете сделать это - пока. Для EF 6.0, но пока - вам придется справиться с этим самостоятельно, вручную...   -  person marc_s    schedule 28.03.2013
comment
На самом деле я работаю с EF6 alpha 2. Можно ли использовать его таким образом?   -  person Andreas Geier    schedule 28.03.2013
comment
Проверьте это сообщение в блоге — похоже, этот парень делает что-то похожее на то, что вы хотите.   -  person marc_s    schedule 28.03.2013
comment
Спасибо, Марк. Это почти соответствует моим требованиям, но я могу использовать это, когда у меня закодированы NavigationProperties и я не хочу добавлять [ForeignKey]-DataAnnotation. Но я не закодировал какое-либо свойство Id или Guid. Моим единственным навигационным свойством является сам объект. Поэтому EF создает дополнительное свойство с именем i.E. Адрес_ГлобальныйКлюч. Это имя, которым я хочу манипулировать в соглашении. Я не мог найти блог, как это сделать.   -  person Andreas Geier    schedule 02.04.2013
comment
Пожалуйста, отметьте лучший ответ.   -  person crimbo    schedule 09.01.2014


Ответы (7)


Наконец-то я нашел ответ на этот вопрос, написав собственное соглашение. Это соглашение работает в 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 в вашем переопределении DbContext.OnModelCreating, используя:

modelBuilder.Conventions.Add(new ForeignKeyNamingConvention());
person crimbo    schedule 15.08.2013
comment
Вместо того, чтобы добавлять соглашение в конце, вы можете добавить его перед ForeignKeyIndexConvention, чтобы применить соглашение об именах к именам индексов внешних ключей --- modelBuilder.Conventions.AddBefore<System.Data.Entity.ModelConfiguration.Conventions.ForeignKeyIndexConvention>(new ForeignKeyNamingConvention()); - person Kedar Vaidya; 09.07.2014
comment
Этот код у меня не совсем работал, потому что этот тест всегда был ложным: if (!properties[i].Name.EndsWith(_ + otherEndProperties[i].Name)) На самом деле само имя уже содержало подчеркивание. Поэтому я пропустил весь этот тест и всегда вызывал NormalizeForeignKeyProperties. - person Rob Kent; 15.07.2014
comment
Это было сделано специально — если свойство Name имеет подчеркивание, то DoPropertiesHaveDefaultNames() должно возвращать true. Это похоже на то, что происходит для вас? Причина этой проверки в том, что она не перезаписывает имена внешних ключей, зафиксированные другими соглашениями или указанные во время настройки. - person crimbo; 15.07.2014
comment
Эй смотри! Microsoft позаимствовала мой ответ для MSDN: msdn.microsoft.com/en-us /data/dn469439.aspx#example2 Поскольку они так замечательно относятся к проектам с открытым исходным кодом, таким как EF и ASP.NET, я рад оставить это без внимания... - person crimbo; 23.10.2014
comment
Просто в качестве примечания... Я предпочитаю просто использовать properties[i].Name = navigationPropertyName для установки имени столбца. Я реализовал это в коде и еще не видел никаких проблем, кто-нибудь знает о них? - person AGoodDisplayName; 05.03.2015
comment
Причина, по которой у меня есть проверка targetKey.StartsWith(navigationPropertyName), заключается в том, что такие случаи, как Invoice_InvoiceId, становятся InvoiceId, а Invoice_Nbr становятся InvoiceNbr. Это просто отражает мои стандарты предпочтений/кодирования. - person crimbo; 05.03.2015
comment
Я могу опоздать, но я создал суть, чтобы улучшить ответ @crimbo: gist.github.com /rexcfnghk/3a6becedb3e09819a4e0 - person rexcfnghk; 18.05.2015
comment
Я просто наткнулся на это, а также обнаружил, что это не работает, как ожидалось. Проверка if (!properties[i].Name.EndsWith("_" + otherEndProperties[i].Name)) в моем случае сравнивает исходный конец (например, Project_Id) с конечным (например, ProjectId), который возвращает false и, следовательно, не исправляет исходный конец. Похоже, это связано с тем, что у меня также есть PrimaryKeyConvention, который удаляет символы подчеркивания из имен. Таким образом, может показаться, что проверка, чтобы убедиться, что это не противоречит другим соглашениям, на самом деле приводит к конфликту с другими соглашениями. - person Derek Greer; 20.09.2016
comment
Кроме того, свойство roleName нигде не используется. - person Derek Greer; 20.09.2016

Вы можете сделать одно из двух:

  1. Следуйте соглашениям EF при именовании внешних ключей, т. е. если у вас есть виртуальный Address, определите свойство ключа как AddressId.

  2. Явно укажите EF, что использовать. Один из способов сделать это — использовать Fluent API, как вы сейчас делаете. Однако вы также можете использовать аннотации данных:

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

Это ваш единственный выбор.

person Chris Pratt    schedule 28.03.2013
comment
Я делал это раньше, но у меня возникли проблемы с синхронизацией. Поэтому я ищу способ сделать это в EF6. - person Andreas Geier; 28.03.2013
comment
Какие ошибки вы получили? Это довольно базовая функциональность, и она все еще должна работать в EF6 (если что-то не сломано, что, конечно, возможно с предварительным выпуском программного обеспечения). - person Chris Pratt; 28.03.2013
comment
Я получил ошибки PK-Constraint при попытке вызвать SaveChanges(). т.е. в ситуации, когда объект Address, который был создан и сохранен до того, как я попытался сохранить Person-Object, который содержал ссылку на Address-Object. Navigation-Property было правильным, а соответствующее Guid-Property — нет (null или Guid.Empty), что обычно не вызывает проблем. (продолжение...) - person Andreas Geier; 29.03.2013
comment
Состояние объекта-адреса в этой ситуации было добавлено (должно было быть прикреплено), но я не мог его изменить, потому что использовал статический общий метод в другом классе для сохранения объектов. Теперь, когда я удалил дополнительное свойство AddressGlobalKey-Property, все заработало нормально. Единственное, это имя столбца в базе данных. - person Andreas Geier; 29.03.2013

Я знаю, что это немного устарело, но вот пример того, как я указываю сопоставление столбцов с помощью моей гибкой конфигурации (OnModelCreating):

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

Надеюсь это поможет,

person covo    schedule 10.03.2015
comment
@ProfK Я не голосовал, но я предполагаю, потому что ОП хотел соглашения, которое будет применяться повсеместно. - person Casey; 01.04.2016

Я также видел ту же проблему, когда тип поля выключен. Дважды проверьте тип поля Пример:

public string StateId {get;set;}

указывающий на объект домена с int в качестве типа State.Id. Убедитесь, что ваши типы совпадают.

person JimmyGoods    schedule 05.05.2015

Я обнаружил, что настройки ключевых столбцов не перехватываются конвенцией 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;
}
person Sean M    schedule 13.08.2014
comment
Я думаю, вы ввели ошибку, если у вас есть более одного столбца в коллекции свойств, поскольку вы возвращаете true при первом успехе. - person Paul Hatcher; 13.06.2015

У меня были проблемы при объединении его с соглашением об именах идентификаторов EntityNameId.

При использовании следующего соглашения для обеспечения того, чтобы таблица 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); 
                }                 
            } 
        } 
    }
person CountZero    schedule 23.01.2015

Большинство этих ответов связаны с независимыми ассоциациями (где определено свойство навигации «MyOtherTable», но не «int MyOtherTableId») вместо ассоциаций внешнего ключа (где определены оба).

Это нормально, поскольку вопрос касается IA (он использует MapKey), но я столкнулся с этим вопросом при поиске решения той же проблемы с FKA. Поскольку другие люди могут прийти сюда по той же причине, я решил поделиться своим решением, в котором используется ForeignKeyDiscoveryConvention.

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

person kevinpo    schedule 05.05.2017