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

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

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

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

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

ОБНОВЛЕНИЕ Хорошо, не уверен, поможет ли это, но вот как выглядит мое действие Сохранить для типичного действия "Создать" в моем проекте:

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);
}

6 ответов

Решение

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

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

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

Если вы определяете уникальное ограничение в базе данных, то почему бы не делегировать ответственность за проверку того, существует ли уникальное значение в базе данных? Используя 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

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

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

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

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

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

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();
            }
        }
    }
}
Другие вопросы по тегам