Запретить Entity Framework для вставки значений для навигационных свойств - PullRequest
14 голосов
/ 25 октября 2011

Я работаю над приложением WPF, использующим Entity Framework 4.0. Когда я попытался сохранить объект, я получил исключение первичного ключа, но первичным ключом является поле AutoIncremented, и я не могу понять причину исключения.

Итак, после того, как я попробовал то и это, немного отладил и использовал профилировщик SQL, я обнаружил, что перед вставкой моего объекта необходимо вставить запись в родительскую таблицу, так как я устанавливаю свойство навигации этого объекта.

Таким образом, суть в том, что если попытка вставить объект Employee и установить его отдел как Employee.Department = deptObject, то будет установлена ​​новая запись для вставки в объект отдела.

Пожалуйста, подскажите мне, каким образом объекты навигационных свойств не будут вставлены в базу данных, любое свойство или любой метод, что угодно.

Спасибо

Ответы [ 3 ]

42 голосов
/ 25 октября 2011

Так работает EF, если вы неправильно используете отдельные объекты. Я полагаю, вы используете что-то вроде этого:

var employee = new Employee();
employee.Department = GetDepartmentFromSomewhere(departmentId);

...

using (var context = new YourContext())
{
    context.Employees.AddObject(employee);
    context.SaveChanges();
}

Этот код подготовил сущность сотрудника, добавил ссылку на существующий отдел и сохранил нового сотрудника в базе данных. В чем проблема? Проблема в том, что AddObject добавляет не только сотрудник, но и весь граф объектов. Вот как работает EF - вы не можете иметь граф объектов, где часть объектов связана с контекстом, а часть нет. AddObject добавляет каждый объект на графике как новый (новый = вставить в базу данных). Поэтому вы должны либо изменить последовательность своих операций, либо вручную исправить состояние объектов, чтобы ваш контекст знал, что отдел уже существует.

Первое решение - использовать тот же контекст для загрузки отдела и сохранения сотрудника:

using (var context = new YourContext())
{
    var employee = new Employee();
    ...
    context.Employees.AddObject(employee);

    employee.Department = context.Departments.Single(d => d.Id == departmentId);
    context.SaveChanges();
}

Второе решение - подключить сущности к контексту отдельно и после этого сделать ссылку между сущностями:

var employee = new Employee();
...

var department = GetDepartmentFromSomewhere(departmentId);

using (var context = new YourContext())
{
    context.Employees.AddObject(employee);
    context.Departments.Attach(department);
    employee.Department = department;

    context.SaveChanges();
}

Третье решение - исправить состояние отдела вручную, чтобы контекст не вставил его снова:

var employee = new Employee();
employee.Department = GetDepartmentFromSomewhere(departmentId);

...

using (var context = new YourContext())
{
    context.Employees.AddObject(employee);
    context.ObjectStateManager.ChangeObjectState(employee.Department, 
                                                 EntityState.Unchanged);
    context.SaveChanges();
}
3 голосов
/ 23 августа 2016

Я хотел бы добавить 4-е решение в дополнение к 3-м решениям, уже представленным в Ладиславе. Отличный ответ.На самом деле это подробный вариант краткого ответа от Наора.Я работаю с платформой сущности версии 6.


Назначить идентификатор отдела сотруднику вместо объекта отдела

Я склонен ку меня есть свойство "значение внешнего ключа" в дополнение к свойству навигации в моих классах модели.

Так что в классе Employee у меня есть свойство Department и также DepartmentId типа int (makeint nullable, если возможно, что Employee не имеет Department):

public class Employee
{
    public int Id { get; set; }

    public String EmployeeName { get; set; }


    #region FK properties

    public Department Department { get; set; }

    public int? DepartmentId { get; set; }

    #endregion
}

Вы могли бы сделать это сейчас, просто установив DepartmentId: Итак, вместо:

employee.Department = departmentObject;

просто установить:

employee.DepartmentId = departmentObject.Id;

или

employee.DepartmentId = departmentid

Теперь при вызове SaveChanges для добавленного сотрудника сохраняется только сотрудник, и новый отдел не создается.Но ссылка от Employee до Department установлена ​​правильно из-за назначенного идентификатора отдела.


Подробнее

Обычно я получаю доступ к объекту Department класса Employee только при чтении / обработке сотрудников.При создании или обновлении сотрудников я бы использовал свойство DepartmentId класса Employee для назначения.

Не присваивание свойству Department Employee имеет один недостаток:отладка сложнее, потому что перед вызовом SaveChanges и повторным чтением сотрудников было бы невозможно увидеть или использовать Department объект Employee.


Исправление информации о состоянии объекта в EF6

Это относится к решению Ladislavs номер 3.

С EF6 это делается следующим образом:

_context.Entry(employee.Department).State = EntityState.Unchanged;
0 голосов
/ 25 октября 2011

Когда вы устанавливаете отдел для сотрудника - я думаю, что вы должны убедиться, что отдел был получен из БД и к нему присоединен объект.
Кроме того, вы можете вместо идентификатора deprtment (свойство внешнего ключа) указатьустановить свойство навигации отдела.

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