Archive for .NET

WebシステムではDataAdapterを使うな!

過激な題名から入りましたが、VB.NETのお話です。

さてVB6などから.NETになってDatabaseへのアクセスするのに以下のようなクラスを使うようになりました。

・DataReader
・DataAdapter

終わり・・・

かなりハショッてます(汗)

要するにConnectionとCommand系クラスはあまり変わらないのですが、
Recordsetだけは大きく2つのクラスに分解されています。

Selectデータの取得方法が前者に上げたDataReaderとDataAdapterになったのですが、
それぞれ使用する場面が分かれます。

1.DataReaderは前方向カーソルのデータ取得になっていて取得はグルグルLoop処理が必要になります。

「メリット」
View情報がDBサーバー上にあり、クライアントサイドにはReadしない限り保持されないので動作が軽い。

「デメリット」
データは上位順にしか取得出来ないのでApp側でコントロール表示などは行ってあげる必要がある。
Paging処理など動的にPageクラスにセットしてあげる必要があるので簡単に出来ない。

2.DataAdapterはDataSetクラスにヒットしたデータを一発でいれる

「メリット」
DataSetを使用して動作するコントロールが多数あるので開発が楽。
必要なデータを簡単にDataSetを通じてClose後でも使用できる。
Paging処理もクラス任せで楽。
また、直感的に修正して更新も出来るなど使い勝手が良い部分も

「デメリット」
ヒットしたDataを全部DataSetに入れるのでクライアント側のメモリ領域を使用してしまい処理が遅い。
(要するにグルグルLoopをALLしてるってこと)

以上になります。

一見すると「DataAdapterの方がいいところいっぱいじゃん!?」と思いでしょう???

ここが落とし穴なんだがや!
楽だと思ってDataAdapterでFillばっかり使っているとやられてしまうで(誰だお前は(苦笑))

もちろん使うタイミングの問題なのですが、Webでもクラサバでも主要なデータを任意のキーで
検索する機能があるシステムが多くなってきました。

この時にDataAdapterを使用して大量データの表示、Paging処理など行うとヒットしたデータを
DataSetに入れてしまう関係でとんでもなく遅い処理になってしまいます!

DataGridなどで大量データを扱う場合に表示が遅いのはこのためドス:oops:

★具体例だと、100万件のデータを任意で検索し10件ごとPagingし3Page目を表示する時、
仮に100万件がヒットしたとして、ヒットデータをDataSet(メモリ)に入れる、
そこから30件目見つけて10件分を表示することになっているので、なんで?って思えるぐらい遅いです。

DataReader使ってLoopで30件目から10件分、取ってきたほうがよっぽど早い!
管理人のテストでは最後の10件を表示する時もこちらのほうが数倍早かった:roll:

余談だけど、Paging処理はDBによりSQLに任せる方法もあります。
例えばMySQLやPostgresなどはLimit関数、SQLServerやOracleはRowIDを使用するなど
しかし、DBによってはPaging処理が整っていなかったりするのでApp側の違いを覚えておくのも
損はないでしょう♪

作成段階では少量のデータで問題なく動いても、普通はテスト段階、あるいは本番稼動後、
じわじわとボディーブローのように遅くなってきます。

昨今はWebはもちろん企業内のシステムも膨大な検索が出来るシステムになってきました。
作ってからの手直しはデグレが怖くて直せなくなりますので作る前からの情報をお勧めします。

最後に適材適所が必要で、DataSetも使う場面によっては楽チンである:???:

管理人も2003の時にテスト段階でこの問題に気づきPaging処理などを直したことがあるので復習兼ねて覚書:razz:

おまけ「DataSetの新機能」非同期って言葉がミソ♪

Disposeにてアンマネージドコードとマネージドコードの違い

前回の続きドス:evil:

