4

我正在努力定义一个填充并返回实例集合的类方法。我不知道如何解决的问题是我有私有属性要填充。

让我们使用 Book 类的示例。我不希望代码直接设置(比如说)一本书的可用性。我希望代码必须在 Book 实例上使用 CheckOut 方法。所以我们有类似的东西:

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 次命中来加载所有书籍,而不仅仅是 1 个查询。我该如何解决这个问题?

我确定这是编程 101,但我需要问一下。

4

4 回答 4

2

您可以创建一个受保护的构造函数,它直接填充私有属性。

于 2009-02-17T15:28:57.803 回答
2

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

}
于 2009-02-17T15:32:29.510 回答
1

举个意识形态纯洁性有些极端的例子:

首先,类的接口可以从给定 ID 的数据库中检索类型为 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该类使用的。您可以编写一个不同的实现,使用 aSqlConnection直接与数据库对话。

你甚至可以这样写:

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()使用给定 ID 调用时,都会返回相同的Book实例。有一个论点是为了Book让它成为类的一个特性——你会在其中创建一个静态私有List<Book>属性Book并编写逻辑来使Create方法维护它。

您使用相同的方法将数据推回数据库 - add UpdateDeleteInsert方法到IAdapter<T>并在您的适配器类中实现它们,并Book在适当的时间调用这些方法。

于 2009-02-17T20:54:31.717 回答
0

为什么不使用 SQL where 语句在数据库端检查该书的可用性?

于 2009-02-17T15:27:58.603 回答