Подтвердить что ты не робот

Расширение DataTable в С#

Статический конструктор для класса SourceManager проходит через все модули/классы и обнаруживает все классы, реализующие ISource. Он будет создавать экземпляр каждого из них и выставить IEnumerable из них как статическое свойство, называемое IEnumerable<ISource> Sources. Для простоты ISource имеет два свойства: DataTable Table { get; } и string UniqueName { get; }. Когда экземпляр каждого другого ISource отвечает за заполнение его Table из SQL, MDX и т.д. Для всех ISource, которые я написал до сих пор, загрузка Table со всеми DataRow при создании экземпляра была достаточной, Однако У меня теперь есть ситуация, когда я хотел бы загружать Table с DataRow лениво, а не все спереди. Как это сделать? Я пройду через пример.

PermissionSource реализует ISource. Свойству Table, имеющему a private set, присваивается значение new PermissionDataTable(). Его UniqueName составляет "Permissions". На данный момент в базу данных не входят никакие разрешения, загруженные из базы данных.

ISource permissionSource = SourceManager.Sources.
    Where(s => "Permission".Equals(s.UniqueName)).First();

Теперь мы получили PermissionSource, но через интерфейс. Позвольте получить разрешение.

DataRow row = permissionSource.Table.Rows.Cast<DataRow>().
    Where(r => r["PermissionName"].Equals("PermissionName")).First()

Я переопределил свойство Rows в PermissionDataTable, чтобы вышесказанное каким-то образом получило значение разрешений, связанных с "PermissionName" в базе данных. Другие разрешения не загружаются.

У меня нет выбора в системе разрешений, и у меня нет выбора, чтобы не использовать DataTable.

EDIT:

В моем примере мне нужно переопределить свойство Rows DataTable. Rows, однако, DataRowCollection, который sealed. Таким образом, не так много можно сделать с точки зрения создания минимальной пользовательской реализации DataTable, как я хочу.

4b9b3361

Ответ 1

Я не уверен, что понимаю ваши ограничения в использовании DataTable, но одна вещь, которую я делал в прошлом, когда мне нужно было "обновлять" данные в DataTable или повторно заполнять их, используя разные критерии, - создать новый класс полученный из DataTable, который включает ссылку на DataAdapter с информацией о соединении и выборе, первоначально используемой для заполнения DataTable.

Например, подкласс класса DataTable может выглядеть примерно как код LazyDataTable ниже. Обратите внимание, что я добавил несколько различных способов доступа к строкам. Они могут иметь смысл после взглянуть на PermissionSource и главный Program код в конце этого сообщения. Также обратите внимание, что я не включил все детали, связанные с правильным открытием и закрытием соединений с базами данных в каждом случае. Как вы справитесь с этим, это будет зависеть от вашей модели для доступа к базе данных (например, объединение пулов, общие подключения и т.д.).

//using System.Data.Common;
public class LazyDataTable : DataTable {
    protected DbDataAdapter Adapter { get; set; }

    public LazyDataTable(DbDataAdapter a) {
        Adapter = a;
    }
    /// <summary>
    /// Save changes back to the database, using the DataAdapter
    /// </summary>
    public void Update() {
        Adapter.Update(this);
    }
    /// <summary>
    /// Fill this datatable using the SelectCommand in the DataAdapter
    /// The DB connection and query have already been set.
    /// </summary>
    public void Fill() {
        Adapter.Fill(this);
    }

    /// <summary>
    /// read and return one row at a time, using IEnumerable syntax
    /// (this example does not actually add the row to this table, 
    /// but that can be done as well, if desired.
    /// </summary>
    public IEnumerable<DataRow> LazyReadRows() {
        using (var reader = OpenReader()) {
            //Get the schema from the reader and copy it to this table.
            var schema = reader.GetSchemaTable();
            var values = new object[schema.Columns.Count];
            while (reader.Read()) {
                reader.GetValues(values);
                var row = schema.NewRow();
                row.ItemArray = values;
                yield return row;
            }
        }
    }

    /// <summary>
    /// Fill one row at a time, and return the new row.
    /// </summary>
    public DataRow ReadRow() {
        if (_reader == null || _reader.IsClosed) 
            _reader = OpenReader();
        //Get the schema from the reader and copy it to this table.
        if (this.Columns.Count == 0) 
            this.Columns.AddRange(_reader.GetSchemaTable().Columns.Cast<DataColumn>().ToArray());
        if (!_reader.Read()) {
            _reader.Dispose();
            return null;
        }
        var values = new object[_reader.FieldCount];
        _reader.GetValues(values);
        return this.Rows.Add(values);
    }
    private DbDataReader _reader = null;

    private DbDataReader OpenReader() {
        OpenConnect();
        return Adapter.SelectCommand.ExecuteReader();
    }

    private void OpenConnect() {
        var cn = Adapter.SelectCommand.Connection;
        if (cn.State == ConnectionState.Closed)
            cn.Open();
    }

    /// <summary>
    /// Change a Parameter in the SelectCommand, to filter which rows to retrieve.
    /// </summary>
    public void SetSelectParam(string name, object value) {
        var selparams = Adapter.SelectCommand.Parameters;
        selparams[name].Value = value;
    }
}

