Как написать методы класса, которые возвращают коллекции экземпляров - PullRequest
4 голосов
/ 17 февраля 2009

Я изо всех сил пытаюсь определить метод класса, который заполняет и возвращает коллекцию экземпляров. Проблема, которую я не знаю, как обойти это то, что у меня есть личные атрибуты для заполнения.

Давайте воспользуемся примером класса Book. Я не хочу, чтобы код напрямую устанавливал (скажем) доступность книги. Я хочу, чтобы код использовал метод CheckOut для экземпляра Book. Итак, у нас есть что-то вроде:

public class Book
{
  private int ID;
  private bool pAvailableForCheckout;

  public string Title { get; set; }
  public bool AvailableForCheckout { get { return pAvailableForCheckout } }

  // instance methods

  public Book(int BookID)
  {
     // Load book from DB by ID
  }
  public CheckOut()
  {
     // perform everything involved with checking a book out
  }
  // .. other methods like saving a book, checking out books etc.

  // class method

  public static List<Book> FindAll()
  {
     // load Dataset of books
     // foreach record in DB, use the Book(int BookID) constructor and add to List
     // return list of books
  }
}

Итак, я могу использовать это в моем коде:

foreach(Book curBook in Book.FindAll())
  { /* do something with book */ }

Проблема с вышеприведенной реализацией состоит в том, что мне нужно использовать N + 1 попадание в базу данных, чтобы загрузить все книги вместо одного запроса. Как мне обойти это?

Я уверен, что это программирование 101, но мне нужно было спросить.

Ответы [ 4 ]

2 голосов
/ 17 февраля 2009

Вы можете создать защищенный конструктор, который напрямую заполняет частные свойства.

2 голосов
/ 17 февраля 2009

foreach должен перебирать список уже созданных объектов, им не нужно подключаться к БД.

Вам необходимо создать конструктор, который будет принимать свойства вашего объекта книги, чтобы вы могли создать экземпляр книги из существующего набора данных, а не из нового обращения к БД.

так:

Конструктор:

public book (String title, String avail) {Title=title...}

А в методе

public static void FindAll()
{
List<Books> books = new List<books>();
using (Sqlconnection conn = new sqlconnection(connstring))
using (sqlcommand cmd = new SqlCommand("select title, available from book ", conn)
{
  SqlDatareader dr = cmd.executereader()
  while (dr.read())
  {
    books.add(new Book(dr["title"], dr["avail"])
  }

}

foreach(Book curBook in Book.FindAll())
  { /* do something with book */ }

}
1 голос
/ 17 февраля 2009

Для примера, несколько экстремального по своей идеологической чистоте:

Во-первых, интерфейс для классов, который может извлекать объекты типа T из базы данных, используя их идентификатор:

interface IAdapter<T>
{
   T Retrieve(int id);
}

Теперь класс Book, который больше не предоставляет открытый конструктор, а вместо этого статический метод, который использует IAdapter<Book> для извлечения книги из базы данных:

public class Book
{
    public static IAdapter<Book> Adapter { get; set; }

    public static Book Create(int id)
    {
       return Adapter.Retrieve(id);
    }

    // constructor is internal so that the Adapter can create Book objects
    internal Book() { }

    public int ID { get; internal set; }
    public string Title { get; internal set; }
    public bool AvailableForCheckout { get; internal set; }

}

Вы должны написать класс, реализующий IAdapter<Book> самостоятельно, и назначить Book.Adapter его экземпляру, чтобы Book.Create() смог извлечь данные из базы данных.

Я говорю «идеологическая чистота», потому что этот дизайн обеспечивает довольно жесткое разделение интересов: в классе Book нет ничего, что знает, как общаться с базой данных - или даже что является базой данных ,

Например, вот одна из возможных реализаций IAdapter<Book>:

public class DataTableBookAdapter : IAdapter<Book>
{
   public DataTable Table { get; set; }
   private List<Book> Books = new List<Book>();

   Book Retrieve(int id)
   {
      Book b = Books.Where(x => x.ID = id).FirstOrDefault();
      if (b != null)
      {
         return b;
      }

      BookRow r = Table.Find(id);
      b = new Book();

      b.ID = r.Field<int>("ID");
      b.Title = r.Field<string>("Title");
      b.AvailableForCheckout = r.Field<bool>("AvailableForCheckout");

      return b;
   }
}

Какой-то другой класс отвечает за создание и заполнение DataTable, который использует этот класс. Вы могли бы написать другую реализацию, которая использует SqlConnection для непосредственного общения с базой данных.

Вы даже можете написать это:

public IAdapter<Book> TestBookAdapter : IAdapter<Book>
{
   private List<Book> Books = new List<Book>();

   public TestBookAdapter()
   {
      Books.Add(new Book { ID=1, Title="Test data", AvailableForCheckout=false };
      Books.Add(new Book { ID=2, Title="Test data", AvailableForCheckout=true };
   }

   Book Retrieve(int id)
   {
      return Books.Where(x => x.ID == id);
   }
}

Эта реализация вообще не использует базу данных - вы использовали бы ее при написании модульных тестов для класса Book.

Обратите внимание, что оба этих класса поддерживают частное свойство List<Book>. Это гарантирует, что каждый раз, когда вы звоните Book.Create() с заданным идентификатором, вы получаете один и тот же экземпляр Book. Вместо этого необходимо сделать аргумент для того, чтобы сделать эту функцию классом Book - вы бы создали статическое приватное свойство List<Book> в Book и написали логику, чтобы метод Create поддерживал его.

Вы используете тот же подход для отправки данных обратно в базу данных - добавьте методы Update, Delete и Insert к IAdapter<T> и внедрите их в свои классы адаптера, а Book вызовите эти методы в соответствующее время.

0 голосов
/ 17 февраля 2009

Почему бы вам не проверить доступность книги на стороне базы данных с помощью оператора SQL where?

...