【问题标题】:Where to put validation for changing an object's children?在哪里进行更改对象子项的验证?
【发布时间】:2011-05-10 06:47:45
【问题描述】:

我将 ASP.NET MVC 2 与 nhibernate 一起使用。我有这个 Sales 类。销售有许多付款。一旦销售状态变为已确认,不应修改销售付款。我需要有关如何执行此操作的建议。

在尝试进行此验证时,我遇到了几件事:

  • 对于添加和删除付款:
    • 我可以在 Sales 中创建 AddPaymentDeletePayment 方法,但每个人都必须记住使用这些方法,而不是直接添加和删除付款收款
    • 我不想隐藏付款集合,因为 nhibernate 需要它,这个集合也用于软件的其他部分
  • 对于修改现有付款:
    • 我认为我不应该将验证放在 Payments 设置器中,因为 nhibernate 需要访问设置器。
  • 我应该抛出异常吗?有一个 discussion 关于抛出异常以防止对象进入无效状态的缺点。在我的情况下,修改后的对象状态可能仍然有效,但我想阻止修改。在这种情况下抛出异常是否合理?有哪些替代方案?

我应该在控制器操作中强制执行此操作吗?

【问题讨论】:

    标签: asp.net-mvc nhibernate validation


    【解决方案1】:

    您可以通过将其设为受保护甚至私有来隐藏收藏。 NHibernate 仍然会找到它。或者,当销售状态为受限值时,您可以让集合 getter 返回一个不可变集合。

    private ISet<Payment> _payments;
    
    public virtual ISet<Payment> Payments
    {
        get
        {
            if (Status == SalesStatus.Confirmed)
                return new ImmutableSet<Payment>(_payments);
    
            return _payments;
        }
        private set { _payments = value; }
    }
    

    将验证规则放入设置器中也可以。您可以告诉 NHibernate 直接访问支持字段(如果您当前正在使用自动属性,则可能需要添加支持字段)。

    <property name="_name" access="field"/>
    <property name="Description" access="nosetter.camelcase-underscore"/>
    

    编辑附加问题...

    我不会在保存之前抛出异常。像在属性设置器中一样,提前抛出它们可能会让 UI 开发人员感到恼火,他们必须一次只返回一个错误给用户。我也主要做 MVC 应用程序,所以我最近一直在使用 System.ComponentModel.DataAnnotations 验证属性。虽然在某些方面受到限制,但它们与 MVC 一起很好地用于在浏览器和控制器中进行模型检查。但是,如果您使用这些,我建议创建一个自定义拦截器以在保存之前检查它们。如果出现任何问题,我将在拦截器中抛出异常。

    【讨论】:

    • 不可变集合是一个非常好的主意!也感谢您的 nhib 信息。
    【解决方案2】:

    我将在您的解决方案中介绍两个新项目(层):

    • 模型层
    • 服务(或业务)层

    您的模型层应包含接口和支持类型:

    public interface ISales
    {
        IEnumerable<IPayment> GetPayments();
    }
    
    public enum PaymentStatus
    {
        Unknown,
        Confirmed
    }
    
    public interface IPayment
    {
        // your public properties
        PaymentStatus Status { get; set; }
    }
    

    您应该将您的 NHibernate 类移动到服务层并使用internal 隐藏它们。您的 NHibernate 类实现模型接口:

    internal class Sales : ISales
    {
        public IEnumerable<IPayment> GetPayments()
        {
            // your implementation
        }
    }
    
    internal class Payment : IPayment
    {
        // your public properties
        public PaymentStatus Status { get; set; }
    }
    
    public class SalesService
    {
        public ISales FindByKey(int key)
        {
            // your implementation
        }
    
        public void AddPayment(ISales sales, IPayment payment)
        {
            // throw exception if validation fails
        }
    
        public void DeletePayment(ISales sales, IPayment payment)
        {
            // throw exception if validation fails
        }
    }
    
    public class PaymentService
    {
        public IPayment FindByKey(int key)
        {
            // your implementation
        }
    }
    

    由于 SalesPayment 类是隐藏的,因此您的控制器必须使用服务类:

    public class SalesController : Controller
    {
        private readonly SalesService salesService;
        private readonly PaymentService paymentService;
    
        public SalesController()
        {
            salesService = new SalesService();
            paymentService = new PaymentService();
        }
    
        public ActionResult AddPayment(int salesId, int paymentId)
        {
            var sales = salesService.FindByKey(salesId);
            var payment = paymentService.FindByKey(paymentId);
    
            salesService.AddPayment(sales, payment);
    
            return RedirectToAction("Index");
        }
    }
    

    您还应该考虑使用IoC container,例如autofacNinject

    【讨论】:

    • 所以 Sales 和 Payments 只包含 getter 和 setter?
    • 是的,通过只处理接口,您可以创建一个隐藏实现细节的干净 API。对接口进行编程而不是直接使用类通常是一种很好的做法。
    【解决方案3】:

    有几种选择:

    • 一个是在数据库中有一个触发器,那么你就 100% 确定
    • 您对 AddPayment 和 DeletePayment 的建议,结合一些代码审查可能是最好的方法。

    【讨论】:

    • 我将使用触发器作为最后的手段,因为我想尽可能地保留应用程序中的逻辑。此外,触发器仅在保存期间通过抛出异常操作。如果可以的话,我更愿意知道我不应该在保存之前修改它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-21
    • 1970-01-01
    • 2012-12-09
    • 2016-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多