30 Mart 2007 Cuma

Kendi Kendini Doğrulayan Typed DataRow

Verinin kendi kendini test etme yaklaşımı Validation Application Block ile iyice güçlendi. Artık Entity’ler daha zeki ve sakladıkları verinin doğruluğundan Entity nesnesi sorumlu. Validation Application Block veriyi Entity üzerinde saklayan programlar için mükemmel bir yaklaşım sunuyor. Fakat Typed DataRow üzerinde bir Application Block yok. Peki benim gibi üşengeç bir .Net yazılımcısı iseniz Entity nesneleri ile ilişkisel verileri saklamak zor geliyorsa, tüm verileri Typed DataSet içinde taşımak ve işlemek size daha kolay geliyorsa ne yapmalısınız?
Seçenek kalmadı kullandığınız Typed DataSet’leri kendi kendini test edebilir şekle getirmeliyiz. Ne gerekli bize:
  1. Atomik test işlemlerini yapan Attribute sınıflar.
  2. Bu test işlemlerini işletecek Controller sınıfı
Tabi tüm bu sınıfların veriden bağımsız olması yani Generic olması gerekmektedir. Burada ki kısıtlama Validation Application Block her bir Entity Field’a attribute vererek doğrulama yapılmakta fakat Typed DataRow üzerinden ki Field’lara Attribute kalıcı olarak eklenemez. Onun için bizim CheckAtiibute’lerimiz direk Typed DataRow nesnelerine eklenmelidir.
Doğrulama iki şekilde olur:
  1. Check: Veri kendi başına doğru mu sorusudur. Mesela email alanı gecerli mi? Para alanına negatif değer girildi mi?
  2. Validation: Veri iş kurallarına uygun mu sorusudur. Yani veri diğer veriler ile birlikte doğru mu?

Tüm işi gerçekleştiren Controller sınıfıdır. Check hatalarını girildiği anda (OnFly) yakalamak için DataSource almaktadır. DataSource üzerinden veri değiştiği zaman BaseCheck sınıfından türetilen Check kuralları çalıştırılmakta ve karşılaşılan hatalar ColumnError olarak atanmaktadır.

Önce takip edilecek Row’a Attributeler atanmalı: 
[cs]
[NotNullCheck("Column1",UserFriendlyName="Birinci alan")]
[MinimumCheck("Column2",12, UserFriendlyMessage = "İkinci alandaki veri 12 den küçük olamaz")] 
[UniqueCheck("Column3", UserFriendlyMessage = "İkinci alandaki veri tüm tabloda unique olmalı")]
partial class DataTable1Row 
{   

}
Veri değişimi takibi:
[cs]
#region FollowTable 
protected virtual void FollowDataSource(DataTable table) 
{ 
 table.ColumnChanging += 
  new DataColumnChangeEventHandler(table_ColumnChanging); 
} 

protected virtual void table_ColumnChanging(object sender, DataColumnChangeEventArgs e)
{ 
 // veri değişti check kurallarını işlet 
  if( e.Row.RowState != DataRowState.Deleted) 
  e.Row.SetColumnError(e.Column, 
   CheckRow(e.Column, e.Row, e.ProposedValue)); 
} 
#endregion
Değişen veriyi test et:
[cs]
#region check 
///  
/// kolon verisi değişti kontrolü 
///  
/// değişen kolon 
/// kullanılan row 
/// yeni değer 
/// hata iceriyorsa hata mesajı. hata yoksa boş string 
protected virtual string CheckRow(DataColumn dataColumn, DataRow dataRow, object columnNewValue) 
{ 
 dataRow.EndEdit(); 
 string result = string.Empty; 
 BaseCheck[] attrs = (BaseCheck[])dataRow.GetType()
  .GetCustomAttributes(typeof(BaseCheck), true); 
 foreach (BaseCheck attr in attrs) 
 {  
  if (attr.ColumnName == dataColumn.ColumnName) 
  { 
   result += attr.IsValid(dataRow, columnNewValue); 
  } 
 } 
 return result; 
} 
#endregion 
Check işleminin çalışma zamanı görüntüsü:

IsValid çağrısı ile önce Check kontrolleri çalıştırılır eğer tüm checkler doğru ise kontrol edilen Row HasSelfValidation attribute’ne sahip mi kontrolü yapılır. Eğer Row HasSelfValidation attribute’ne sahip ise SelfValidation attribute’ne sahip fonksiyonlar aranır ve bu fonksiyonlar çağırılır.
[cs]
#region validate 
///  
/// Veri tabanına kayıt etmeden önce kayıtın doğruluğunu kontrol et 
/// hata bulunur ise hatayı throw et 
///  
/// kontrol edilecek kayıt 
public virtual bool IsValid(DataRow row) 
{ 
 row.EndEdit(); 
 _errorMessage = string.Empty; 
 if (row.RowState != DataRowState.Deleted 
  && row.RowState != DataRowState.Detached) 
 { 
  foreach (DataColumn col in row.Table.Columns) 
   _errorMessage += CheckRow(col, row, row[col]);                       
   //tüm childe row'ları valid mi 
  foreach (DataRelation relation in row.Table.ChildRelations) 
   foreach (DataRow childeRow in row.GetChildRows(relation)) 
    foreach (DataColumn col in childeRow.Table.Columns) 
     _errorMessage += 
      CheckRow(col, childeRow, childeRow[col]); 
 } 
 // validation 
 if (_errorMessage.Length == 0 && row.RowState != DataRowState.Deleted 
  && row.RowState != DataRowState.Detached) 
 { 
  HasSelfValidation[] attrs = (HasSelfValidation[])row.GetType()
   .GetCustomAttributes(typeof(HasSelfValidation), false); 
  if (attrs != null && attrs.Length > 0) 
  { 
   foreach (MethodInfo method in row.GetType().GetMethods()) 
   { 
    SelfValidation[] validation = (SelfValidation[])method
     .GetCustomAttributes(typeof(SelfValidation), false); 
    if (validation != null && validation.Length > 0) 
    { 
     object result = method.Invoke(row, new object[] { }); 
     if (!string.IsNullOrEmpty((string)result)) 
     { 
      this._errorMessage += (string)result; 
     } 
    } 
   } 
  } 
 } 
 return string.IsNullOrEmpty(_errorMessage); 
} 
#endregion 
Validation kontrollerinin çalışma zamanı görüntüsü: