В чем смысл функции обновления в шаблоне Repository EF?

Я использую шаблон репозитория в EF, используя функцию Update, которую я нашел в Интернете.

public class Repository<T> : IRepository<T> where T : class
{
    public virtual void Update(T entity)
    {
        var entry = this.context.Entry(entity);
        this.dbset.Attach(entity);
        entry.State = System.Data.Entity.EntityState.Modified;
    }
}

Затем я использую его в DeviceService вот так:

public void UpdateDevice(Device device)
{
    this.serviceCollection.Update(device);
    this.uow.Save();
}

Я понимаю, что на самом деле это обновляет ВСЮ информацию об устройстве, а не просто обновляет измененное свойство. Это означает, что в многопоточной среде изменения могут быть потеряны.

После тестирования я понял, что могу просто изменить Device, а затем вызвать uow.Save(), что сохранит данные и не перезапишет существующие изменения.

Итак, мой вопрос действительно таков: какой смысл в функции Update()? Он появляется почти в каждом шаблоне репозитория, который я нахожу в Интернете, но кажется разрушительным.


person Chris    schedule 13.02.2014    source источник
comment
Он проливает свет на качество почти каждого шаблона репозитория, который вы найдете в Интернете.   -  person Slauma    schedule 13.02.2014
comment
Ха, действительно! Однако это сложный шаблон для реализации без примера кода!   -  person Chris    schedule 13.02.2014
comment
Подробнее об этом здесь: Entity Framework — зачем явно задавать состояние объекта как измененное?.   -  person Gert Arnold    schedule 21.09.2015
comment
Почему должно иметь значение, сколько свойств вы изменяете, если ваш объект (или код, который его использует) не является потокобезопасным? Меньшие изменения могут уменьшить вероятность, но если это не потокобезопасно, то оно не является многопоточно безопасным, и проблемы возникают рано или поздно в многопоточной среде.   -  person The incredible Jan    schedule 14.07.2021


Ответы (2)


Я бы не назвал этот общий метод Update в целом "деструктивным", но я согласен с тем, что он имеет ограниченные варианты использования, которые редко обсуждаются в этих реализациях репозитория. Полезен ли метод или нет, зависит от сценария, в котором вы хотите его применить.

В «прикрепленном сценарии» (например, приложение Windows Forms), когда вы загружаете объекты из базы данных, изменяете некоторые свойства, пока они все еще подключены к контексту EF, а затем сохраняете изменения, этот метод бесполезен, потому что контекст все равно будет отслеживать все изменения и знать в конце, какие столбцы должны быть обновлены или нет. В этом сценарии вам вообще не нужен метод Update (подсказка: DbSet<T> (который является универсальным репозиторием) по этой причине не имеет метода Update). А в ситуации параллелизма это деструктивно, да.

Однако неясно, иногда ли «обновление с отслеживанием изменений» не является разрушительным. Если два пользователя изменяют одно и то же свойство на разные значения, обновление с отслеживанием изменений для обоих пользователей сохранит новое значение столбца, и последний из них выиграет. Если это нормально или нет, зависит от приложения и от того, насколько безопасно оно хочет, чтобы изменения были сделаны. Если приложение запрещает когда-либо редактировать объект, который не является последней версией в базе данных до того, как изменение будет сохранено, оно не может допустить, чтобы последнее сохранение имело преимущество. Он должен был бы остановиться, заставить пользователя перезагрузить последнюю версию и просмотреть последние значения, прежде чем он введет свои изменения. Чтобы справиться с этой ситуацией, необходимы маркеры параллелизма, которые бы обнаруживали, что кто-то еще тем временем изменил запись. Но эти проверки параллелизма работают одинаково с обновлениями с отслеживанием изменений или при установке состояния сущности на Modified. Разрушительный потенциал обоих методов останавливается исключениями параллелизма. Однако установка состояния на Modified по-прежнему создает ненужные накладные расходы, поскольку в базу данных записываются неизмененные значения столбцов.

