Ответ 1
Я рассматриваю два случая - обновление существующего бизнеса и добавление нового бизнеса - отдельно, потому что две упомянутые вами проблемы имеют разные причины.
Обновление существующего бизнес-объекта
Это пример if
(if (business.BusinessId > 0)
) в вашем примере. Понятно, что здесь ничего не происходит, и никакие изменения не будут сохранены в базе данных, потому что вы просто присоединяете объекты Category
и объект Business
, а затем вызываете SaveChanges
. Прикрепление означает, что сущности добавляются в контекст в состоянии Unchanged
, а для объектов, находящихся в этом состоянии, EF вообще не будет отправлять какую-либо команду в базу данных.
Если вы хотите обновить граф отдельных объектов - Business
плюс коллекцию объектов Category
в вашем случае - вы, как правило, имеете проблему с тем, что элемент коллекции мог быть удален из коллекции, и элемент мог быть добавлен - по сравнению с текущим состоянием, хранящимся в базе данных. Возможно также, что свойства элемента коллекции и что родительский объект Business
был изменен. Если вы не отследили все изменения вручную, в то время как граф объекта был отсоединен - то есть сам EF не смог отслеживать изменения - что сложно в веб-приложении, потому что вы должны были сделать это в пользовательском интерфейсе браузера, ваш единственный шанс выполнить правильное UPDATE весь граф объекта сравнивает его с текущим состоянием в базе данных, а затем помещает объекты в правильное состояние Added
, Deleted
и Modified
(и, возможно, Unchanged
для некоторых из них).
Итак, процедура заключается в загрузке Business
, включая ее текущий Categories
из базы данных, а затем слияние изменений выделенного графика в загруженный (= прикрепленный) график. Это может выглядеть так:
private ActionResult Save(Business business)
{
if (business.BusinessId > 0) // = business exists
{
var businessInDb = Context.Businesses
.Include(b => b.Categories)
.Single(b => b.BusinessId == business.BusinessId);
// Update parent properties (only the scalar properties)
Context.Entry(businessInDb).CurrentValues.SetValues(business);
// Delete relationship to category if the relationship exists in the DB
// but has been removed in the UI
foreach (var categoryInDb in businessInDb.Categories.ToList())
{
if (!business.Categories.Any(c =>
c.CategoryId == categoryInDb.CategoryId))
businessInDb.Categories.Remove(categoryInDb);
}
// Add relationship to category if the relationship doesn't exist
// in the DB but has been added in the UI
foreach (var category in business.Categories)
{
var categoryInDb = businessInDb.Categories.SingleOrDefault(c =>
c.CategoryId == category.CategoryId)
if (categoryInDb == null)
{
Context.Categories.Attach(category);
businessInDb.Categories.Add(category);
}
// no else case here because I assume that categories couldn't have
// have been modified in the UI, otherwise the else case would be:
// else
// Context.Entry(categoryInDb).CurrentValues.SetValues(category);
}
}
else
{
// see below
}
Context.SaveChanges();
return RedirectToAction("Index");
}
Добавление нового бизнес-объекта
Ваша процедура добавления нового Business
вместе с соответствующим Categories
верна. Просто присоедините все Categories
к существующим объектам в контекст, а затем добавьте новый Business
в контекст:
foreach (var category in business.Categories)
Context.Categories.Attach(category);
Context.Businesses.Add(business);
Context.SaveChanges();
Если все Categories
, которые вы прикрепляете, действительно имеют ключевое значение, существующее в базе данных, это должно работать без исключения.
Ваше исключение означает, что хотя бы один из Categories
имеет недопустимое значение ключа (т.е. он не существует в базе данных). Может быть, он был удален тем временем из БД или потому, что он неправильно отправлен обратно из веб-интерфейса.
В случае независимой ассоциации - это ассоциация без свойства FK BusinessId
в Category
- вы действительно получаете этот OptimisticConcurrencyException
. (Предполагается, что EF предполагает, что категория была удалена из БД другим пользователем.) В случае ассоциации внешнего ключа - это ассоциация, которая имеет свойство FK BusinessId
в Category
- вы получите исключение о нарушении ограничения внешнего ключа.
Если вы хотите избежать этого исключения - и если это происходит на самом деле, потому что другой пользователь удалил Category
, а не потому, что Category
пуст /0
, так как он не отправляется обратно на сервер ( исправьте это со скрытым полем ввода) - вы лучше загружаете категории из CategoryId
(Find
) из базы данных, а не присоединяете их, а если их больше нет, игнорируйте их и удаляйте из коллекции business.Categories
(или перенаправить на страницу с ошибкой, чтобы сообщить пользователю или что-то в этом роде).