Честно говоря, да, вы делаете что-то не так. По целому ряду причин вы никогда не должны сохранять экземпляр, созданный из пользовательского ввода (то есть экземпляр Person
, передаваемый в ваше действие и создаваемый из тела запроса сообщения), непосредственно в вашу базу данных. Одна из таких причин заключается в том, что это приводит к хаосу с ORM, такими как EF, которые используют отслеживание сущностей для оптимизации запросов.
Просто этот Person
экземпляр здесь не отслеживается - EF ничего не знает об этом. Затем вы используете Add
, чтобы добавить его в свой контекст, что сигнализирует EF, чтобы начать отслеживать его как новую вещь. Когда вы позже сохраняете, EF, то покорно выдает оператор вставки, но поскольку в эту вставку входит идентификатор, вы получаете конфликт первичного ключа. Вместо этого вы хотели, чтобы EF сделал обновление, но он не знает, что должен.
Есть способы, которыми вы можете технически это исправить. Например, вы можете использовать Attach
вместо Add
. Это просто слепо говорит EF, что это то, что он должен отслеживать, не обязательно сообщая, что он должен что-то делать с ним. Если вы внесете какие-либо изменения в этот экземпляр после того, как он будет отслежен, EF изменит его на «измененный», и вы получите инструкцию обновления, которая будет выдана при сохранении. Однако, если вы не вносите какие-либо изменения, а просто сохраняете их напрямую, вам также необходимо явно установить его состояние на «измененный», иначе EF по существу ничего не сделает. Приятно то, что если вы изменяете состояние неотслеживаемой сущности, EF автоматически присоединяет его для отслеживания указанного состояния, поэтому вам не нужно делать Attach
вручную. Если коротко, вы можете очистить исключение, просто заменив строку Add
на:
_context.Entry(person).State = EntityState.Modified;
Однако это может вызвать проблемы, если вы попытаетесь полностью добавить нового человека. Более серьезная проблема, с которой вы столкнулись, заключается в том, что у вас есть одно действие, выполняющее двойной долг Согласно REST, POST не может быть воспроизведен и должен выполняться только для тех ресурсов, которые являются идемпотентными. Проще говоря, вы POST только к ресурсу, подобному /api/person
(а не к чему-то вроде /api/person/1
, и каждый раз, когда вы делаете это , должен быть создан новый человек . Для обновления вы должны сделать запрос к этому фактическому ресурсу, то есть /api/person/1
, и вместо этого HTTP-глагол должен быть PUT. Один и тот же запрос PUT к одному и тому же ресурсу всегда будет иметь один и тот же результат, что имеет место при обновлении определенного ресурса.
Если оставить в стороне теорию, простой момент заключается в том, что у вас должно быть два действия:
[HttpPost("")]
public async Task<IActionResult> AddPerson([FromBody]Person person)
[HttpPut("{id}")]
public async Task<IActionResult> UpdatePerson(int id, [FromBody]Person person)
Наконец, даже при всем этом, сохранение параметра person напрямую ставит слишком большое доверие пользователю при выполнении обновления. Может быть любое количество свойств, которые конечный пользователь не может изменять с помощью обновления (например, что-то вроде «созданной» даты), но они могут это делать, когда вы делаете это. В некотором смысле хуже, даже если пользователь не является злонамеренным, вы по-прежнему полагаетесь на то, что он отправит все данных для этого объекта. Например, если у вас действительно было свойство созданной даты, но пользователь не публикует его со своим обновлением (честно, почему будет , вы публикуете созданную дату вместе с запросом на обновление ресурса), тогда это приведет к очистке этого свойства. Если есть значение по умолчанию, оно будет возвращено к этому, а если нет, вы можете получить исключение при сохранении, если столбец NOT NULL.
Короче говоря, это не очень хорошая идея. Вместо этого используйте модель представления, DTO или подобное. Этот класс должен содержать только те свойства, которые вы хотите разрешить пользователю изменять или вообще влиять на создание. Затем, в случае обновления, вы извлекаете ресурс из базы данных и отображаете на него значения из вашего экземпляра param. Наконец, вы сохраняете версию из базы данных обратно в базу данных. Это гарантирует, что 1) пользователь не может изменять то, что вы явно не разрешаете, 2) пользователю нужно только публиковать то, что он действительно хочет изменить, и 3) объект будет правильно отслеживаться, а EF будет корректно выдавать оператор обновления при сохранении.