Entity Framework Обновление навигационной сущности - PullRequest
0 голосов
/ 03 апреля 2012

Я занимаюсь разработкой веб-приложения ASP.Net MVC 3 с использованием Entity Framework 4.1, однако у меня возникла проблема с обновлением навигационной сущности объекта.

У меня есть сущность с именем Shift

public partial class Shift
{
    public Shift()
    {
        this.Locations = new HashSet<ShiftLocation>();
    }

    public int shiftID { get; set; }
    public string shiftTitle { get; set; }

    public virtual ICollection<ShiftLocation> Locations { get; set; }

}

В моем контроллере сдвига у меня есть два метода HttpPost для создания и редактирования сдвига.

[HttpPost]
    public ActionResult CreateShift(ViewModelShift model)
    {

            if (ModelState.IsValid)
            {
                Shift shift = new Shift();
                shift = Mapper.Map<ViewModelShift, Shift>(model);

                //Create new shift location and add it to the newly created Shift
                ShiftLocation location = new ShiftLocation();
                location.locationID = model.locationID;
                shift.Locations.Add(location);

                _shiftService.AddShift(shift);

                return RedirectToAction("Index");
            }
}

[HttpPost]
    public ActionResult EditShift(ViewModelShift model)
    {

            if (ModelState.IsValid)
            {
                Shift shift = new Shift();
                shift = Mapper.Map<ViewModelShift, Shift>(model);


                ShiftLocation location = new ShiftLocation();
                //location = Mapper.Map<ViewModelShift, ShiftLocation>(model);
                location.shiftID = model.shiftID;
                location.locationID = model.locationID;
                shift.Locations.Add(location);

                _shiftService.UpdateShift(shift);

                return RedirectToAction("Index");
            }
        }

Метод CreateShift работает нормально, т.е. вставляет запись в базу данных для нового Shift, а также новую запись для Shift Location в другой таблице. Однако метод EditShift обновляет только запись Shift, но не обновляет запись местоположения Shift, даже если я назначил shiftID и новый locationID.

Любая информация о том, как это исправить, будет очень признательна.

Спасибо.

EDIT

Мой класс ShiftLocation выглядит следующим образом

public partial class ShiftLocation
{
    public int shiftLocationID { get; set; }
    public int shiftID { get; set; }
    public int locationID { get; set; }

    public virtual Shift Shift { get; set; }
}

Метод UpdateShift в классе ShiftService выглядит следующим образом

public void UpdateShift(Shift item)
    {
        _UoW.Shifts.Update(item);
        _UoW.Commit();
    }

И тогда вызывается метод Update в моем универсальном репозитории

public void Update(TEntity entityToUpdate)
    {
        dbSet.Attach(entityToUpdate);
        context.Entry(entityToUpdate).State = EntityState.Modified;
    }

2-е редактирование

Исходя из ответа Слаумы, я отредактировал свое действие PostShift Post на следующее

if (ModelState.IsValid)
            {
                //Shift shift = new Shift();
                Shift shift = _shiftService.GetShiftByID(model.shiftID);
                ShiftLocation shiftLocation = shift.Locations.Where(s => s.shiftID == model.shiftID).Single();

                shift = Mapper.Map<ViewModelShift, Shift>(model);

                shiftLocation.locationID = model.locationID;
                shift.Locations.Add(shiftLocation);

                _shiftService.UpdateShift(shift);

                return RedirectToAction("Index");
            }

Моя проблема сейчас заключается в том, что при вызове метода обновления в моем общем репозитории возникает следующая ошибка

An object with the same key already exists in the ObjectStateManager. 
The ObjectStateManager cannot track multiple objects with the same key

Теперь, я теперь понимаю, почему это происходит, потому что в моем действии EditPost я получаю экземпляр Shift, а затем в методе обновления в общем репозитории я пытаюсь присоединить тот же Shift.

Сейчас я просто запутался. Мой универсальный репозиторий выглядит нормально, так как я скопировал его из учебника Microsoft MVC, упомянутого в начале этого поста.

