Как лучше всего использовать списки только для чтения в 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(); оно не может. При каждом доступе к Lines выдается новая копия массива (хотя мне это не нравится...). В 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 to 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
Это не имеет значения, имхо, так как вы не должны сравнивать это таким образом :) - person Frederik Gheysels; 15.03.2009
comment
Хорошее решение. Это мне очень помогло. Спасибо! - person CalebHC; 11.06.2009
comment
Почему вы решили вернуть new вместо кэширования readonly collection в ленивом приватном поле? - 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
Почему бы просто не вернуть строки? Вы защищаете от литья? Решение с выходом означает, что Count() потребует foreach и загрузит все поддерживаемые объекты (в случае ленивой загрузки). - person Mike Chaliy; 15.03.2009
comment
IEnumerable в сочетании с yield позволяет вам перемещаться по коллекции, не имея возможности добавлять/удалять элементы. - 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