11

我有一个网站,允许用户创建新的零件记录。我试图找出验证特定字段的唯一性的最佳方法。我想确保如果 PartNumber 已经存在于不同的 Part 上,则不会有人尝试添加 PartNumber 1234 的 Part。

Web 应用程序使用带有流利 nHibernate 的 Asp.net MVC 将我的对象映射到数据库。我在我的视图模型上对 ValidateNonEmpty、ValidateRange 等使用 Castle 验证。我是否应该使用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);
}
4

6 回答 6

1

我被问过很多次这个问题。我的朋友们担心他们是否可以从验证器代码中执行数据访问。答案很简单。如果你需要这样做,你应该这样做。通常我们需要在每个抽象级别做这样的检查。在所有检查之后,您应该准备好捕获由违反唯一约束引起的异常。

于 2009-09-17T20:02:45.557 回答
1

如果您在数据库中定义了唯一约束,那么为什么不将检查唯一值是否已经存在的责任委托给数据库呢?使用 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

于 2009-09-18T16:31:54.170 回答
1

我通常在我的控制器和存储库之间放置一个服务层。
然后服务层将处理对存储库的验证和调用。

然后,如果服务层出现验证错误,我会抛出一个自定义异常,在控制器中捕获它,并将错误注入模型状态。

于 2009-09-19T15:42:08.203 回答
0

我无法回答您的问题,但您可以查看sharparchitecture.net 网站。它包含一些针对 asp.net mvc 和 nhibernate 的最佳实践。此外,我可以建议您查看有关使用数据注释验证器进行验证的 xval 项目和教程

于 2009-09-17T19:13:27.567 回答
0

我发现对我有用的解决方案是

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();
            }
        }
    }
}
于 2009-09-17T20:10:51.113 回答
0

我会说这对你的架构很重要。使用我过去完成的 MVC 应用程序,我们将域内容从 Web 内容中抽象出来,自然我们使用依赖注入来避免硬依赖。

当您在绑定模型时验证模型时,是的,您可以轻松地在 ValidateSelf 方法中使用服务、存储库或架构中接下来的任何内容。我认为关于这种依赖的问题出现了。

如果我没记错的话,您可以创建自己的自定义绑定器,该绑定器将使用您的依赖注入框架来插入模型在创建模型时需要进行验证的任何服务,调用 MVC 的默认绑定器来填充对象,然后调用 Castle Validation 的框架进行验证。这不是一个经过深思熟虑的解决方案,但希望它能激发一些想法。

于 2009-09-23T00:53:51.657 回答