Где выполнить двойную проверку для объекта

Я ищу совет в «лучшем» месте, чтобы поставить логику проверки, такую ​​как двойная проверка для сущности при использовании Entity Framework Code-First, в приложении MVC.

Чтобы использовать простой пример:

public class JobRole { public int Id { get; set; } public string Name { get; set; } } 

Правило заключается в том, что поле «Имя» должно быть уникальным.

Когда я добавляю новый JobRole, легко запустить проверку в репозитории роли задания, что имя еще не существует.

Но если пользователь редактирует существующий JobRole и случайно устанавливает имя в уже существующее, как я могу это проверить?

Проблема заключается в том, что в репозитории не требуется «обновление», так как объект Job Role Entity автоматически обнаружит изменения, поэтому нет места для проверки, прежде чем пытаться сохранить.

Я рассмотрел два варианта:

  1. Переопределите метод ValidateEntry в DbContext, а затем, когда объект JobRole будет сохранен с EntityState.Modified, запустите повторную проверку.
  2. Создайте службу проверки дубликатов, вызванную из controllerа, перед попыткой сохранения.

Ничего не кажется идеальным. Использование ValidateEntry кажется довольно запоздалым (незадолго до сохранения) и трудным для тестирования. Использование службы оставляет возможность того, что кто-то забывает называть ее с controllerа, позволяя дублировать данные.

Есть ли способ лучше?

