Kỹ thuật dùng PureObject

 

PureObject là gì? Tại sao phải dùng PureObject?

PureObject là những object cho ta tự tạo ra để thay thế Cho DataRow trong DataTable của ADO. Đi kèm theo PureObject là 1 cái Collection thay thế cho cái DataTable. Có bạn nghĩ mấy cái DataRow, DataTable hay như vậy sao không xài mà tự nhiên tạo ra 1 cái mới cho cho nó cực khổ vậy! Trước đây mình cũng nghĩ vậy, nhưng càng về sau mình càng thấy sức mạnh của nó và những project của mình đều xài PureObject.(chỉ trừ những chương trình Demo nho nhỏ thì mình dùng DataSet,DataRow, DataTable cho nhanh he he he...)

- Thường thì 1 DataTable đại diện cho 1 Table trong Database. Nó là 1 list dữ liệu dạng phẳng-tức là nếu bạn muốn có 1 dữ liệu kiểu master/detail thì không cách nào khác là bạn dùng DataSet với DataRelation để gắn kết các table lại. Kiểu lập trình này thì nó không có trực quan như lập trình hướng đối tượng. Trong DataSet thì có 1lô 1 lốc cá thuộc tính, đối tượng mà mình không bao giờ dùng đến => kết quả là nó làm chậm trang web của bạn đôi chút. Nếu mình muốn 1 field chỉ là kết quả tính toán từ khác field khác thì mình phải xử lý bằng tay mỗi khi dữ liệu các field này thay đổi. hoặc có những rule tính toán phức tạp cho 1 field nào đó thì mình cũng xử lý rất nhiêu khê. Hoặc đôi khi bạn không thao tác dữ liệu trên database mà là trên file hay trên bộ hớ lúc chạy chương trình thì dùng DataSet có vẻ không hay lắm. Dùng PureObject có thể giúp bạn giảm bớt những gánh nặng này. Đó là lý do tại sao có người gọi nó là Smart Data Object

- ví dụ 1 pure object:

public class Employee : INotifyPropertyChanged
{
    private string _name;
    private DateTime _birthday;
    private int _age;

    public Employee(string name, int age)
    {
        _name = name;
        _age = age;
    }

    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
            PropertyChanged(this, new PropertyChangedEventArgs("Name"));
        }
    }

    public DateTime Birthday
    {
        get
        {
            return _birthday;
        }
        set
        {
            _birthday = value;
            PropertyChanged(this, new PropertyChangedEventArgs("Birthday"));
            PropertyChanged(this, new PropertyChangedEventArgs("Age"));
            //Thông báo ra ngoài tuổi cũng thay đổi vì ngày sinh đã thay đổi
        }
    }

    public int Age
    {
        get 
        { 
        return DateTime.Now.Year - _birthday.Year 
        }
    }
    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

* Giải thix: Như các bạn thấy thì nó chỉ đơn giản là 1 class với các field và property. Nhưng nó có kế thừa thêm interface INotifyPropertyChanged và ở mỗi hàm set thì gọi event PropertyChanged. Làm như vậy là để phục vụ cho việc Binding và xử lý trên các event của dữ liệu. xem bài kỹ thuật binding

Ví dụ: bạn có 1 Object là Emplyee, bạn bind vào các control trong giao diện cái nagỳ sinh và tuổi, khi trong code bạn gán ngày sinh employee.Birthday = DateTime.Today .. thì trên giao diện sẽ ngay lập tức hiện ra ngày sinh này và tuổi của employee. giả sử bạn không implement interface INotifyPropertyChanged thì trên giao diện vẫn giữ lại giá trị cũ mặc dù dữ liệu đã thay đổi.

Dựa trên ý tưởng này thì ta có thể xây dựng 1 lớp gọi là BaseEntity kế thừa từ INotifyPropertyChanged. Tất cả các entity khác kế thừa từ lớp BaseEntity này. Lớp này cũng cần cài đặt 1 số hàm mà tất cả Entity cần dùng tùy theo nhu cầu của bạn. Ví dụ như thêm 1 thuộc tính chỉ định data có bị thay đổi chưa, copy object, ....

* Công việc tiếp theo là ta tìm 1 cái thay thế DataTable. Nếu bạn để ý, khi ta bind 1 cái List thông thường vào DataGridView thì ta không thể Add thêm dữ liệu được, trừ phi bạn Add dữ liệu vào cái list này và binding lại (gán lại DataSource). Thay vì dùng List<T> bình thường thì bạn có thể dùng BindingList<T>. 

Ví dụ:

BindingList<Employee> list = new BindingList<Employee>(); 
list.Add(new Employee("das", 11));   
...         
dataGridViewEmployee.DataSource = list;

 