Опять же, любая помощь в том, как заставить работать это простое обновление, была бы очень признательна.

1 Ответ

1 голос
/ 05 апреля 2012

Ссылаясь на ваш комментарий ...

Когда я достигаю действия 'EditShift', я хочу обновить 'Shift' с новыми деталями, которые извлекаются из переданного в «Модель ViewModelShift». Эта ViewModel также будет содержать 'locationID' только для 1 'ShiftLocation', однако это ShiftLocation уже существует в базе данных, я просто хочу обновить его с новым locationID.

... Я бы попробовал это (опуская структуру вашего хранилища):

[HttpPost]
public ActionResult EditShift(ViewModelShift model)
{
    if (ModelState.IsValid)
    {
        Shift shift = context.Shifts
            .Include(s => s.Locations)
            .Single(s => s.shiftID == model.shiftID);

        context.Entry(shift).CurrentValues.SetValues(model);

        ShiftLocation location = shift.Locations.Single();
        // because you expect exactly one location

        location.locationID = model.locationID;

        context.SaveChanges();

        return RedirectToAction("Index");
    }

    //...
}

Установка состояния shift на Modified не работает, поскольку не влияет на любые связанные объекты. Место будет по-прежнему в состоянии Unchanged. Вы могли бы также установить состояние для местоположения на Modified, но, похоже, у вас нет свойства первичного ключа shiftLocationID в вашей ViewModel. Таким образом, единственный способ найти правильное местоположение для обновления - загрузить его через свойство навигации сдвига (= Include в приведенном выше примере).

Редактировать

Код в вашем 2-м редактировании не работает, потому что AutoMapper создает новый экземпляр shift, и позже вы присоединяете этот сдвиг к контексту. Но поскольку вы уже загрузили shift из базы данных, этот shift также привязан к контексту. Вы не можете прикрепить два разных экземпляра с одним и тем же ключом к контексту. Это вызывает исключение.

Мой код выше должен работать, я пытаюсь переписать его, используя ваш сервис и общий репозиторий. Оба должны быть расширены, потому что сервис и хранилище не обладают достаточным количеством функций для выполнения задачи. Особенно универсальный Update слишком слаб для обновления графа объектов (корневой объект + одно или несколько свойств навигации).

[HttpPost]
public ActionResult EditShift(ViewModelShift model)
{
    if (ModelState.IsValid)
    {
        Shift shift = Mapper.Map<ViewModelShift, Shift>(model);
        _shiftService.UpdateShiftAndLocation(shift, model.LocationID);

        return RedirectToAction("Index");
    }

    //...
}

Создать новый метод в сервисе:

public void UpdateShiftAndLocation(Shift detachedShift, int locationID)
{
    Shift attachedShift = _UoW.Shifts.Find(
        s => s.ShiftID == detachedShift.ShiftID, s => s.Locations);

    _UoW.Shifts.UpdateFlatProperties(attachedShift, detachedShift);

    ShiftLocation location = attachedShift.Locations.Single();
    // MUST be exactly one location for the Shift in the DB
    // ToDo: Catch the case somehow, if there is no or more than one location
    location.LocationID = locationID;

    _UoW.Commit();
}

Find метод в вашем общем хранилище:

public IQueryable<T> Find(Expression<Func<T, bool>> predicate,
    params Expression<Func<T, object>>[] includes)
{
    return dbSet.IncludeMultiple(includes)
        .Where(predicate);
}

(Используйте метод расширения IncludeMultiple Ладислава IQueryabe<T> отсюда: https://stackoverflow.com/a/5376637/270591)

UpdateFlatProperties метод в вашем общем хранилище:

// "Flat" means: Scalar and Complex properties, but not Navigation properties
public void UpdateFlatProperties(T attachedEntity, T detachedEntity)
{
    context.Entry(attachedEntity).CurrentValues.SetValues(detachedEntity);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...