NHibernate中只读列表的最佳实践是什么?

10

我正在处理的领域模型具有根聚合和子实体。类似以下代码:

class Order
{
   IList<OrderLine> Lines {get;set;}
}

class OrderLine
{
}

现在我想让我的Order控制行。就像这样:

class Order
{
   OrderLine[] Lines {get;}

   void AddLine(OrderLine line);
}

目前我们正在使用以下模式:

class Order
{
   private IList<OrderLine> lines = new List<OrderLine>();
   OrderLine[] Lines {get {return this.lines;}}

   void AddLine(OrderLine line)
   {
      this.orders.Add(line);
   {
}

NHibernate直接映射到lines字段。

现在的问题是...

  • 在这种情况下你会采取什么做法?
  • 有人使用方法:public IEnumerable GetLines()吗?
  • 对于属性,您使用什么作为返回类型?可能是ReadOnlyCollection或IEnumerable;
  • 也许这不是询问的最佳位置?请提供建议。

更新:似乎IEnumerable获胜了,但解决方案仍然不完美...

5个回答

14

我是这样做的:

public class Order
{
      private ISet<OrderLine> _orderLines = new HashedSet<OrderLine>();

      public ReadOnlyCollection<OrderLine> OrderLines
      {
          get { return new List<OrderLine>(_orderLines).AsReadOnly(); }
      }

      public void AddOrderLine( OrderLine ol )
      {
          ...
      }
}

然后,在映射中,当然需要告诉 NHibernate 使用 _orderLines 字段:

<set name="OrderLine" access="field.camelcase-underscore" ... >
...
</set>

使用您的代码,以下代码将失败: order.OrderLines!= order.OrderLines; 我不确定这是好还是不好... 无论如何,谢谢。 - Mike Chaliy
这在我看来并不重要,因为你不应该那样进行比较 :) - Frederik Gheysels
好的解决方案。这帮了我很多忙。谢谢! - CalebHC
你为什么选择返回新的对象而不是将ReadOnlyCollection缓存到一个懒加载的私有字段中呢? - smartcaveman
如果您要返回新的对象,我认为您最好按照其他人建议的方式执行 return new ReadOnlyCollection<OrderLine>(_orderLines)。这是因为 .AsReadOnly() 返回一个仍具有 Add()(只有在调用时抛出异常)的对象,而 System.Collections.ObjectModel 中的 ReadOnlyCollection<T> 则没有,因此可以确保没有人会错误地使用您的只读集合。 - Dav
不确定我是否做错了什么,但是为了在ISet中使用ReadOnlyCollection<t>,我不得不像这样实例化它: return new ReadOnlyCollection<OrderLine>(new List<OrderLine>(_orderLines))。否则,将从ISet转换时出现错误,无法将其强制转换为IList。 - Josh Anderson

9
我使用的模式是:
class Order
{
   private List<OrderLine> lines = new List<OrderLine>();

   IEnumerable<OrderLine> Lines { get { return this.lines; } }

   void AddLine(OrderLine line)
   {
       this.orders.Add(line);
   }
}

如果您使用NET 3.5,您将使用LINQ获得所有与IEnumerable相关的搜索功能,并隐藏您的集合实现。
返回OrderLine[]的问题在于您的集合可以在外部进行修改,例如:
Order.Lines[0] = new OrderLine().

2
是的,但你必须进行强制类型转换。这不仅仅是为了保护,而是为了展示你可以对集合做什么。如果你想要额外的安全性,你可以返回this.lines.AsReadonly()。 - gcores
如果我这样做,就会出现错误“找不到属性XXX的setter”。我需要配置映射以使用后端字段而不是属性吗? - Rezler
1
@Rezler 是的,你必须这样做。 - gcores
@gcores 另外一个问题 - 上述解决方案是否可以与Linq to NH查询一起正常工作?过去我尝试使用 ReadOnlyCollection 实现只读列表,但它与 Linq to NH 不兼容。谢谢。 - Rezler
理想情况下,订单聚合应该负责创建其订单行。按照现有的写法,你正在违反聚合的边界,因为暗示着外部对象创建了订单的弱实体。最佳实践是将OrderLine实体的ctor参数传递给AddLine方法,并在添加到底层列表之前在该方法中实例化它。(我尽可能地限制子实体在包含聚合之外的接口中的暴露) - smartcaveman
显示剩余3条评论

3

我将集合公开为ReadOnlyCollection,并使用AddX和RemoveX方法来维护集合。我们刚刚转换到3.5版本,我正在考虑改为公开IEnumerable。大多数情况下,使用NHibernate时,子元素都有对父元素的引用,因此公开Add和Remove方法可以维护这种关系:

    public void AddPlayer(Player player)
    {
        player.Company = this;
        this._Players.Add(player);
    }

    public void RemovePlayer(Player player)
    {
        player.Company = null;
        this._Players.Remove(player);
    }

是的,似乎IEnumerable比ReadOnlyCollection更好... ReadOnlyCollection使API变得混乱。它仍然有Add方法,在运行时会抛出异常... - Mike Chaliy
1
Mike,我觉得你对这个有误解。返回一个List<T>.AsReadOnly()确实会暴露一个Add方法,但是ReadOnlyCollection<T>却没有。ReadOnlyCollection<T>位于System.Collections.ObjectModel命名空间中,所以经常被忽视。 - Jamie Ide
有趣,是的,那是真的...我疏忽了。非常感谢你。 - Mike Chaliy

1
如果我要公开一个不应该被修改的列表,那么我会使用IEnumerable和yield。我发现尝试将ReadOnlyCollections与NHiberante结合使用很麻烦。
通过这种方法,您仍然拥有私有的lines字段,它通过NHibernate进行映射和填充;但是,对集合的公共访问是通过迭代器执行的。您不能使用Lines属性添加或删除底层列表中的元素。
例如:
public IEnumerable<OrderLine> Lines {
    get {
        foreach (OrderLine aline in lines) {
            yield return aline;
        }
    }
}

为什么不只是“返回行”?你是在防止强制转换吗?使用yield的解决方案意味着Count()将需要foreach并加载所有支持的项(在惰性加载的情况下)。 - Mike Chaliy
IEnumerable与yield结合使用,可以让您在无法添加/删除项目的情况下遍历集合。 - Steve Scheffler
IEnumerable本身也会防止您添加/删除项目 - 因此,public IEnumerable<OrderLine> Lines { get { return lines; }}就足够了。 - Dav

0

我花了几天时间寻找NHibernate中只读列表的最佳方法。这个讨论对我很有帮助,使我能够形成适合我们项目的方法。

这是一种我开始使用的方法:

  1. 使用后备字段来存储集合
  2. 使用IEnumerable<T>来公开集合,以强制客户端使用AddLine()和RemoveLine()方法。
  3. 除了IEnumerable之外,还使用ReadOnlyCollection类型。

代码:

public class Order
{
    private readonly IList<OrderLine> lines = new List<OrderLine>();

    public virtual IEnumerable<OrderLine> Lines
    {
        get
        {
            return new ReadOnlyCollection<OrderLine>(lines);
        }
    }

    public void AddLine(OrderLine line)
    {
        if (!lines.Contains(line))
        {
            this.lines.Add(line);
            line.Order = this;
        }
    }

    public void RemoveLine(OrderLine line)
    {
        if (lines.Contains(line))
        {
            this.lines.Remove(line);
            line.Order = null;
        }
    }
}

public class OrderLine
{
    public Order Order { get; set; }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接