khi này thì hành vi trên DataGridView sẽ giống như khi ta binding và 1 DataTable. Nhưng chỉ khác 1 điều là click trên Header thì nó không sắp xếp cho mình. Để giải quyết vấn đề này thì mình sẽ cài đặt lại cái Binding List.

public class ObjectCollection<T> : BindingList<T> where T : BaseEntity
{
    #region Sorting

    private bool _isSorted;
    private ListSortDirection _dir = ListSortDirection.Ascending;
    private PropertyDescriptor _sort = null;

    public void Sort()
    {
        ApplySortCore(_sort, _dir);
    }

    public void Sort(string property)
    {
        // Get the PD
        _sort = FindPropertyDescriptor(property);

        //Sort
        ApplySortCore(_sort, _dir);
    }

    public void Sort(string property, ListSortDirection direction)
    {
        //Get the sort property 
        _sort = FindPropertyDescriptor(property);
        _dir = direction;

        //Sort
        ApplySortCore(_sort, _dir);
    }

    protected override bool SupportsSortingCore
    {
        get { return true; }
    }

    protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction)
    {
        List<T> items = this.Items as List<T>;

        if ((null != items) && (null != property))
        {
            PropertyComparer<T> pc = new PropertyComparer<T>(property, direction);
            BeginEdit();
            items.Sort(pc);
            EndEdit();

            //Set sorted
            _isSorted = true;
        }
        else
        {
            // Set sorted
            _isSorted = false;
        }
    }

    protected override bool IsSortedCore
    {
        get { return _isSorted; }
    }

    protected override void RemoveSortCore()
    {
        _isSorted = false;
    }

    private PropertyDescriptor FindPropertyDescriptor(string property)
    {
        PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(typeof(T));
        PropertyDescriptor prop = null;

        if (null != pdc)
        {
            prop = pdc.Find(property, true);
        }

        return prop;
    }

    #endregion
}

Và cần 1 cái Comparer để nó so sánh để sort

internal class PropertyComparer<TKey> : IComparer<TKey>
{
    private PropertyDescriptor _property;
    private ListSortDirection _direction;

    public PropertyComparer(PropertyDescriptor property, ListSortDirection direction)
    {
        _property = property;
        _direction = direction;
    }

    public int Compare(TKey xVal, TKey yVal)
    {
        //Get property values
        object xValue = GetPropertyValue(xVal, _property.Name);
        object yValue = GetPropertyValue(yVal, _property.Name);

        //Determine sort order
        if (_direction == ListSortDirection.Ascending)
        {
            return CompareAscending(xValue, yValue);
        }
        else
        {
            return CompareDescending(xValue, yValue);
        }
    }

    public bool Equals(TKey xVal, TKey yVal)
    {
        return xVal.Equals(yVal);
    }

    public int GetHashCode(TKey obj)
    {
        return obj.GetHashCode();
    }

    // Compare two property values of any type
    private int CompareAscending(object xValue, object yValue)
    {
        int result;

        //If values implement IComparer
        if (xValue is IComparable)
        {
            result = ((IComparable)xValue).CompareTo(yValue);
        }
        // If values don't implement IComparer but are equivalent
        else if (xValue.Equals(yValue))
        {
            result = 0;
        }
        //Values don't implement IComparer and are not equivalent, so compare as string values
        else result = xValue.ToString().CompareTo(yValue.ToString());

        // Return result
        return result;
    }

    private int CompareDescending(object xValue, object yValue)
    {
        //Return result adjusted for ascending or descending sort order ie
        //multiplied by 1 for ascending or -1 for descending
        return CompareAscending(xValue, yValue) * -1;
    }

    private object GetPropertyValue(TKey value, string property)
    {
        //Get property
        PropertyInfo propertyInfo = value.GetType().GetProperty(property);

        //Return value 
        return propertyInfo.GetValue(value, null);
    }
}

Nhưng cái này vẫn còn thiếu. Cũng như bạn biết thì để filter dữ liệu thì ADO.NET có 1 cái là DataView rất mạnh. Cái BindingList này không làm được. Tiếc là cái này không có trong .NET 2.0, nhưng nó lại có trong cái .NET 3.0 với cái tên ListCollectionView. Việc cài đặt nó cũng tương đối khó khăn, nên bạn có thể down load 1 ố open source xài đỡ. Chừng nào đủ trình độ thì tự cài đặt cho mình 1 cái (Tới lúc đó chắc chuyển lên .NET 3.0 cho phẻ he he he). Vô google seach chữ BindingListView(trong sourceforge có) hoặc ObjectListView nó sẽ chỉ chỗ cho bạn download. mặc dù vậy bạn vẫn cần cái ObjectCollection<T> mà mình giới thiệu ở trên để thực hiện thêm các chức năng như Merge, delete.... . bạn nên vào google tìm chữ BindingList hay pureobject hay smart business object gì đó để được các sư phụ hướng dẫn cài đặt lại lớp này.

 

Chúc các bạn thành công.