Запретить платформу Entity Framework вставлять значения для навигационных свойств
Я работаю над WPF-приложением, использующим Entity Framework 4.0. Когда я попытался сохранить объект, у меня возникло первичное ключевое исключение, но первичный ключ - это поле AutoIncremented, и я не могу понять причину исключения.
Итак, после того, как вы это проделали, и немного отлаживая и используя профилировщик SQL, я обнаружил, что перед вставкой моего объекта запись должна быть вставлена в родительскую таблицу, поскольку я устанавливаю свойство навигации этого объекта.
Таким образом, проблема заключается в попытке вставить объект Employee и установить его отдел как Employee.Department = deptObject, после чего новая запись устанавливается в объект отдела.
Пожалуйста, предложите мне, каким образом объекты навигационной собственности не будут вставлены в базу данных, какое-либо свойство или какой-либо метод Anything.
Спасибо
Ответы
Ответ 1
Так работает 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();
}
Ответ 2
Я хотел бы добавить четвертое решение в дополнение к трем решениям, уже представленным в Ladislavs. В фактах это подробная версия короткого ответа от Наора. Я работаю с картой сущностей 6.
Назначить id депараметра сотруднику вместо объекта отдела
Я склонен иметь свойство "внешнего ключа" в дополнение к свойству навигации в моих классах моделей.
Итак, в классе Employee
у меня есть свойство Department
, а также DepartmentId
типа int (сделать int 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;
Ответ 3
Когда вы устанавливаете отдел для сотрудника - я думаю, что вы должны проверить, что отдел был извлечен из db и прикрепленного объекта.
Кроме того, вы можете поместить идентификатор депрессии (свойство внешнего ключа) вместо того, чтобы устанавливать свойство навигации отдела.