Родительский объект находится в EntityState.Unchanged, но он все еще вставлен в базу данных
У меня есть простая схема снежинки, из которой я создал мою модель Entity Framework.
Проблема заключается в том, что я пытаюсь сопоставить дочерний объект с существующим родительским и/или дедушканым бабушкой, но он все равно вставляет его.
Я последовал за этим:
Вставить новый объект с существующим объектом
Запретить платформу Entity Framework вставлять значения для навигационных свойств
Интересно, что хотя EntityState родительских объектов "Unchanged", Entity Framework все еще пытается вставить его.
Схема
![enter image description here]()
Метод CarRepository.Save()
public void Save(Car car)
{
using (DBContext context = new DBContext())
{
// No need to save if it already exists
if ( context.Cars.FirstOrDefault(x => x.RegistrationNumber == car.RegistrationNumber) != null)
{
return;
}
else
{
// Check if the parent POCOs exist in the DB.
Model existingModel = context.Models.FirstOrDefault(x => x.Name == car.Model.Name);
Manufacturer existingManufacturer = context.Manufacturers.FirstOrDefault(x=> x.Name == car.Model.Manufacturer.Name)
Trader existingTrader = context.Traders.FirstOrDefault(x=> x.Name == car.Trader.Name)
TraderCompany existingTraderCompany = context.TraderCompanys.FirstOrDefault(x=> x.Name == car.Trader.TraderCompany.Name)
context.ContextOptions.LazyLoadingEnabled = false;
//Attach to the context if existing in the DB, i.e mark the existing POCOs not to be added the DB
if (existingModel != null)
{
car.Model = existingModel;
Assert.IsTrue(context.ObjectStateManager.GetObjectStateEntry(car.Model).State == EntityState.Unchanged);
}
if (existingManufacturer != null)
{
car.Model.Manufacturer = existingManufacturer;
Assert.IsTrue(context.ObjectStateManager.GetObjectStateEntry(car.Model.Manufacturer).State == EntityState.Unchanged);
}
if (existingTrader != null)
{
car.Trader = existingTrader;
Assert.IsTrue(context.ObjectStateManager.GetObjectStateEntry(car.Trader).State == EntityState.Unchanged);
}
if (existingTraderCompany != null)
{
car.Trader.TraderCompany = existingTraderCompany;
Assert.IsTrue(context.ObjectStateManager.GetObjectStateEntry(car.Trader.TraderCompany).State == EntityState.Unchanged);
}
//Mark the Car for Addition to the DB
context.Cars.AddObject(car);
context.ObjectStateManager.ChangeObjectState(car, EntityState.Added);
//If the POCOs do not exist in the DB mark them for addition
if (existingModel == null)
{
context.ObjectStateManager.ChangeObjectState(car.Model,EntityState.Added);
}
if (existingManufacturer == null)
{
context.ObjectStateManager.ChangeObjectState(car.Model.Manufacturer,EntityState.Added);
}
if (existingTrader == null)
{
context.ObjectStateManager.ChangeObjectState(car.Trader,EntityState.Added);
}
if (existingTraderCompany == null)
{
context.ObjectStateManager.ChangeObjectState(car.Trader.TraderCompany,EntityState.Added);
}
context.SaveChanges();
}
}
}
Изменить:
После нескольких дней возиться мне удалось придумать обходной путь, который работал у меня.
Кажется, что автомобиль, который передается CarRepository.Save(), имеет какой-то внутренний контекст, который является undetectable... Это так, это невозможно отделить его от этого контекста /s и добавить его в файл CarRepository.Save(). Чтобы действительно добавить его в этот контекст, я глубоко/лениво копирую объект Car и его навигационные свойства, если они есть.
Обходной путь
public void Save(Car car)
{
using (DBContext context = new DBContext())
{
// No need to save if it already exists
if ( context.Cars
.Any(x => x.RegistrationNumber == car.RegistrationNumber))
{
return;
}
else
{
//Assign scalar properties to the deep copy
Car carToBeSaved = new Car
{
carToBeSaved.RegistrationNumber = car.RegistrationNumber,
carToBeSaved.Price = car.Price
}
//Car -> Trader -> ...
if(car.Trader != null)
{
Trader existingTrader =
context.Traders
.FirstOrDefault(x => x.Name == car.Trader.Name);
//If exists in DB assign, if not deep copy
carToBeSaved.Trader = existingTrader ??
new Trader
{
Name = car.Trader.Name,
JobTitle = car.Trader.JobTitle
}
//Car -> Trader -> TraderCompany
if(car.Trader.TraderCompany != null)
{
TraderCompany existingTraderCompany =
context.TradersCompanys
.FirstOrDefault(x => x.Name == car.Trader
.TraderCompany
.Name);
//If exists in DB assign, if not deep copy
carToBeSaved.Trader.TraderCompany = existingTraderCompany ??
new TraderCompany
{
Name = car.Trader.TraderCompany.Name,
Address = car.Trader.TraderCompany.Address,
PhoneNumber = car.Trader.TraderCompany.PhoneNumber
}
}
}
//Car -> Model -> ...
if(car.Model != null)
{
Model existingModel =
context.Models
.FirstOrDefault(x => x.Name == car.Model.Name);
//If exists in DB assign, if not deep copy
carToBeSaved.Model = existingModel ??
new Model
{
Name = car.Model.Name
}
//Car -> Model -> Manufacturer
if(car.Model.Manufacturer != null)
{
Manufacturer existingManufacturer =
context.Manufacturers
.FirstOrDefault(x => x.Name == car.Model
.Manufacturer
.Name);
//If exists in DB assign, if not deep copy
carToBeSaved.Model.Manufacturer = existingManufacturer ??
new Manufacturer
{
Name = car.Model.Manufacturer.Name
}
}
}
//Mark the Car for Addition to the DB
context.Cars.AddObject(car);
context.SaveChanges();
}
}
}
Если у кого-то есть какие-то мысли по этому поводу, поделитесь им.
Спасибо.
Ответы
Ответ 1
Самый надежный способ, который я нашел в этом типе проблемы, - использовать идентификатор существующего объекта, а не связывать экземпляр объекта. Таким образом, вы могли бы установить "чужой" ключ трейдеру после поиска существующего трейдера:
car.TraderId = existingTrader.Id;
В течение некоторого времени это казалось мне взломом, но в апрельской версии MSDN mag я читал, что Джулия Лерман также рекомендует этот подход.
Ответ 2
После нескольких дней возиться мне удалось придумать обходной путь, который работал у меня.
Кажется, что автомобиль, который передается CarRepository.Save(), имеет какой-то внутренний контекст, который является undetectable... Это так, это невозможно отделить его от этого контекста /s и добавить его в файл CarRepository.Save(). Чтобы действительно добавить его в этот контекст, я глубоко/лениво копирую объект Car и его навигационные свойства, если они есть.
Обходной путь
public void Save(Car car)
{
using (DBContext context = new DBContext())
{
// No need to save if it already exists
if ( context.Cars
.Any(x => x.RegistrationNumber == car.RegistrationNumber))
{
return;
}
else
{
//Assign scalar properties to the deep copy
Car carToBeSaved = new Car
{
carToBeSaved.RegistrationNumber = car.RegistrationNumber,
carToBeSaved.Price = car.Price
}
//Car -> Trader -> ...
if(car.Trader != null)
{
Trader existingTrader =
context.Traders
.FirstOrDefault(x => x.Name == car.Trader.Name);
//If exists in DB assign, if not deep copy
carToBeSaved.Trader = existingTrader ??
new Trader
{
Name = car.Trader.Name,
JobTitle = car.Trader.JobTitle
}
//Car -> Trader -> TraderCompany
if(car.Trader.TraderCompany != null)
{
TraderCompany existingTraderCompany =
context.TradersCompanys
.FirstOrDefault(x => x.Name == car.Trader
.TraderCompany
.Name);
//If exists in DB assign, if not deep copy
carToBeSaved.Trader.TraderCompany = existingTraderCompany ??
new TraderCompany
{
Name = car.Trader.TraderCompany.Name,
Address = car.Trader.TraderCompany.Address,
PhoneNumber = car.Trader.TraderCompany.PhoneNumber
}
}
}
//Car -> Model -> ...
if(car.Model != null)
{
Model existingModel =
context.Models
.FirstOrDefault(x => x.Name == car.Model.Name);
//If exists in DB assign, if not deep copy
carToBeSaved.Model = existingModel ??
new Model
{
Name = car.Model.Name
}
//Car -> Model -> Manufacturer
if(car.Model.Manufacturer != null)
{
Manufacturer existingManufacturer =
context.Manufacturers
.FirstOrDefault(x => x.Name == car.Model
.Manufacturer
.Name);
//If exists in DB assign, if not deep copy
carToBeSaved.Model.Manufacturer = existingManufacturer ??
new Manufacturer
{
Name = car.Model.Manufacturer.Name
}
}
}
//Mark the Car for Addition to the DB
context.Cars.AddObject(car);
context.SaveChanges();
}
}
}
Если у кого-то есть какие-то мысли по этому поводу, поделитесь им.
Ответ 3
Я думаю, у вас есть следующая проблема:
Если вы установили Model в existingModel, как здесь,
if (existingModel != null)
{
car.Model = existingModel;
Assert.IsTrue(context.ObjectStateManager.GetObjectStateEntry(car.Model).State == EntityState.Unchanged);
}
то ObjectStateManager обнаруживает, что автомобиль является новым и автоматически добавляет его в ваш контекст. Из-за этого EF обнаруживает, что, например, заданный автомобиль. Трейдер неизвестен (означает новый) и добавляет его также. И так далее.
Вот почему тезисы будут сохранены в любом случае.