NHibernate 映射 - 带有被重用列的用户类型?

Posted

技术标签:

【中文标题】NHibernate 映射 - 带有被重用列的用户类型?【英文标题】:NHibernate Mapping - UserType with a column being re-used? 【发布时间】:2011-03-15 18:17:44 【问题描述】:

我基本上有一个Money 值类型,它由AmountCurrency 组成。我需要将多个Money 值映射到一个表中,该表具有多个金额字段,但只有一种货币。换句话说,我有桌子:

Currency     Amount1    Amount2
===============================
USD          20.00      45.00

这需要映射到具有 2 个 Money 值的类(逻辑上永远不会有不同的货币):

class Record
 
    public Money Value1  get; set; 
    public Money Value2  get; set; 


struct Money 

    public decimal Amount  get; set; 
    public string Currency  get;set; 
 

(示例有点简化)

无法更改表架构。如果需要,我很乐意实现IUserType,但我不知道如何访问Currency 列的两个值。

如何使用 NHibernate 进行映射?

【问题讨论】:

【参考方案1】:

这是不可能的。

如果您创建 Money UserType,则该类型的目的是将两个原语组合成一个新的单一数据类型。该数据类型现在是单个原子单元。因为 UserType 被认为是原子类型,所以对于映射的每个 Money 值,这两个列都必须存在。 NHibernate 执行的规则实际上在语义上是正确的。您有两个 Money 值。因此,您必须有两种货币 - 它们不能共享一种,因为单一货币类型具有货币和金额,并且现在是一个原子数据单元,不能拆分或共享。

您有时希望将货币视为变量,因此需要它自己的列,而在其他时候,您希望将货币视为固定的,以便多个 UserType 可以共享一个列。即使这是有效的,它不是基于金钱的定义方式,NHibernate 怎么会知道你什么时候想做什么?系统不能在任何给定时间只知道您想要什么。您现在还需要使用 Money 类型保存其他数据,以指定任何给定值的使用方式。

底线,您的列不能映射为 UserType。如果您无法更改架构,那么您可以执行此操作的唯一方法是将所有三列映射为普通原语,然后在应用程序代码中构造(和解构)Money 类型。

结果是什么?我真的很惊讶,即使是像 Fowler 这样的人也建议这种方法作为某种“最佳实践”而没有真正考虑实际细节。事实上,大多数数据是一组货币由一些通常隐含或外部因素(例如原产国或企业经营所在的国家/地区等)决定的。

在您实际上甚至可能需要货币的情况下,您经常有很多其他的包袱,这些包袱来自多种货币,例如当前汇率等,以至于将货币作为货币类型的一部分甚至没有那么有用。这是一种方便,但对于使用起来非常不方便且无法以其他方式制作的数据。大多数情况下,货币是固定的,通常甚至可以推断出来。因此,在典型情况下,Money 类型要么无法接近您真正需要的 - 转换,要么只是不必要的信息。除了少数例外,Money 类型只是应用程序中的一个小果子。

在您花费大量时间尝试实现某些东西之前,除了人们开始称其为“最佳实践”之外,可能很少或没有其他原因,请问自己您是否甚至需要将它用于任何事情?

【讨论】:

+1,虽然我不同意你关于 Money 值类型的有用性。【参考方案2】:

这是不可能的,因为当您考虑读取和写入实体时它没有意义。

考虑 Value1 为 USD 20 而 Value2 为 GBP 40 的情况。你会如何坚持下去?

【讨论】:

我明白这一点,但我的逻辑表明,同一记录中的货币必须相同。如果我可以更改架构以包含多个货币列,我可能会。 @driis:那么您需要更改您的域。此外,NHibernate 不可能知道该约束。【参考方案3】:

像这样改变你的课程怎么样?

class Record
 
    public MoneyCollection Money  get; set; 


class MoneyCollection

    public MoneyCollection(string currency, params decimal[] amount1)  /*...*/ 
    public decimal[] Amount  get; private set; 
    public string Currency  get; private set; 
    public Money[] Money
    
      get
      
        return Amount.Select(x => new Money(Currency, x)).ToArray();
      
    
 

class Money 

    public Money(decimal amount, string currency )  /* ... */ 
    public decimal Amount  get; private set; 
    public string Currency  get; private set; 

现在您可以为MoneyCollection 编写用户类型。

注意:您需要确保MoneyCollection 有一个常数,或者至少有一个最大数量的值,因为您需要将它映射到一个常数列。在类本身中检查这一点。

【讨论】:

【参考方案4】:

我只是一个新手,现在才学习和使用 NHibernate 几个月,但是是否可以创建一个复杂的用户类型 (IEnhancedUserType) 来写入多个列,而一个列将是多余的?

很难解释,但是如果您将实体中的属性映射到负责读/写操作的货币列,并且您在复杂的用户类型映射中多次引用同一列并关闭插入和更新,因此使它成为只读列,这行不通?我一直在考虑尝试做一个这样的用户类型,看看它是否有效(因为我还没有尝试过,我没有任何示例代码 atm)

【讨论】:

【参考方案5】:

我知道这已经晚了几年 - 但我有一个解决方案。

private decimal _subTotal;
private decimal _shipping;
private CurrencyIsoCode _currency;

public virtual Money SubTotal

    get  return new Money(_subTotal, _currency); 
    set
    
        _subTotal = value.Amount;
        _currency = value.CurrencyCode;
    


public virtual Money Shipping

    get  return new Money(_shipping, _currency); 
    set
    
        _shipping = value.Amount;
        _currency = value.CurrencyCode;
    

映射:

Map(Reveal.Member<Basket>("_subTotal")).Column("SubTotal");
Map(Reveal.Member<Basket>("_shipping")).Column("Shipping");
Map(Reveal.Member<Basket>("_currency")).Column("Currency");

【讨论】:

以上是关于NHibernate 映射 - 带有被重用列的用户类型?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 NHibernate 中映射没有标识列的视图?

Nhibernate/Hibernate、查找表和对象设计

Nhibernate/Hibernate、查找表和对象设计

使用流利的 nhibernate 映射枚举

nhibernate 映射枚举对(键 - 值)

如何使用 NHibernate 在 CompositeId 映射中设置列