Используя MVC и свободно Nhibernate, как я могу проверять уникальные поля в моей модели ViewModel, прежде чем привязывать их к объекту Domain и сохранить их?

У меня есть веб-сайт, на котором я разрешаю пользователям создавать новые записи деталей. Я пытаюсь найти лучший способ проверить конкретные поля для уникальности. Я хочу убедиться, что кто-то не пытается добавить Part с PartNumber 1234, если этот PartNumber уже существует в другой части.

Веб-приложение использует Asp.net MVC с плавным nHibernate для сопоставления моих объектов с базой данных. Я использую проверку валидатов на моделях просмотра для таких вещей, как ValidateNonEmpty, ValidateRange и т.д. Должен ли я использовать метод ValidateSelf для запроса репозитория на посмотрите, существует ли этот номер детали? Что-то не похоже на использование моего репозитория в ViewModel.

Было бы лучше, если бы я поставил эту логику на действие контроллера? Это не кажется правильным, потому что я ожидаю, что мой ViewModel уже будет проверен в точке (во время ModelBind).

Или, может быть, ни один из вышеперечисленных. Спасибо за любую помощь на этом.

UPDATE Хорошо, не уверен, что это поможет, но вот что мое действие Save выглядит как типичное действие Create в моем проекте:

public ActionResult Create(PartViewModel viewModel)
{
 //I think I'd like to know if its Valid by this point, not on _repository.Save
 if(ModelState.IsValid)
 {
    try
    {
        var part = _partCreateViewModelMap.MapToEntity(viewModel);

        _repository.Save(part);
        return Redirect("~/Part/Details/" + part.Id);
    }
    catch (Exception e)
    {
        // skip on down...
    }
 }

 // return view to edit 
 return View(viewModel);
}

Ответ 1

Меня задавали этот вопрос много раз. Мои друзья беспокоились о том, могут ли они выполнять доступ к данным из кода валидатора. Ответ прост. Если вам нужно это сделать, вы должны это сделать. Обычно нам нужно делать такие проверки на каждом уровне абстракции. И после всех проверок вы должны быть готовы поймать исключение, вызванное уникальным нарушением ограничения.

Ответ 2

Если вы определяете уникальное ограничение в базе данных, то почему бы не делегировать ответственность за проверку того, существует ли в базе данных уникальное значение? Используя NHibernate, вы можете использовать интерфейс NHibernate.Exceptions.ISQLExceptionConverter для захвата и преобразования известных ошибок, связанных с нарушениями ограничений. Вы также можете использовать конструкторы NHibernate.Exceptions.IViolatedConstraintNameExtracter (см. NHibernate.Exceptions.TemplatedViolatedConstraintNameExtracter), чтобы получить подробные сведения об исключении своей базы данных и преобразовать его в удобное для пользователя сообщение, переупаковать в качестве исключения для проверки вашего выбора и уловить его в соответствующем контроллере,

Пример быстрого, очень конкретного быстрого и грязного конвертера исключений из одного из моих проектов:


Imports NHibernate
Imports NHibernate.Exceptions
Imports System.Data.SqlClient
Imports System.Data.Common

Namespace NHibernate

    Public Class ConstraintViolationExceptionConverter
        Implements ISQLExceptionConverter

        Public Function Convert(ByVal adoExceptionContextInfo As Global.NHibernate.Exceptions.AdoExceptionContextInfo) As System.Exception Implements Global.NHibernate.Exceptions.ISQLExceptionConverter.Convert

            Dim dbEx As DbException = ADOExceptionHelper.ExtractDbException(adoExceptionContextInfo.SqlException)

            If TypeOf dbEx Is SqlException Then
                Dim sqlError As SqlException = DirectCast(dbEx, SqlException)

                Select Case sqlError.Number
                    Case 547
                        Return New ConstraintViolationException(adoExceptionContextInfo.Message, adoExceptionContextInfo.SqlException)

                End Select

            End If

            Return SQLStateConverter.HandledNonSpecificException(adoExceptionContextInfo.SqlException, adoExceptionContextInfo.Message, adoExceptionContextInfo.Sql)

        End Function


    End Class

End Namespace

Конфигурируется с помощью элемента свойства web.config/nhibernate-configuration/session-factory:


<property name="sql_exception_converter">csl.NHibernate.ConstraintViolationExceptionConverter, csl</property>

Изменить: Возможно, следует упомянуть, что интерфейс конвертера изменился в последних версиях NHibernate, интерфейс из этого примера - из NHibernate.dll v2.1.0.4000

Ответ 3

Обычно я устанавливаю сервисный уровень между моими контроллерами и репозиториями.
Затем сервисный уровень обрабатывает проверку и вызовы в репозиторий.

Затем, если есть ошибка проверки на уровне службы, я бросаю пользовательское исключение, улавливаю его в контроллере и ввожу ошибки в состояние модели.

Ответ 4

У меня нет ответа на ваш вопрос, но вы можете проверить сайт sharparchitecture.net. Он содержит несколько лучших практиков для asp.net mvc и nhibernate. Также я могу порекомендовать вам проверить xval-проект и руководства по проверке с помощью проверки достоверности данных

Ответ 5

Я нашел решение, которое работает для меня, это

1.) Спросите, действительна ли сущность для выполнения вашей проверки.
2.) После того, как это будет завершено, вы должны иметь что-то на своем объекте, чтобы показать его действительным или нет (в моем случае я использую CSLA как понятие "нарушенных правил" ).
3.) Если у вас есть что-то подобное, вы можете проверить, что объект действителен до того, как NHibernate попытается сохранить его, как показано ниже.

Единственная проблема с этим подходом заключается в том, что вам нужно реализовать интерфейс для каждого объекта, требующего проверки. Если вы можете жить с этим, это остановит NHibernate от сохранения изменений объекта, который недействителен в соответствии с вашими правилами.

using System;
using NHibernate;
using NHibernate.Event;
using Validation.Entities.Interfaces;
using Persistence.SessionBuilder;

namespace Persistence.Validation
{
    public class ValidationEventListener : IPreInsertEventListener, IPreUpdateEventListener
    {

        public bool OnPreInsert(NHibernate.Event.PreInsertEvent @event)
        {
            var entityToInsert = @event.Entity as IBusinessBase;

            if (entityToInsert != null)
            {
                if (entityToInsert.BrokenRules != null)
                {
                    RollbackTransactionBecauseTheEntityHasBrokenRules();
                }
            }

            return false;
        }

        public bool OnPreUpdate(NHibernate.Event.PreUpdateEvent @event)
        {
            var entityToUpdate = @event.Entity as IBusinessBase;

            if (entityToUpdate != null)
            {
                if (entityToUpdate.BrokenRules != null)
                {
                    RollbackTransactionBecauseTheEntityHasBrokenRules();
                }
            }

            return false;
        }

        private void RollbackTransactionBecauseTheEntityHasBrokenRules()
        {
            try
            {
                ISession session = SessionBuilderFactory.GetBuilder().CurrentSession;

                if (session != null)
                {
                    session.Transaction.Rollback();
                }
            }
            catch (Exception ex)
            {
                //this will force a rollback if we don't have a session bound to the current context 
                throw new NotImplementedException();
            }
        }
    }
}

Ответ 6

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

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

Если я правильно помню, вы можете создать собственное пользовательское связующее, которое будет использовать вашу инфраструктуру внедрения зависимостей для подключения любых служб, которые требуется вашей модели для проверки при ее создании, вызовите связующее по умолчанию MVC для заполнения объекта, а затем вызовите Завершение проверки для проверки. Это не вполне продуманное решение, но, надеюсь, оно вызывает некоторые идеи.