Коя е най-добрата практика за списъци само за четене в NHibernate

Моделът на домейн, върху който работя, има коренен агрегат и дъщерни обекти. Нещо като следния код:

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

class OrderLine
{
}

Сега искам моята поръчка да контролира линиите. Нещо такова:

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 се картографира директно към полето за линии.

Сега въпроси...

  • Какво практикувате в такива ситуации?
  • Някой използва ли методи: public IEnumerable GetLines()
  • Какво използвате като тип връщане за собственост? Може да бъде ReadOnlyCollection или IEnumerable;
  • Може би това не е най-доброто място да попитате? Предложете моля.

Актуализация: Изглежда, че IEnumerable печели, но решението все още не е перфектно...


person Mike Chaliy    schedule 14.03.2009    source източник


Отговори (5)


Моделът, който използвам е:

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, получавате цялата функционалност за търсене, която бихте искали за IEnumerable с помощта на LINQ, и скривате внедряването на вашата колекция.

Проблемът с връщането на OrderLine[] е, че вашата колекция може да бъде модифицирана външно, например:

Order.Lines[0] = new OrderLine().
person gcores    schedule 14.03.2009
comment
Относно Order.Lines[0] = new OrderLine(); не може. Всеки път, когато имате достъп до линиите, се издава ново копие на масива (не ми харесва това обаче...). IEnumerable няма функционалност за списък, имам предвид Count, достъп по индекс. Разбира се, LINQ покрива това. Благодаря ти! - person Mike Chaliy; 15.03.2009
comment
Тук все още можете да прехвърлите свойството Lines към List‹OrderLine› и да промените колекцията по този начин ... - person Frederik Gheysels; 15.03.2009
comment
Да, но трябва да направите кастинга. Не става въпрос толкова за защита, а за показване на това, което ви е позволено да правите с колекцията. Можете да върнете this.lines.AsReadonly(), ако искате тази допълнителна сигурност. - person gcores; 15.03.2009
comment
Съгласен съм с gcores, ключовото нещо е да предадете това, което трябва да направите на глас, чрез публичния интерфейс. Ако искате да хакнете чрез кастинг, можете да го направите, но дори и да го копирате, някой може да използва отражение, за да стигне до основния списък. - person Caleb Vear; 09.02.2011
comment
@gcores Ако направя това, тогава получавам грешка Не може да се намери сетер за свойството XXX. Трябва ли да конфигурирам картографирането да използва задното поле вместо свойството? - person Rezler; 31.07.2011
comment
@Rezler Да, трябва да го направите. - person gcores; 31.07.2011
comment
@gcores Страхотно. Благодаря за отговора - person Rezler; 01.08.2011
comment
@gcores Само още нещо - горното решение ще работи ли добре с Linq към NH заявки? В миналото се опитвах да постигна списъци само за четене чрез използване на ReadOnlyCollection, но не се получи добре с Linq to NH. Благодаря. - person Rezler; 01.08.2011
comment
В идеалния случай агрегатът Order трябва да отговаря за създаването на своя OrderLine. Както е написано, вие нарушавате границата на съвкупността, като намеквате, че външен обект създава слабите обекти на Ордена. Най-добрите практики включват предаване на аргументите на ctor на обекта OrderLine към метода AddLine и инстанцирането му в рамките на този метод преди добавянето му към основния списък. (Опитвам се да огранича излагането си на дъщерни обекти извън съдържащите агрегати до интерфейси, когато е възможно) - person smartcaveman; 07.04.2012

Аз го правя така:

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>
person Frederik Gheysels    schedule 14.03.2009
comment
С вашия код следният код ще се провали: order.OrderLines != order.OrderLines; Не съм сигурен дали това е лошо или не... Все пак благодаря. - person Mike Chaliy; 15.03.2009
comment
Това няма значение imho, тъй като не трябва да го сравнявате по този начин :) - person Frederik Gheysels; 15.03.2009
comment
Добро решение. Това ми помогна много. Благодаря! - person CalebHC; 11.06.2009
comment
Защо бихте избрали да върнете ново, вместо да кеширате колекцията само за четене в мързеливо частно поле? - person smartcaveman; 07.04.2012
comment
Ако връщате нови, мисля, че е по-добре да направите return new ReadOnlyCollection<OrderLine>(_orderLines), както предложиха други. Това е така, защото .AsReadOnly() връща обект, който все още има Add() (просто хвърля изключения при извикване), докато ReadOnlyCollection<T> (от System.Collections.ObjectModel) не го прави и по този начин гарантира, че никой дори не мисли да използва неправилно вашата колекция само за четене. - person Dav; 11.09.2012
comment
Не съм сигурен дали правя нещо нередно, но за да използвам ReadOnlyCollection<t> с ISet, трябваше да го инстанцирам така: return new ReadOnlyCollection<OrderLine>(new List<OrderLine>(_orderLines)). В противен случай получавам грешка при кастинг от ISet към IList. - person Josh Anderson; 04.01.2013

Излагам колекциите като 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);
    }
person Jamie Ide    schedule 14.03.2009
comment
Да, изглежда IEnumerable бие ReadOnlyCollection... ReadOnlyCollection прави API объркващ. Все още има метод Add, който ще хвърли изключение по време на изпълнение... - person Mike Chaliy; 15.03.2009
comment
Майк -- мисля, че грешиш в това. Връщането на List‹T›.AsReadOnly() разкрива метод Add, но ReadOnlyCollection‹T› не. ReadOnlyCollection‹T› е в System.Collections.ObjectModel, така че често се пренебрегва. - person Jamie Ide; 16.03.2009
comment
Странно, да, вярно е.. Пропуснах го. Благодаря ти много. - person Mike Chaliy; 21.05.2010

Ако излагам списък, който не трябва да се променя, тогава използвам IEnumerable и yield. Намирам за тромаво да се опитвам да използвам ReadOnlyCollections във връзка с NHiberante.

С този подход все още имате полето за лични линии, което се картографира и попълва чрез NHibernate; публичният достъп до колекцията обаче се осъществява чрез итератори. Не можете да добавяте или премахвате от основния списък с това свойство Lines.

Например:

public IEnumerable<OrderLine> Lines {
    get {
        foreach (OrderLine aline in lines) {
            yield return aline;
        }
    }
}
person Steve Scheffler    schedule 14.03.2009
comment
Защо просто да не върнете редовете? Защитаваш ли се от кастинг? Решение с yield означава, че Count() ще изисква foreach и ще зареди всички подкрепени елементи (в случай на отложено зареждане). - person Mike Chaliy; 15.03.2009
comment
IEnumerable, комбиниран с добива, ви позволява да се разхождате из колекцията, без да можете да добавяте/премахвате елементи. - person Steve Scheffler; 15.03.2009
comment
Е, IEnumerable сам по себе си също би ви попречил да добавяте/премахвате елементи - така че public IEnumerable<OrderLine> Lines { get { return lines; }} трябва да е достатъчно. - person Dav; 11.09.2012

Прекарах няколко дни в търсене на най-добрия подход за списъци само за четене в NHibernate. Тази дискусия ми помогна много да оформя този, който отговаря на нашия проект.

Има един подход, който започнах да използвам:

  1. Резервните полета се използват за съхраняване на колекции
  2. IEnumerable‹ T> се използва за излагане на колекции, за да принуди клиентите да използват методите AddLine() и RemoveLine().
  3. Типът ReadOnlyCollection се използва в допълнение към IEnumerable.

Код:

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; }
}
person Ilya Palkin    schedule 25.08.2013