データ管理
フォーム上の値の更新をユーザーに許可する場合は、その値を検証する必要が生じます。すべての値が有効なときは、変更を保存するオプションを提供するようにします。オブジェクトバインディングは、検証およびデータ管理プロセスを支援するいくつかの機能を備えています。
オブジェクトバインディングを使って検証を実装するには、最初に、ErrorProviderコントロールをフォームに追加し、そのバインディングソースを、本稿で作成したCustomerBindingSourceに設定します。これにより、ErrorProviderはビジネスオブジェクトからのエラーを認識できるようになります。
ErrorProviderを設定したら、ビジネスオブジェクトのプロパティに直接、フィールドレベルの検証ルールを実装します。プロパティが無効な場合は、例外をスローします。ユーザーが入力した値が無効の場合は、ErrorProviderアイコンに例外のテキストが自動的に表示されます。
簡単な例として、Customerビジネスオブジェクトに、ユーザーに姓の入力を求めるビジネスルールがあるものとします。これは、次のように実装できます。
Public Property LastName() As String Get Return _LastName End Get Set(ByVal value As String) If String.IsNullOrEmpty(value) Then Throw New Exception( _ "The Last Name cannot be empty") End If _LastName = value End Set End Property
public string LastName { get { return _LastName;} set { if (String.IsNullOrEmpty(value)) { throw new Exception( "The Last Name cannot be empty"); } _LastName = value; } }
適切な検証コードをビジネスオブジェクトプロパティに直接追加することができ、必要であれば例外をスローできます。もっと柔軟で再利用性に優れた検証処理が必要な場合は、独立したValidationクラスを作成し、このクラスで必須フィールドのチェック、文字列長のチェック、数値チェック、日付範囲のチェックなど、さまざまな種類の検証を行うことができます。Validationクラスは、例外をスローする代わりに、検証エラーのコレクションを保持できます。その場合、ビジネスオブジェクトはIDataErrorInfoインターフェイスを実装して、このコレクションをErrorProviderに公開する必要があります。この方法の詳細については割愛しますが、興味を抱く読者が多いようであれば、このトピックに関する記事を書くつもりです。
ユーザーの入力値を検証したら、ビジネスオブジェクトは、値の変更を追跡する必要があります。これによって、何らかの変更があった場合のみ、終了時に変更を保存するかどうかをユーザーに確認するなどの機能を実装できるようになります。また、オブジェクトの更新、作成、または削除を追跡し、元のデータソースにさかのぼって適切な処理を実行することができます。
このデータ管理では、次の4つの基本ステップが必要です。
- データ状態を表す定数を定義します。
- ビジネスオブジェクトにプロパティを追加して、状態を保持します。
INotifyPropertyChangedインターフェイス(System.ComponentModel.INotifyPropertyChanged)を実装します。PropertyChangedイベントを生成します。
この4ステップを実装するにあたり、最も直接的なのは、必要なコードの大部分をすべてのビジネスオブジェクトクラスで繰り返すという方法です。もっと洗練された実装にするには、4ステップのコードを実装した基本のビジネスオブジェクトクラスを作成し、この基本クラスをすべてのビジネスオブジェクトクラスで継承するという方法もあります。
ここでは、例を簡単にするために、上記の4ステップをCustomerビジネスオブジェクトクラスに直接実装することにします。
ステップ1:データ状態を表す定数を定義します。
Public Enum EntityStateEnum Unchanged Added Deleted Modified End Enum
public enum EntityStateEnum { Unchanged, Added, Deleted, Modified, }
ステップ2:状態を保持するプロパティを定義します。
Private _EntityState As EntityStateEnum Public Property EntityState() As EntityStateEnum Get Return _EntityState End Get Private Set(ByVal value As EntityStateEnum) _EntityState = value End Set End Property
private EntityStateEnum _EntityState; public EntityStateEnum EntityState { get { return _EntityState; } private set { _EntityState = value; } }
setアクセサの実装はPrivateであることに注意してください。これによって、他のコードによる状態の変更が回避されます。
ステップ3:INotifyPropertyChangedインターフェイスを実装します。このインターフェイスを実装し、プロパティ値が変更されたときにPropertyChangedイベントを起動することによって、オブジェクトバインディングは変更を自動的に検出します。プロパティに加えられた変更は、ユーザーインターフェイスの中で確実に更新されます。
Public Class Customer Implements INotifyPropertyChanged Public Event PropertyChanged(ByVal sender As _ Object, ByVal e As PropertyChangedEventArgs) _ Implements INotifyPropertyChanged.PropertyChanged
class Customer : INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged;
ステップ4:PropertyChangedイベントを起動します。
Private Sub DataStateChanged(ByVal dataState As _ EntityStateEnum, ByVal propertyName As String) ' Raise the event If Not String.IsNullOrEmpty(propertyName) Then RaiseEvent PropertyChanged(Me, _ New PropertyChangedEventArgs(propertyName)) End If ' If the state is deleted, mark it as deleted If dataState = EntityStateEnum.Deleted Then Me.EntityState = dataState End If If Me.EntityState = EntityStateEnum.Unchanged Then Me.EntityState = dataState End If End Sub
private void DataStateChanged(EntityStateEnum dataState, string propertyName) { // Raise the event if (PropertyChanged != null && propertyName != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } // If the state is deleted, mark it as deleted if (dataState == EntityStateEnum.Deleted) { this.EntityState = dataState; } if (this.EntityState == EntityStateEnum.Unchanged) { this.EntityState = dataState; } }
このDataStateChangedメソッドは、PropertyChangedイベントを起動し、適切なデータ状態を設定します。DataStateChangedメソッドは、次に示すように、ビジネスオブジェクト内の各プロパティのプロパティプロシージャで呼び出されます。
Public Property LastName() As String Get Return _LastName End Get Set (ByVal value as String) If String.IsNullOrEmpty(value) Then Throw New Exception( _ "The Last Name cannot be empty") End If If value <> _LastName Then Me.DataStateChanged( _ EntityStateEnum.Modified, _ "LastName") End If _LastName = value End Set End Property
public string LastName { get { return _LastName;} set { if (String.IsNullOrEmpty(value)) { throw new Exception( "The Last Name cannot be empty"); } if (value != _LastName) { this.DataStateChanged( EntityStateEnum.Modified, "LastName"); } _LastName = value; } }
基本のデータ管理コードを実装した後で、必要に応じて、追加の機能を実装することができます。例えば、次のような単純な読み取り専用のIsDirtyプロパティを追加できます。
Public ReadOnly Property IsDirty() As Boolean Get Return Me.EntityState <> _ EntityStateEnum.Unchanged End Get End Property
public Boolean IsDirty { get{ return this.EntityState!=EntityStateEnum.Unchanged;} }
このIsDirtyプロパティをユーザーインターフェイスのClosingイベントの中で呼び出せば、ユーザーに変更の保存確認を求めるべきどうかを判別できます。
前述の通り、この一連のデータ管理コード(最後のIsDirtyプロパティも含む)を1つの基本クラスに追加し、この基本クラスをすべてのビジネスオブジェクトに継承させるという実装方法も考えられます。そうすれば、すべてのビジネスオブジェクトにかかわる一連の基本機能を1つのコードセットで提供することができます。
