一个.NET下的简单的Data Access Layer

在.NET环境下进行数据库交互的方法主要是ADO.NET。从ADO.NET 1.0到现今,ADO.NET的功能已经相当完善了。但是,ADO.NET只是一个数据库操作API,如何让软件的业务逻辑层可以有效地与数据库进行交互(也就是我们常说的Data Access Layer)并不是只用ADO.NET可以解决的问题。

在.NET下可以通过以下的方法搭建Data Access Layer:

  1. 通过ADO.NET API直接实现;
  2. 通过NHibernate 或者 Entity Framework实现;
  3. 通过Linq to SQL实现。
用第一种方法,很多代码需要程序员手工完成。从编程效率上来讲是最低的。建立一个基本的数据访问,程序员需要先在数据库完成访问数据的SQL代码(通常会是一个存储过程),然后需要建立一个Business Object(通常以POCO的形式出现)。然后,还需要手工建立Column-to-Property的映射。如果要对访问的数据进行修改,还需要开发update数据的存储过程,等等。如果在软件中需要显示和修改的不是一个Object,而是一个Collection的话,那么当软件对数据进行修改后,程序员需要自己对哪条数据改变了进行判断。但是,第一种方法也不是一无是处。相对第二种和第三种方法,第一种方法虽然在编程效率上远远落后,但是,程序员对整个代码有完全的控制权,对于进行小修改和性能调优都是非常方便的。同时,很多几年前开发的系统都是用这种方法来设计DAL层的,在对这些系统的维护过程中,一定会和这种DAL打交道。

第二种方法可以说是业界最推崇的现代DAL的API。他们的优点我在这里就不多说了,这里主要列举一下使用它们可能遇到的问题:

  1. 学习曲线。这些Framework并不是可以现学现卖的,同时,在一边学习一边使用的过程中,我们经常会发现很多Data Model被设计的过于复杂。
  2. 对于Legacy System进行小修改的时候,我们很难有时间和精力和Resource 为整个系统添加一个ORM Layer。
  3. 维护成本。一旦系统使用了ORM Layer,在日后的维护中我们需要有这方面工作经验的人。而系统的真正服役时间往往比我们的计划的要长很多很多
  4. 性能。由于这些框架对数据库操作进行了复杂的封装。当我们遇到数据访问性能的问题的时候,我们能做的很有限。

第三种方法其实和第二种方法很象,我把他单独列出来的原因是我认为如果软件的功能只限于对数据的只读访问,而不牵扯对数据的修改,使用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表的显示:


在代码例子中并没有添加对数据修改的存储,而只对所有的修改进行了显示。有了所有修改的显示,如果将这些修改存储到数据库我相信难不倒各位看官。例子的源代码可以在这里找到

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值