前回「Dispose」の実装について記述しましたが

  'クラスのリソースを解放するオーバーロードされた Dispose メソッド。
  'このメソッドは基本クラスの Dispose メソッドおよび Finalize メソッドから呼び出す必要があります。
  Protected Overridable Sub Dispose(ByVal disposing As Boolean)
    If Not Me.disposed Then
      If disposing Then
        ' Insert code to free unmanaged resources.
      End If
    ' Insert code to free shared resources.
    End If
    Me.disposed = True
  End Sub

引数にBuooleanの引数があり、内部If文でTrueとFalseで開放の種類を選別できる形になっていますが、
これの意味を解説しますね。

.NETではCLRという中間言語を使用して管理しています。

しかしWin32APIなど旧来のロジックも記述することが出来るので、開放時のタイミングがNew元が開放するのと
GC任せで開放するのではタイミングにずれが生じ、後者の場合CLR以外の記述がある時に問題が発生する可能性があります。

New元が呼び出す「Public Overloads Sub Dispose() Implements IDisposable.Dispose」内では

全ての開放が出来る「Dispose(True)」で呼び出され

いつ呼び出されるか分からない「Protected Overrides Sub Finalize()」内では

CLRが管理するものだけ開放が出来る「Dispose(False)」で呼び出されているのです。

なので特殊対応しているものでない限りIf外に記述することになるでしょうね:razz:

特殊対応は責任を持ってその都度、後処理が必要になるということですな:cool:

DisposeとFinalize

ガベージコレクション(以下GC)の話をしましたが、Classの良い所は前処理と後処理が出来るところだと思いますが、
.NETではFinalizeの実装はされていますが、これはGCのせいで、いつ呼び出されるか分かりません。

そのためプログラマは開放処理を狙って行えない!

メモリーの解放はシカトでもファイルのIOやDBのIOなど後処理をしたいClassが沢山ある:evil:
「Finalize」は外部から意図的にCall出来ないので後処理としてはあまり使えません。

VB6の頃はオブジェクトの開放といえば「Set obj = Nothing」でしたが、.NETでは「obj.Dispose」となりました。
しかし、この「Dispose」くんはControlやFormなどには標準でインターフェイスとして付いてますが、
プログラマが独自にClassを作成するときには明示してあげないと使えません・・・・・:sad:

そこで「Implements IDisposable」を実装して以下のようにしてあげます。

Public Class Class1
  Implements IDisposable
 
  'オブジェクトが破棄されたかどうかを追跡するためのフィールド。
  Protected disposed As Boolean = False
 
  Public Sub New()
  End Sub
 
  Protected Overrides Sub Finalize()
    MyBase.Finalize()
    Dispose(False)
  End Sub
 
  Public Overloads Sub Dispose() Implements IDisposable.Dispose
    Dispose(True)
    'GCに対して自分を解放してくれ!と命令!
     GC.SuppressFinalize(Me)
  End Sub
 
  'クラスのリソースを解放するオーバーロードされた Dispose メソッド。
  'このメソッドは基本クラスの Dispose メソッドおよび Finalize メソッドから呼び出す必要があります。
  Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)
    If Not Me.disposed Then
      If disposing Then
        ' Insert code to free unmanaged resources.
      End If
    ' Insert code to free shared resources.
    End If
    Me.disposed = True
  End Sub
 
End Class

ここで出てくる「GC.SuppressFinalize(Me)」はGCからメモリ開放してと御願いしているので、
メモリー開放まで行わない方はコメントでも良いでしょう。

まぁ普通は破棄のタイミングも考えて開発しますからあって、良い処理ですな:wink:

そして「Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)」の中に
開放ロジックを書いてあげて、呼び出し元には必ず「obj.Dispose」を呼んで貰うようにすればOK

VB6の場合「Set obj = Nothing」
 
.NETの場合「obj.Dispose」

「Dispose」が無いものは書かなくても良い=後処理無いのでメモリーの解放はGCに任せる!でもOKだが・・・

やっぱり綺麗に書くことが何事にもプラスに働くはず☆