首页 > 解决方案 > 在 NHibernate 中的组件内建模列表的最佳方法

问题描述

想象一下,我有一个房地产网站,可让您向不同的房地产经纪人询问有关给定财产的信息。不同的查询方法可能具有与之关联的不同计费计算,并且并非所有代理都会启用每种计费模型。

public class EmailEnquiryBillingModel : ValueObject
{
    public string EmailAddress { get; set; }
    public decimal CostPerEnquiry { get; set; }
}

public enum DayOfWeek
{
    Monday,
    Tuseday,
    // etc.
}

public class OpeningHours : ValueObject
{
    public DateTime OpeningTime { get; set; }
    public DateTime ClosingTime { get; set;} 
}

public class PhoneEnquiryBillingModel : ValueObject
{
    public PhoneEnquiryBillingModel()
    {
        OpeningHours = new Dictionary<DayOfWeek, OpeningHours>();
    }
    public int PhoneNumber { get; set; }
    public IDictionary<DayOfWeek, OpeningHours> OpeningHours { get; set; }
}

public class EstateAgent : Entity
{
    public string Name { get; set; }
    public EmailEnquiryBillingModel EmailEnquiryBillingModel { get; set; }
    public PhoneEnquiryBillingModel PhoneEnquiryBillingModel { get; set; }
}

NHibernate 具有组件(值对象)的语义,如果组件中的每个属性都为 null,则该组件也将为 null。

因此,通过适当的映射,您可以编写if(estateAgent.EmailEnquiryBillingModel != null)而不需要检查电子邮件查询计费模型的每个单独属性,或者该模型是否有效:我们要么有模型,要么没有。这是一种检查是否启用了特定计费模型的简单、优雅的方法。

当您在一个组件中有一个集合时,问题就出现了,例如电话查询计费模型和各种开放时间。thePhoneEnquiryBillingModel和 the都不OpeningHours是实体。这些是合法的价值对象:我们不关心房地产经纪人是在周一上午 9 点还是从周一上午 9 点开始营业,只关心在周一上午 9 点开始营业。

因此,这感觉像是在 C# 中表示此域模型的语义正确方式。

然而,PhoneEnquiryBillingModel包含一个集合 (of ProviderOpenHours),并且集合在 NHibernate 中不能为空,只能为空,这意味着ProviderOpenHours即使房地产经纪人没有有意义地启用该查询模型,它也将始终为非空。(有关更多信息,请参阅:https ://ayende.com/blog/4685/those-are-the-rules-even-when-you-dont-like-them )。

这意味着您不能像 一样进行简单的检查if(estateAgent.PhoneEnquiryBillingModel != null),因为该对象始终不为空。

因此,对于某些计费模型,您可以进行空值检查以查看它们是否已启用,但对于其他计费模型,您必须找到另一种检查方式,具体取决于这些计费模型是否包含一组。

实际上,您需要了解计费模型的内部结构才能知道是否可以进行这种比较,这感觉就像您正在打破封装并根据 ORM 的规则更改域模型。

有没有更好的建模方法?PhoneEnquiryBillingModel或者,如果 NHibernate没有电话号码或任何营业时间,是否可以让 NHibernate 在 as null 中序列化?

标签: nhibernatedomain-driven-design

解决方案


因此,通过适当的映射,您可以编写 if(estateAgent.EmailEnquiryBillingModel != null)

这本身并不是最好的封装。

相反,你去:

if (estateAgent.DoesAcceptEmailEnquiries())

if (estateAgent.DoesAcceptPhoneEnquiries())

与询问 EstageAgent 聚合上的属性以对 EstateAgent 的能力进行假设相比,这将提供更好的封装。如果您决定更改 EstateAgent 在内部存储此信息的方式的实现,该怎么办?您需要更改所有客户端。

EstateAgent 对其基础价值对象执行单个属性检查并没有什么特别糟糕的地方。

但是,您可以更进一步,在 PhoneEnquiryBillingModel 上实现一个检查器方法,甚至可能是静态的,以避免在 EstateAgent 中进行空检查。

电话查询计费模式

public class PhoneEnquiryBillingModel : ValueObject
{
    public PhoneEnquiryBillingModel()
    {
        OpeningHours = new Dictionary<DayOfWeek, OpeningHours>();
    }
    public int PhoneNumber { get; set; }
    public IDictionary<DayOfWeek, OpeningHours> OpeningHours { get; set; }

    public static bool DoesAcceptEnquiries(PhoneEnquiryBillingModel phone)
    {
        if (phone == null) return false;

        if (phone.OpeningHours.Count == 0) return false;
 
        return true;
    }
}

地产代理

public class EstateAgent : Entity
{
    public string Name { get; set; }
    public EmailEnquiryBillingModel _emailEnquiryBillingModel { get; set; }
    public PhoneEnquiryBillingModel _phoneEnquiryBillingModel { get; set; }

    public bool DoesAcceptPhoneEnquiries()
    {
        return PhoneEnquiryBillingModel.DoesAcceptEnquiries(
            _phoneEnquiryBillingModel);
    }
}

推荐阅读