В «отключенном сценарии» (например, веб-приложение) обновление с отслеживанием изменений недоступно. Если вы не хотите устанавливать для всего объекта значение Modified, вам необходимо загрузить последнюю версию из базы данных (в новом контексте), скопировать свойства, полученные из пользовательского интерфейса, и снова сохранить изменения. Однако это не предотвращает перезапись изменений, внесенных другим пользователем за это время, даже если они являются изменениями в других свойствах. Представьте, что два пользователя одновременно загружают одну и ту же сущность клиента в веб-форму. Пользователь 1 редактирует имя клиента и сохраняет. Пользователь 2 редактирует номер банковского счета клиента и сохраняет его через несколько секунд. Если объект загружается в новый контекст для выполнения обновления для пользователя 2, EF просто увидит, что имя клиента в базе данных (которое уже включает изменение пользователя 1) отличается от имени клиента, отправленного обратно пользователем 2 (которое по-прежнему старое имя клиента). Если вы скопируете значение имени клиента, свойство будет помечено как измененное, а старое имя будет записано в базу данных и перезапишет изменение пользователя 1. Это обновление будет столь же разрушительным, как и изменение состояния всего объекта на измененное. Чтобы избежать этой проблемы, вам нужно будет либо реализовать некоторое пользовательское отслеживание изменений на стороне клиента, которое распознает, изменил ли пользователь 2 имя клиента, а если нет, то просто не копирует значение в загруженный объект. Или вам придется снова работать с токенами параллелизма.

Вы не упомянули самое большое ограничение этого метода Update в своем вопросе, а именно то, что он не обновляет никакие связанные объекты. Например, если у вашего объекта Device есть связанная коллекция Parts, и вы будете редактировать эту коллекцию в отдельном пользовательском интерфейсе (добавлять/удалять/изменять элементы), устанавливая состояние родительского объекта Device на Modified, эти изменения не сохранятся в базе данных. . Это повлияет только на скалярные (и сложные) свойства самого родителя Device. В то время, когда я использовал репозитории такого типа, я назвал метод обновления FlatUpdate, чтобы лучше указать это ограничение в имени метода. Я никогда не видел общего "DeepUpdate". Работа со сложными графами объектов — это всегда не универсальная вещь, которая должна быть написана индивидуально для каждого типа объекта и в зависимости от ситуации. (К счастью, такая библиотека, как entity/" rel="nofollow">GraphDiff может ограничить объем кода, который необходимо написать для таких обновлений графа.)

Короче говоря:

  • Для подключенных сценариев метод Update является избыточным, поскольку автоматическое отслеживание изменений EF выполняет всю необходимую работу для записи правильных инструкций UPDATE в базу данных, включая изменения в графах связанных объектов.
  • Для отдельных сценариев это удобный способ выполнения обновлений простых сущностей без связей.
  • Обновление графов объектов с родительскими и дочерними объектами в отдельном сценарии невозможно выполнить с помощью такого упрощенного метода Update и требует значительно большей (неуниверсальной) работы.
  • Для безопасного управления параллелизмом требуются более сложные инструменты, такие как включение оптимистичных проверок параллелизма, предоставляемых EF, и обработка возникающих исключений параллелизма удобным для пользователя способом.
person Slauma    schedule 13.02.2014
comment
Большое спасибо за этот ответ, он очень помог... Итак, в случае, когда я хочу сделать глубокое сохранение (т.е. я изменил элемент в коллекции Device), недостаточно просто внести изменения, а затем вызвать uow.Save() ? К вашему сведению, мы отделены друг от друга в том смысле, что WebService контролирует все сохранение и загрузку данных. - person Chris; 14.02.2014

После очень глубокого и практического ответа Слаумы я хотел бы остановиться на некоторых основных принципах.

В этой статье MSDN есть одно важное предложение

Репозиторий отделяет бизнес-логику от взаимодействия с базовым источником данных или веб-службой.

Простой вопрос. Какое отношение бизнес-логика имеет к Update?

Фаулер определяет шаблон репозитория как

Выступает посредником между слоями отображения домена и данных, используя интерфейс, похожий на набор, для доступа к объектам домена.

Итак, с точки зрения бизнес-логики репозиторий — это просто коллекция. Семантика коллекций связана с добавлением и удалением объектов или проверкой существования объекта. Основные операции: Add, Remove и Contains. Ознакомьтесь с интерфейсом ICollection<T>: нет Update метод есть.

Бизнес-логика не заботится о том, следует ли помечать объекты как «модифицированные». Он просто изменяет объекты и полагается на другие слои для обнаружения и сохранения изменений. Предоставление метода Update

  • возлагает на бизнес-уровень ответственность за отслеживание изменений и отчетность о них. Скоро появятся всевозможные if конструкции, чтобы проверять, изменились значения или нет.
  • разрушает игнорирование постоянства, потому что сам факт того, что хранение обновлений — это нечто иное, чем хранение новых объектов, является деталью уровня данных.
  • не позволяет уровню доступа к данным выполнять свою работу должным образом. Действительно, реализация, которую вы показываете, разрушительна. Хотя уровень доступа к данным может прекрасно воспринимать и сохранять детализированные изменения, этот метод помечает весь объект как измененный и принудительно проводит UPDATE.
person Gert Arnold    schedule 14.02.2014