Я сделал консольный тестовый проект с EF 4.3.1. Код - это мое предположение о том, что вы подразумеваете под строкой с комментариями и вашими комментариями под вопросом (но мое предположение, вероятно, неверно, потому что программа не воспроизводит вашу ошибку):
Вы можете скопировать код в program.cs и добавить ссылку на EF 4.3.1:
using System.Data;
using System.Data.Entity;
namespace EFUpdateTest
{
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public int SubObjectId { get; set; }
public SubObject SubObject { get; set; }
}
public class SubObject
{
public int Id { get; set; }
public string Something { get; set; }
}
public class CustomerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<SubObject> SubObjects { get; set; }
}
class Program
{
static void Main(string[] args)
{
int customerId = 0;
int subObject1Id = 0;
int subObject2Id = 0;
using (var ctx = new CustomerContext())
{
// Create customer with subobject
var customer = new Customer { Name = "John" };
var subObject = new SubObject { Something = "SubObject 1" };
customer.SubObject = subObject;
ctx.Customers.Add(customer);
// Create a second subobject, not related to any customer
var subObject2 = new SubObject { Something = "SubObject 2" };
ctx.SubObjects.Add(subObject2);
ctx.SaveChanges();
customerId = customer.Id;
subObject1Id = subObject.Id;
subObject2Id = subObject2.Id;
}
// New context, simulate detached scenario -> MVC action
using (var ctx = new CustomerContext())
{
// Changed customer name
var customer = new Customer { Id = customerId, Name = "Jim" };
ctx.Customers.Attach(customer);
// Changed reference to another subobject
var subObject2 = ctx.SubObjects.Find(subObject2Id);
customer.SubObject = subObject2;
ctx.Entry(customer).State = EntityState.Modified;
ctx.SaveChanges();
// No exception here.
}
}
}
}
Это работает без исключения. Вопрос в том, что отличается в вашем коде, что может вызвать ошибку?
Редактировать
На ваш комментарий, что у вас нет свойства внешнего ключа SubObjectId
в классе клиента: если я удалю свойство в примере программы выше, я смогу воспроизвести ошибку.
Решение состоит в том, чтобы загрузить исходный подобъект из базы данных перед изменением отношения:
// Changed customer name
var customer = new Customer { Id = customerId, Name = "Jim" };
ctx.Customers.Attach(customer);
// Load original SubObject from database
ctx.Entry(customer).Reference(c => c.SubObject).Load();
// Changed reference to another subobject
var subObject2 = ctx.SubObjects.Find(subObject2Id);
customer.SubObject = subObject2;
ctx.Entry(customer).State = EntityState.Modified;
ctx.SaveChanges();
// No exception here.
Без свойства внешнего ключа у вас есть Независимая ассоциация , которая требует, чтобы объект , включая все ссылки , представлял состояние в базе данных перед его изменением. Если вы не установите ссылку SubObject
в customer
EF предполагает, что исходное состояние в базе данных - это то, что клиент не ссылается ни на какой подобъект. Сгенерированный SQL для оператора UPDATE содержит предложение WHERE, например:
WHERE [Customers].[Id] = 1 AND [Customers].[SubObject_Id] IS NULL
Если у клиента есть подобъект в БД [SubObject_Id]
равен , а не NULL, условие не выполняется и ОБНОВЛЕНИЕ не происходит (или происходит для "неожиданного числа строк 0").
Проблема не возникает, если у вас есть свойство внешнего ключа ( Ассоциация внешнего ключа ): предложение WHERE в этом случае только:
WHERE [Customers].[Id] = 1
Таким образом, не имеет значения, каково первоначальное значение SubObject
и SubObjectId
. Вы можете оставить значения null
, и, тем не менее, ОБНОВЛЕНИЕ работает.
Следовательно, альтернативное решение для загрузки исходного подобъекта - ввести свойство внешнего ключа в Customer
:
public int SubObjectId { get; set; }
Или, если отношения не требуются:
public int? SubObjectId { get; set; }