Ваша проблема с ValidateEntity заключается в том, что проверка выполняется на SaveChanges, и это слишком поздно для вас. Но в Entity Framework 5.0 вы можете вызвать проверку раньше, если хотите использовать DbContext.GetValidationErrors . И, конечно же, вы можете просто вызвать DbContext.ValidateEntity напрямую. Вот как я это делаю:

  1. Переопределите метод ValidateEntity в DbContext :

     protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary items) { //base validation for Data Annotations, IValidatableObject var result = base.ValidateEntity(entityEntry, items); //You can choose to bail out before custom validation //if (result.IsValid) // return result; CustomValidate(result); return result; } private void CustomValidate(DbEntityValidationResult result) { ValidateOrganisation(result); ValidateUserProfile(result); } private void ValidateOrganisation(DbEntityValidationResult result) { var organisation = result.Entry.Entity as Organisation; if (organisation == null) return; if (Organisations.Any(o => o.Name == organisation.Name && o.ID != organisation.ID)) result.ValidationErrors .Add(new DbValidationError("Name", "Name already exists")); } private void ValidateUserProfile(DbEntityValidationResult result) { var userProfile = result.Entry.Entity as UserProfile; if (userProfile == null) return; if (UserProfiles.Any(a => a.UserName == userProfile.UserName && a.ID != userProfile.ID)) result.ValidationErrors.Add(new DbValidationError("UserName", "Username already exists")); } 
  2. Context.SaveChanges в try catch и создайте метод для доступа к Context.GetValidationErrors( ). Это в моем classе UnitOfWork :

     public Dictionary GetValidationErrors() { return _context.GetValidationErrors() .SelectMany(x => x.ValidationErrors) .ToDictionary(x => x.PropertyName, x => x.ErrorMessage); } public int Save() { try { return _context.SaveChanges(); } catch (DbEntityValidationException e) { //http://blogs.infosupport.com/improving-dbentityvalidationexception/ var errors = e.EntityValidationErrors .SelectMany(x => x.ValidationErrors) .Select(x => x.ErrorMessage); string message = String.Join("; ", errors); throw new DataException(message); } } 
  3. В моем controllerе вызовите GetValidationErrors() после добавления объекта в контекст, но перед SaveChanges() :

     [HttpPost] public ActionResult Create(Organisation organisation, string returnUrl = null) { _uow.OrganisationRepository.InsertOrUpdate(organisation); foreach (var error in _uow.GetValidationErrors()) ModelState.AddModelError(error.Key, error.Value); if (!ModelState.IsValid) return View(); _uow.Save(); if (string.IsNullOrEmpty(returnUrl)) return RedirectToAction("Index"); return Redirect(returnUrl); } 

Мой базовый class репозитория реализует InsertOrUpdate следующим образом:

  protected virtual void InsertOrUpdate(T e, int id) { if (id == default(int)) { // New entity context.Set().Add(e); } else { // Existing entity context.Entry(e).State = EntityState.Modified; } } 

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

Самым надежным местом для выполнения этой логики будет сама firebase database, объявив уникальное ограничение поля в столбце имен. Когда кто-то пытается вставить или обновить существующий объект и попытаться установить его имя на существующее имя, будет выведено исключение нарушения ограничений, которое вы могли бы поймать и интерпретировать на вашем уровне доступа к данным.

Во-первых , ваш первый вариант выше допустим . Тот факт, что другой разработчик не ловушка для отказа, не является огромным недостатком. Это всегда происходит с проверкой: вы поймаете это как можно раньше и элегантно, но главное – сохранить целостность ваших данных. Упростить просто ограничение в базе данных и ловушку для этого, правда? Но да, вы хотите поймать его как можно раньше.

Если у вас есть объем и время, лучше было бы иметь более умный объект, который бы справлялся с сохранением , и, возможно, другие вещи, которые вам нужно обрабатывать последовательно. Есть много способов сделать это. Это может быть shell для вашей сущности. (См. Рисунок Decorator , хотя я предпочитаю, чтобы мой объект постоянно имел свойство Data для доступа к сущности). Для создания экземпляра может потребоваться соответствующий объект. Ваш controller предоставит сущности этому умному объекту для сохранения (опять же, возможно, создание экземпляра этого умного объекта с вашей сущностью). Этот интеллектуальный объект будет знать всю необходимую логику проверки и убедиться, что это произойдет.

В качестве примера можно создать бизнес-объект JobRole. («busJobRole», префикс шины для «business».) Он может иметь коллекцию DataExceptions . Ваш controller берет объект JobRole, отправленный назад, создает экземпляр busJobRole и вызывает метод SaveIfValid , который возвращает true, если элемент был успешно сохранен и false, если были проблемы с проверкой. Затем вы проверяете свойство busJobRoles DataExceptions для получения точных проблем и заполняете свое модельное состояние и т. Д. Возможно, например:

 // Check ModelState first for more basic errors, like email invalid format, etc., and react accordingly. var jr = new busJobRole(modelJobRole); if (jr.SaveIfValid == false) { ModelState.AddModelError(jr.DataExceptions.First.GetKey(0), jr.DataExceptions.First.Get(0)) } 

Мы последовательно следуем этой модели, и я сделал метод расширения для ModelState, чтобы принять коллекцию NameValue (возвращенную бизнес-объектами) (версия vb.net):

  _ Public Sub AddModelErrorsFromNameValueCollection( ByVal theModelState As ModelStateDictionary, ByVal collectionOfIssues As NameValueCollection, Optional ByRef prefix As String = "") If String.IsNullOrEmpty(prefix) Then prefix = "" Else prefix = prefix & "." End If For i = 0 To CollectionOfIssues.Count - 1 theModelState.AddModelError(prefix & CollectionOfIssues.GetKey(i), CollectionOfIssues.Get(i)) Next End Sub 

это позволяет быстро, элегантно добавлять исключения (определяемые бизнес-объектами) в ModelState:

 ModelState.AddModelErrorsFromNameValueCollection(NewApp.RuleExceptions, "TrainingRequest") 

Ваше беспокойство о том, что другие разработчики не могут следовать плану, который вы настроили, очень важен и хорош. Вот почему ваша схема должна быть последовательной . Например, в моем текущем проекте у меня есть две категории classов, которые действуют, как я описал. Если они очень легкие и имеют дело с кешированием и проверкой, они являются classами «диспетчера данных» (например: BureauDataManager ). Некоторые из них являются истинным объектом бизнес-домена, очень полным, который я префикс «bus» (например: busTrainingRequest). Первые все наследуются от общего базового classа, чтобы обеспечить согласованность (и, конечно, сократить код). Согласованность позволяет использовать истинную инкапсуляцию, для обнаружения кода, для правильного кода, находящегося в правом (одном) месте.

  • сообщение и получить с такой же подписью метода
  • Установка стандартного JSON-сериализатора в ASP.NET MVC
  • Атрибут .NET WebAPI Маршрутизация и наследование
  • Ненавязчивая проверка не работает с частичным представлением с динамическим добавлением
  • атрибут, зависящий от другого поля
  • Загрузка ASP.NET MVC Razor из базы данных
  • ASP.NET MVC Forms Authentication + Авторизовать атрибут + простые роли
  • Авторизовать атрибут и jquery AJAX в asp.net MVC
  • Получите доступ к адресу электронной почты в OAuth ExternalLoginCallback из Facebook v2.4 API в ASP.NET MVC 5
  • Реализация функции «Запомнить меня» в ASP.NET MVC
  • Лучшая практика программирования использования DropDownList в ASP.Net MVC
  • Давайте будем гением компьютера.