Затем ваш PermissionSource создаст LazyDataTable и соответствующим образом настроит DataAdapter (включая соединение и команду SELECT). Он не заполнил DataTable, но вместо этого вернул бы его пустым, чтобы быть заполненным позже кодом приложения. Таким образом, ваш PermissionSource может что-то вроде кода ниже. Я использовал объекты данных System.Data.OleDb в качестве примера, но вы бы использовали любые необходимые поставщики ADO.

interface ISource {
    public DataTable Table { get; }
    string UniqueName { get; }
}

public class PermissionSource : ISource {
    /// <summary>
    /// Loads a DataTable with all of the information to load it lazily.
    /// </summary>
    public DataTable Table { 
        get { 
            const string SELECT_CMD = "SELECT * FROM [Permissions] WHERE ([PermissionName] IS NULL OR [PermissionName][email protected]) AND [OtherProperty][email protected]";
            var conn = new OleDbConnection("...ConnectionString...");
            var selectCmd = new OleDbCommand(SELECT_CMD, conn);
            selectCmd.Parameters.AddWithValue("p1", "PermissionName");
            selectCmd.Parameters.AddWithValue("p2", 0);
            var adapter = new OleDbDataAdapter(selectCmd);
            var builder = new OleDbCommandBuilder(adapter); //used to generate the UPDATE and DELETE commands...
            adapter.UpdateCommand = builder.GetUpdateCommand(); //etc.
            //Do NOT fill the table here. Instead, let the caller fill it.
            return new LazyDataTable(adapter);
        }
    }
    public string UniqueName { get { return "Permission"; } }
}

Ваш основной программный код будет использовать PermissionSource и LazyDataTable следующим образом:

    static class Program {
    void GetPermissions() {
        ISource permissionSource = SourceManager.Sources.
            Where(s => "Permission".Equals(s.UniqueName)).First();

        var table = permissionSource.Table as LazyDataTable;
        table.SetSelectParam("PermissionName", "Admin");

        //If you want to fill ALL rows in one step:
        table.Fill(); 

        // OR If you want to fill one row at a time, and add it to the table:
        DataRow row;
        while(null != (row = table.ReadRow())) {
            //do something with each individual row. Exit whenever desired.
            Console.WriteLine(row["PermissionName"]);
        }

        // OR If you prefer IEnumerable semantics:
        DataRow row = table.LazyReadRows().FirstOrDefault(someValue.Equals(row["columnname"]));

        //OR use foreach, etc. Rows are still ONLY read one at a time, each time IEnumerator.MoveNext() is called.
        foreach (var row in table.LazyReadRows())
            if (row["someColumn"] == "someValue")
                DoSomething(row["anothercolumn"]);
    }
}

Вы можете, конечно, смешивать и сопоставлять части LazyDataTable, показанные здесь, чтобы достичь именно того, чего вы хотите в рамках ограничений приложения. Разумеется, было бы намного лучше, если бы вы могли переключиться на другую модель обмена данными, но если вы ДОЛЖНЫ возвращать DataTable из каждого источника, то по крайней мере вы можете вернуть более функциональный DataTable, когда это необходимо, путем подкласса его, как я продемонстрировал Вот. Это позволяет вам вернуть дополнительную информацию, которую вы можете использовать, чтобы заполнить таблицу по своему усмотрению. Я бы по-прежнему рекомендовал вам изучить LinqToSQL, а также, возможно, попытаться перейти к простому возврату DbDataReader или другого объекта, подобного приведенному здесь LazyDataTable, который позволит вам как настроить исходный запрос (например, используя SetSelectParam), а также для чтения данных в одну строку за раз.

Надеюсь, что это поможет!

Ответ 2

Вероятно, он больше не нужен, но это может сделать это.

public interface ISource
{
    DataTable Table { get; }
    string Name { get; set; }
}

public class MySource : ISource
{
    private DataTable table;
    public DataTable Table
    {
        get
        {
            if (table == null)
                // Initialize your data.
                table = new System.Data.DataTable();
            return table;
        }
        private set
        {
            this.table = value;
        }
    }
    public string Name { get; set; }
}

Ответ 3

Я думаю, что то, что вы ищете, это что-то вроде LinqToSql, где каждый из ваших ISources может возвращать таблицу вместо DataTable. Это позволит вам использовать динамические запросы, подобные приведенным в примере, и загружать только запрошенные данные и только при необходимости. Я не знаю, сможете ли вы найти поставщиков LinqToSql для всех ваших источников данных. Если это становится проблемой, вы можете попытаться использовать Entity Framework, как было предложено ранее.

Ответ 4

Следующий пример добавляет значение метки времени в DataTable через свойство ExtendedProperties.

private void GetAndSetExtendedProperties(DataTable myTable){
 // Add an item to the collection.
 myTable.ExtendedProperties.Add("TimeStamp", DateTime.Now);
 // Print the item.
Console.WriteLine(myTable.ExtendedProperties["TimeStamp"]);
}