在.NET环境下进行数据库交互的方法主要是ADO.NET。从ADO.NET 1.0到现今,ADO.NET的功能已经相当完善了。但是,ADO.NET只是一个数据库操作API,如何让软件的业务逻辑层可以有效地与数据库进行交互(也就是我们常说的Data Access Layer)并不是只用ADO.NET可以解决的问题。
在.NET下可以通过以下的方法搭建Data Access Layer:
- 通过ADO.NET API直接实现;
- 通过NHibernate 或者 Entity Framework实现;
- 通过Linq to SQL实现。
第二种方法可以说是业界最推崇的现代DAL的API。他们的优点我在这里就不多说了,这里主要列举一下使用它们可能遇到的问题:
- 学习曲线。这些Framework并不是可以现学现卖的,同时,在一边学习一边使用的过程中,我们经常会发现很多Data Model被设计的过于复杂。
- 对于Legacy System进行小修改的时候,我们很难有时间和精力和Resource 为整个系统添加一个ORM Layer。
- 维护成本。一旦系统使用了ORM Layer,在日后的维护中我们需要有这方面工作经验的人。而系统的真正服役时间往往比我们的计划的要长很多很多
- 性能。由于这些框架对数据库操作进行了复杂的封装。当我们遇到数据访问性能的问题的时候,我们能做的很有限。
第三种方法其实和第二种方法很象,我把他单独列出来的原因是我认为如果软件的功能只限于对数据的只读访问,而不牵扯对数据的修改,使用Linq to SQL未尝不是一个很好的选择。
上面说了这么多,回到这篇文章的主题,我们讨论如何基于ADO.NET API建立一个简单的Data Access Layer方便业务层对数据进行修改和显示。在ADO.NET中,我们最常打交道的对象就是DataTable。那么我们对DataTable进行一个对业务层和显示层更友好的封装:DataTableLinkedModel 和 DataTableLinkedCollection:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
namespace DynamicDataTableWrapper
{
public class DataTableLinkedModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private DataRow _attachedDataRow;
public DataTableLinkedModel()
{
}
public DataTableLinkedModel(DataRow dataRow)
{
this._attachedDataRow = dataRow;
}
public DataRow AttachedDataRow
{
get { return _attachedDataRow; }
set
{
_attachedDataRow = value;
NotifyAttachedDataRowChanged();
}
}
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
protected virtual void NotifyAttachedDataRowChanged()
{
}
public override int GetHashCode()
{
return AttachedDataRow.GetHashCode();
}
public override bool Equals(object obj)
{
var dataTableLinkedModel = obj as DataTableLinkedModel;
return dataTableLinkedModel != null && AttachedDataRow.Equals((dataTableLinkedModel).AttachedDataRow);
}
public DataRowState RowState
{
get { return AttachedDataRow.RowState; }
}
}
}
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Linq;
using System.Text;
namespace DynamicDataTableWrapper
{
public class DataTableLinkedEnumerator<T> : IEnumerator<T> where T : DataTableLinkedModel, new()
{
public DataTable AttachedDataTable { get; set; }
private int cursorIndex = -1;
public DataTableLinkedEnumerator()
{
}
public DataTableLinkedEnumerator(DataTable dataTable)
{
this.AttachedDataTable = dataTable;
}
public void Dispose()
{
}
public bool MoveNext()
{
if (AttachedDataTable == null)
return false;
if (cursorIndex >= AttachedDataTable.Rows.Count - 1)
{
return false;
}
cursorIndex++;
return true;
}
public void Reset()
{
this.cursorIndex = -1;
}
public T Current
{
get { return new T {AttachedDataRow = AttachedDataTable.Rows[cursorIndex]}; }
}
object IEnumerator.Current
{
get { return Current; }
}
}
public class DataTableLinkedCollection<T> : IList<T>, INotifyCollectionChanged where T : DataTableLinkedModel, new()
{
public DataTable AttachedDataTable { get; set; }
public DataTableLinkedCollection()
{
}
public DataTableLinkedCollection(DataTable dataTable) : this()
{
AttachedDataTable = dataTable;
}
public IEnumerator<T> GetEnumerator()
{
return new DataTableLinkedEnumerator<T> {AttachedDataTable = AttachedDataTable};
}
public void Add(T item)
{
AttachedDataTable.Rows.Add(item.AttachedDataRow);
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, AttachedDataTable.Rows.Count - 1));
}
public void Clear()
{
AttachedDataTable.Rows.Clear();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Reset));
}
public bool Contains(T item)
{
return AttachedDataTable.Rows.Contains(item.AttachedDataRow);
}
public void CopyTo(T[] array, int arrayIndex)
{
for (int index = arrayIndex; index < array.Length + arrayIndex; index++)
{
array[index - arrayIndex] = new T {AttachedDataRow = AttachedDataTable.Rows[arrayIndex]};
}
}
public bool Remove(T item)
{
var index = IndexOf(item);
item.AttachedDataRow.Delete();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, item, FromAbsoluteIndexToVisualIndex(index)));
return true;
}
public int Count
{
get { return AttachedDataTable.Rows.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public int IndexOf(T item)
{
return AttachedDataTable.Rows.IndexOf(item.AttachedDataRow);
}
public void Insert(int index, T item)
{
AttachedDataTable.Rows.InsertAt(item.AttachedDataRow, index);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, item, index));
}
public void RemoveAt(int index)
{
var absoluteIndex = FromVisualIndexToAbsoluteIndex(index);
var item = this[absoluteIndex];
item.AttachedDataRow.Delete();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, item, index));
}
public T this[int index]
{
get { return new T { AttachedDataRow = AttachedDataTable.Rows[index] }; }
set
{
foreach (DataColumn column in AttachedDataTable.Columns)
{
AttachedDataTable.Rows[index][column] = value.AttachedDataRow[column];
}
}
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
CollectionChanged(this, e);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public T NewModelInstance()
{
return new T {AttachedDataRow = AttachedDataTable.NewRow()};
}
public int FromAbsoluteIndexToVisualIndex(int absoluteIndex)
{
var deletedCount = 0;
for (int index = 0; index < absoluteIndex; index++)
{
if (AttachedDataTable.Rows[index].RowState == DataRowState.Deleted) deletedCount++;
}
return absoluteIndex - deletedCount;
}
public int FromVisualIndexToAbsoluteIndex(int visualIndex)
{
var absoluteIndex = -1;
for (int calculatedVisualIndex = 0; calculatedVisualIndex <= visualIndex; )
{
absoluteIndex++;
if (AttachedDataTable.Rows[absoluteIndex].RowState != DataRowState.Deleted) calculatedVisualIndex++;
}
return absoluteIndex;
}
}
}
通过上面这两个class的封装,我们可以很简单的在一个WPF控件中显示一个DataTable。例如下面一个对于NorthWind Example数据库的Customer表的显示:
在代码例子中并没有添加对数据修改的存储,而只对所有的修改进行了显示。有了所有修改的显示,如果将这些修改存储到数据库我相信难不倒各位看官。例子的源代码可以在这里找到