Динамические данные, связывающие данные

У меня есть набор "динамических данных", которые мне нужно привязать к GridControl. До сих пор я использовал стандартный класс DataTable, который является частью пространства имен System.Data. Это отлично работает, но мне сказали, что я не могу использовать это, поскольку он слишком тяжел для сериализации по сети между клиентом и сервером.

Итак, я подумал, что могу легко реплицировать "вырезанную" версию класса DataTable, просто имея тип List<Dictionary<string, object>>, посредством которого List представляет коллекцию строк, и каждый Словарь представляет одну строку с именами столбцов и значения как тип KeyValuePair. Я мог бы настроить Grid, чтобы свойства столбца DataField соответствовали свойствам ключей в словаре (точно так же, как я делал для имен столбцов DataTable.

Однако после выполнения

gridControl.DataSource = table;
gridControl.RefreshDataSource();

В сетке нет данных...

Мне кажется, мне нужно реализовать IEnumerator - любая помощь по этому поводу была бы высоко оценена!

Пример кода вызова выглядит следующим образом:

var table = new List<Dictionary<string,object>>();

var row = new Dictionary<string, object>
{
    {"Field1", "Data1"},
    {"Field2", "Data2"},
    {"Field3", "Data3"}
};

table.Add(row);

gridControl1.DataSource = table;
gridControl1.RefreshDataSource();

Ответ 1

Добро пожаловать в прекрасный мир System.ComponentModel. Этот темный угол .NET очень мощный, но очень сложный.

Слово предостережения; если у вас не будет много времени для этого - вы можете сделать так, чтобы просто сериализовать его в любом механизме, которым вы довольны, но переведите его обратно на DataTable на каждом конце... что следует не для слабонервных;-p

Во-первых - привязка данных (для таблиц) работает против списков (IList/IListSource) - так что List<T> должно быть хорошо (отредактировано: я что-то неправильно понял). Но он не поймет, что ваш словарь действительно столбцы...

Чтобы получить тип, чтобы претендовать на наличие столбцов, вам нужно использовать пользовательские реализации PropertyDescriptor. Существует несколько способов сделать это, в зависимости от того, являются ли определения столбцов всегда одинаковыми (но определяется во время выполнения, то есть, возможно, из конфигурации), или изменяется ли оно на использование (например, как каждый экземпляр DataTable может иметь разные столбцы).

Для настройки "для каждого экземпляра" вам нужно посмотреть ITypedList - этот зверь (реализованный в дополнение к IList) имеет интересную задачу представления свойств для табличных данных... но он не одинок:

Для настройки "для каждого типа" вы можете посмотреть TypeDescriptionProvider - это может предложить динамические свойства для класса...

... или вы можете реализовать ICustomTypeDescriptor - но это используется только (для списков) в случаях очень случайных событий (индексатор объектов (public object this[int index] {get;} ") и по крайней мере одна строка в список в точке привязки). (этот интерфейс намного полезнее при связывании дискретных объектов, т.е. не списков).

Реализация ITypedList и предоставление модели PropertyDescriptor - это тяжелая работа... следовательно, это делается только очень редко. Я довольно хорошо знаком с этим, но я бы не сделал это просто для смеха...


Здесь очень, очень упрощенная реализация (все столбцы - это строки, отсутствие уведомлений (через дескриптор), отсутствие проверки (IDataErrorInfo), отсутствие конверсий (TypeConverter), отсутствие дополнительной поддержки списка (IBindingList/IBindingListView), абстракции (IListSource), других других метаданных/атрибутов и т.д.):

using System.ComponentModel;
using System.Collections.Generic;
using System;
using System.Windows.Forms;

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        PropertyBagList list = new PropertyBagList();
        list.Columns.Add("Foo");
        list.Columns.Add("Bar");
        list.Add("abc", "def");
        list.Add("ghi", "jkl");
        list.Add("mno", "pqr");

        Application.Run(new Form {
            Controls = {
                new DataGridView {
                    Dock = DockStyle.Fill,
                    DataSource = list
                }
            }
        });
    }
}
class PropertyBagList : List<PropertyBag>, ITypedList
{
    public PropertyBag Add(params string[] args)
    {
        if (args == null) throw new ArgumentNullException("args");
        if (args.Length != Columns.Count) throw new ArgumentException("args");
        PropertyBag bag = new PropertyBag();
        for (int i = 0; i < args.Length; i++)
        {
            bag[Columns[i]] = args[i];
        }
        Add(bag);
        return bag;
    }
    public PropertyBagList() { Columns = new List<string>(); }
    public List<string> Columns { get; private set; }

    PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
    {
        if(listAccessors == null || listAccessors.Length == 0)
        {
            PropertyDescriptor[] props = new PropertyDescriptor[Columns.Count];
            for(int i = 0 ; i < props.Length ; i++)
            {
                props[i] = new PropertyBagPropertyDescriptor(Columns[i]);
            }
            return new PropertyDescriptorCollection(props, true);            
        }
        throw new NotImplementedException("Relations not implemented");
    }

    string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
    {
        return "Foo";
    }
}
class PropertyBagPropertyDescriptor : PropertyDescriptor
{
    public PropertyBagPropertyDescriptor(string name) : base(name, null) { }
    public override object GetValue(object component)
    {
        return ((PropertyBag)component)[Name];
    }
    public override void SetValue(object component, object value)
    {
        ((PropertyBag)component)[Name] = (string)value;
    }
    public override void ResetValue(object component)
    {
        ((PropertyBag)component)[Name] = null;
    }
    public override bool CanResetValue(object component)
    {
        return true;
    }
    public override bool ShouldSerializeValue(object component)
    {
        return ((PropertyBag)component)[Name] != null;
    }
    public override Type PropertyType
    {
        get { return typeof(string); }
    }
    public override bool IsReadOnly
    {
        get { return false; }
    }
    public override Type ComponentType
    {
        get { return typeof(PropertyBag); }
    }
}
class PropertyBag
{
    private readonly Dictionary<string, string> values
        = new Dictionary<string, string>();
    public string this[string key]
    {
        get
        {
            string value;
            values.TryGetValue(key, out value);
            return value;
        }
        set
        {
            if (value == null) values.Remove(key);
            else values[key] = value;
        }
    }
}