views:

1142

answers:

4

Hello, Domain Model I am working on has root aggregate and child entities. Something like the following code:

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

class OrderLine
{
}

Now I want my Order to control lines. Something like that:

class Order
{
   OrderLine[] Lines {get;}

   void AddLine(OrderLine line);
}

At this time we are using the following pattern:

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

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

NHibernate is mapped directly to the lines field.

Now questions...

  • What do you practice in such situations?
  • Does anyone use methods: public IEnumerable GetLines()
  • What are you using as return type for property? May be ReadOnlyCollection or IEnumerable;
  • May be this is not the best place to ask? Suggest please.

Update: Seems IEnumerable wins, however solution still is not perfect...

+2  A: 

The pattern I use is:

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

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

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

If you're using NET 3.5 you get all the search functionality you could want for IEnumerable using LINQ, and you hide your collection implementation.

The problem with returning OrderLine[] is that your collection can be modified externally eg:

Order.Lines[0] = new OrderLine().
gcores
Regarding "Order.Lines[0] = new OrderLine();" it cannot. Each time you access to the Lines new copy of the array issued (I do not like this though...). IEnumerable has lack of list functionality, I mean Count, access by index. Of course, LINQ covers this.Thank you!
Mike Chaliy
Here, you can still cast the Lines property to a List<OrderLine> and modify the collection in that way ...
Frederik Gheysels
Yes, but you have to do the casting. It's not so much about protection but about showing what you're allowed to do to the collection. You can return this.lines.AsReadonly() if you want that extra security.
gcores
+1  A: 

I expose collections as ReadOnlyCollection and use AddX and RemoveX methods for maintaining the collections. We just switched to 3.5 and I'm thinking about exposing IEnumerable instead. In most cases with NHibernate the child has a reference to the parent so exposing Add and Remove methods allows you to maintain that relationship:

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

    public void RemovePlayer(Player player)
    {
        player.Company = null;
        this._Players.Remove(player);
    }
Jamie Ide
Yeh, seems IEnumerable beats ReadOnlyCollection... ReadOnlyCollection make API confusing. It still has Add method that will throw exception in runtime...
Mike Chaliy
Mike -- I think you're mistaken on this. Returning a List<T>.AsReadOnly() does expose an Add method but ReadOnlyCollection<T> does not. ReadOnlyCollection<T> is in System.Collections.ObjectModel so it's often overlooked.
Jamie Ide
Funny, yes thats true.. I overlooked it. Thank you very much.
Mike Chaliy
+1  A: 

If I'm exposing a list which shouldn't be modified, then I use IEnumerable and yield. I find it cumbersome trying to use ReadOnlyCollections in conjunction with NHiberante.

With this approach, you still have the private lines field which gets mapped and populated via NHibernate; however, public access to the collection is performed through iterators. You cannot add to or remove from the underlying list with this Lines property.

For example:

public IEnumerable<OrderLine> Lines {
    get {
        foreach (OrderLine aline in lines) {
            yield return aline;
        }
    }
}
schefdev
Why not to just "return lines"? You are protecting from casting? Solution with yield means that Count() will require foreach and will load all backed ites (in case of lazy load).
Mike Chaliy
The IEnumerable combined with the yield allows you to walk the collection without being able to add/remove items.
schefdev
+7  A: 

I do it like this:

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 )
      {
          ...
      }
}

Then, offcourse, in the mapping, NHibernate is told to use the _orderLines field:

<set name="OrderLine" access="field.camelcase-underscore" ... >
...
</set>
Frederik Gheysels
With your code following code will fail:order.OrderLines != order.OrderLines;I am not sure if this is bad or not...Anyway thank you.
Mike Chaliy
That doesn't matter imho, since you shouldn't compare it that way :)
Frederik Gheysels
Good solution. This helped me out a lot. Thanks!
CalebHC