Pergunta

Eu estou lutando para definir um método de classe que preenche e retorna uma coleção de instâncias. A questão que eu não sei como dar a volta é que eu tenho atributos privados para preencher.

Vamos usar o exemplo de uma classe Book. Eu não quero o código para definir diretamente (digamos) a disponibilidade de um livro. Eu quero o código de ter que usar um método de checkout em uma instância Book. Portanto, temos algo como:

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
  }
}

Então, eu posso não usar isso em meu código:

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

O problema com a implementação acima é que eu tenho que usar N + 1 hits ao banco de dados para carregar todos os livros em vez de apenas 1 consulta. Como posso resolver isso?

Tenho certeza que isso está programando 101, mas eu precisava perguntar.

Foi útil?

Solução

Você pode criar um construtor protegido que preenche as propriedades privadas diretamente.

Outras dicas

O foreach deve ser iteração sobre uma lista de objetos já instanciado, eles não precisam se conectar ao DB.

Você precisa criar um construtor que aceita as propriedades do objeto livro de modo que você pode instanciar um livro a partir de um conjunto existente de dados em vez de um novo hit para o DB.

forma:

Construtor:

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

E no método

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 */ }

}

Para um exemplo extremo pouco em sua pureza ideológica:

Em primeiro lugar, uma interface para as classes que pode recuperar objetos do tipo T do banco de dados dado o seu ID:

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

Agora, a classe Book, que já não expõe um construtor público, mas sim um método estático que usa um IAdapter<Book> para recuperar o livro a partir do banco de dados:

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; }

}

Você tem que escrever o IAdapter<Book> implementação de classe mesmo, e Book.Adapter atribuir a uma instância do mesmo modo que Book.Create() será capaz de puxar as coisas a partir do banco de dados.

Eu digo "pureza ideológica" porque este projeto impõe uma separação muito rígida de preocupações: não há nada na classe Book que sabe como falar com o banco de dados - ou mesmo que há é um banco de dados.

Por exemplo, aqui está uma possível implementação de 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;
   }
}

Algum outro classe é responsável por criar e preencher o DataTable que este usa a classe. Você poderia escrever uma implementação diferente que usa um SqlConnection para falar com o banco de dados diretamente.

Você pode até escrever isto:

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);
   }
}

Esta implementação não usa um banco de dados em tudo -. Você pode usar isso quando escrever testes de unidade para a classe Book

Note que ambas as classes de manter uma propriedade List<Book> privado. Isso garante que cada vez que você chamar Book.Create() com um dado ID, você recebe de volta a mesma instância Book. Há um argumento a ser feito para tornar isto uma característica da classe Book vez -. Você iria fazer uma propriedade List<Book> privada estática em Book e escrever a lógica para tornar o método Create mantê-lo

Você pode usar a mesma abordagem para empurrar dados de volta para o banco de dados -. Update add, Delete e métodos Insert para IAdapter<T> e implementá-las em suas classes de adaptador, e têm apelo Book esses métodos no momento apropriado

Por que você não verificar a disponibilidade do livro ao lado do banco de dados usando um SQL onde declaração?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top