Entity Framework: автоматическое обновление внешнего ключа при установке новой ссылки на объект - PullRequest
8 голосов
/ 11 июня 2010

Я портирую существующее приложение из Linq в SQL на Entity Framework 4 (генерация кода по умолчанию).

Я заметил одно различие между ними в том, что свойство внешнего ключа не обновляется при сбросе ссылки на объект. Теперь мне нужно решить, как с этим бороться.

Например, предположим, что у вас есть два типа сущностей, Компания и Сотрудник. Одна компания имеет много сотрудников.

В Linq To SQL установка компании также устанавливает идентификатор компании:

var company=new Company(ID=1);
var employee=new Employee();
Debug.Assert(employee.CompanyID==0);
employee.Company=company;
Debug.Assert(employee.CompanyID==1); //Works fine!

В Entity Framework (и без использования какой-либо настройки шаблона кода) это не работает:

var company=new Company(ID=1);
var employee=new Employee();
Debug.Assert(employee.CompanyID==0);
employee.Company=company;
Debug.Assert(employee.CompanyID==1); //Throws, since CompanyID was not updated!

Как заставить EF вести себя так же, как LinqToSQL? Я взглянул на шаблон T4 для генерации кода по умолчанию, но не смог понять, как внести необходимые изменения. Кажется, что однострочник должен сделать трюк, но я не мог понять, как получить свойство ID для данной ссылки.

Ответы [ 2 ]

4 голосов
/ 15 июня 2010

Из того, что я вижу в шаблоне T4 по умолчанию, свойства внешнего ключа сущностей не связаны напрямую со ссылкой на сущность, связанную с ключом.

Есть пары, подходящие к вашей проблеме в отношении миграции изLinq для SQL в EF4.Одним из них будет регистрация на событие AssociationChanged ваших ассоциаций, чтобы оно автоматически обновляло ваше поле.В вашем контексте один из подходов может выглядеть примерно так:

// Extends Employee entity
public partial class Employee
{
    private void CompanyChanged(Object sender, CollectionChangeEventArgs e)
    {
        // Apply reactive changes; aka set CompanyID
        // here
    }

    // Create a default constructor that registers your event handler
    public Employee()
    {
        this.CompanyReference.AssociationChanged += CompanyChanged;
    }
}

Лично, если вы хотите ограничить обслуживание, необходимое для поддержки такой логики, я бы предложил изменить ваш шаблон T4 (либо изменитьэто самостоятельно или найдите), чтобы установить CompanyId при изменении Company, как показано ранее.

Гил Финк написал довольно хорошее введение в шаблоны T4 с EF4, и вы можете посмотреть Скотт Хансельман , обернул множество полезных ссылок и ресурсов для работы сШаблоны T4.

На последнем замечании, если я не ошибаюсь, прямой доступ к внешним ключам в качестве свойств объекта является чем-то новым от EF3.5 до 4. В 3.5 единственный доступ к нему был только черезсвязанный объект (Employee.Company.CompanyID).Я полагаю, что эта функция была добавлена ​​в EF4, чтобы вам не приходилось загружать ассоциации (используя «include»), чтобы получить внешний ключ при выборе из хранилища данных.

Возможно, EF примет этобыть, если у вас есть ассоциация, сначала пройдите через ассоциацию, чтобы получить удостоверение личности.Но это всего лишь предположение, поскольку я не получил никаких цитат, подтверждающих это.

[EDIT 2010-06-16] : После быстрого изучения и анализа xml-элементов edmx я нашел одинназывается ReferentialConstraint, который, по-видимому, содержит поля внешнего ключа для конкретного FK_Relation.

Здесь фрагмент кода для изменения в шаблоне edmx T4 по умолчанию, раздел Запись свойств навигации.(Template_RegionNavigationProperties), около строки 388 неизмененного шаблона.Попробуйте игнорировать ужасное форматирование ...

<#=code.SpaceAfter(NewModifier(navProperty))#><#=Accessibility.ForProperty(navProperty)#> <#=MultiSchemaEscape(navProperty.ToEndMember.GetEntityType(), code)#> <#=code.Escape(navProperty)#>
    {
        <#=code.SpaceAfter(Accessibility.ForGetter(navProperty))#>get
        {
            return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<<#=MultiSchemaEscape(navProperty.ToEndMember.GetEntityType(), code)#>>("<#=navProperty.RelationshipType.FullName#>", "<#=navProperty.ToEndMember.Name#>").Value;
        }
        <#=code.SpaceAfter(Accessibility.ForSetter(navProperty))#>set
        {
            // edit begins here
            if(value != null)
            {
                // Automatically sets the foreign key attributes according to linked entity

<#
            AssociationType association = GetSourceSchemaTypes<AssociationType>().FirstOrDefault(_ => _.FullName == navProperty.RelationshipType.FullName);
            foreach(var cons in  association.ReferentialConstraints)
            {
                foreach(var metadataProperty in cons.FromProperties)
                {
#>
                this.<#=metadataProperty.Name#> = value.<#=metadataProperty.Name#>;
                //this._<#=metadataProperty.Name#> = value._<#=metadataProperty.Name#>; // use private field to bypass the OnChanged events, property validation and the likes..

<#
                }
            }
#>  
            }
            else
            {
                // what usually happens in Linq-to-SQL when an association is set to null
                // here
            }
            // edit ends here

            ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<<#=MultiSchemaEscape(navProperty.ToEndMember.GetEntityType(), code)#>>("<#=navProperty.RelationshipType.FullName#>", "<#=navProperty.ToEndMember.Name#>").Value = value;
        }
    }

Я грубо проверил его, но это факт, что есть некоторая проверка и такая пропущенная.Возможно, это может дать вам подсказку к решению независимо.

2 голосов
/ 18 февраля 2011

Спасибо за это решение.Я улучшил его (больше не зависит от конкретных соглашений об именах) и включил в исправление, которое также устраняет другую проблему с шаблоном Entity Framework.

Проверьте здесь мое решение и генерацию исправленного кодашаблон

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...