Заполнить таблицу данных из средства чтения данных

103

Я делаю базовые вещи на C # (MS VS2008), и у меня вопрос больше о правильном дизайне, чем о конкретном коде.

Я создаю базу данных, а затем пытаюсь загрузить ее из устройства чтения данных (которое основано на хранимой процедуре SQL). Что мне интересно, так это то, является ли наиболее эффективным способом загрузки таблицы данных выполнение оператора while или есть лучший способ.

Для меня единственный недостаток заключается в том, что мне нужно вручную вводить поля, которые я хочу добавить в свой оператор while, но я также не знаю, как это автоматизировать, поскольку я не хочу, чтобы все поля из SP просто выбирали их. , но в моих глазах это не имеет большого значения.

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

var dtWriteoffUpload = new DataTable();
dtWriteoffUpload.Columns.Add("Unit");
dtWriteoffUpload.Columns.Add("Year");
dtWriteoffUpload.Columns.Add("Period");
dtWriteoffUpload.Columns.Add("Acct");
dtWriteoffUpload.Columns.Add("Descr");
dtWriteoffUpload.Columns.Add("DEFERRAL_TYPE");
dtWriteoffUpload.Columns.Add("NDC_Indicator");
dtWriteoffUpload.Columns.Add("Mgmt Cd");
dtWriteoffUpload.Columns.Add("Prod");
dtWriteoffUpload.Columns.Add("Node");
dtWriteoffUpload.Columns.Add("Curve_Family");
dtWriteoffUpload.Columns.Add("Sum Amount");
dtWriteoffUpload.Columns.Add("Base Curr");
dtWriteoffUpload.Columns.Add("Ledger");  

cmd = util.SqlConn.CreateCommand();
cmd.CommandTimeout = 1000;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "proc_writeoff_data_details";
cmd.Parameters.Add("@whoAmI", SqlDbType.VarChar).Value = 

WindowsIdentity.GetCurrent().Name;

cmd.Parameters.Add("@parmEndDateKey", SqlDbType.VarChar).Value = myMostRecentActualDate;
cmd.Parameters.Add("@countrykeys", SqlDbType.VarChar).Value = myCountryKey;
cmd.Parameters.Add("@nodekeys", SqlDbType.VarChar).Value = "1,2";
break;


dr = cmd.ExecuteReader();
while (dr.Read())                    
{
    dtWriteoffUpload.Rows.Add(dr["country name"].ToString(), dr["country key"].ToString());
}
Райан Уорд
источник
Повторяющийся вопрос: stackoverflow.com/questions/4089471/…
vapcguy

Ответы:

283

Вы можете загрузить DataTableпрямо из средства чтения данных, используя Load()метод, который принимает IDataReader.

var dataReader = cmd.ExecuteReader();
var dataTable = new DataTable();
dataTable.Load(dataReader);
Саги
источник
2
Вы спасли мой день (Y)
Uzair Xlade
1
Это то, что я искал неделю!
TheTechy
17

Пожалуйста, проверьте приведенный ниже код. Автоматически он будет преобразован как DataTable

private void ConvertDataReaderToTableManually()
    {
        SqlConnection conn = null;
        try
        {
            string connString = ConfigurationManager.ConnectionStrings["NorthwindConn"].ConnectionString;
            conn = new SqlConnection(connString);
            string query = "SELECT * FROM Customers";
            SqlCommand cmd = new SqlCommand(query, conn);
            conn.Open();
            SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
            DataTable dtSchema = dr.GetSchemaTable();
            DataTable dt = new DataTable();
            // You can also use an ArrayList instead of List<>
            List<DataColumn> listCols = new List<DataColumn>();

            if (dtSchema != null)
            {
                foreach (DataRow drow in dtSchema.Rows)
                {
                    string columnName = System.Convert.ToString(drow["ColumnName"]);
                    DataColumn column = new DataColumn(columnName, (Type)(drow["DataType"]));
                    column.Unique = (bool)drow["IsUnique"];
                    column.AllowDBNull = (bool)drow["AllowDBNull"];
                    column.AutoIncrement = (bool)drow["IsAutoIncrement"];
                    listCols.Add(column);
                    dt.Columns.Add(column);
                }
            }

            // Read rows from DataReader and populate the DataTable
            while (dr.Read())
            {
                DataRow dataRow = dt.NewRow();
                for (int i = 0; i < listCols.Count; i++)
                {
                    dataRow[((DataColumn)listCols[i])] = dr[i];
                }
                dt.Rows.Add(dataRow);
            }
            GridView2.DataSource = dt;
            GridView2.DataBind();
        }
        catch (SqlException ex)
        {
            // handle error
        }
        catch (Exception ex)
        {
            // handle error
        }
        finally
        {
            conn.Close();
        }

    }
Сараткумар
источник
Есть прямая возможность загрузить datareader в datatable, тогда зачем кому-то это использовать?
Аббас
@sarathkumar Хорошая работа .. я искал такой код
SimpleGuy
@Abbas Coz, встроенные данные загружаются очень медленно
SimpleGuy
dt.Load(reader)также не всегда работает - я бы получил эти надоедливые Object reference not set to an instance of an objectошибки, вероятно, когда я не получил ни одной строки обратно. Что-то вроде этого руководства пригодится. Я попробовал и мне пришлось избавиться от этих column.строк в dtSchema foreachцикле, потому что он сказал, что это незаконное приведение к boolвключению (bool)drow["IsUnique"]. Они мне не понадобились, достаточно получить имена столбцов, чтобы заполнить новые DataTable. Это помогло мне преодолеть ds.Fill(adapter)проблему, из-за которой я не мог загрузить большую таблицу SELECT * FROM MyTable.
vapcguy
Одно предостережение - если в каком-либо из столбцов есть нулевые значения, они должны быть обработаны, иначе эта функция вызовет исключение. Придется проверить if (!dr.IsDBNull(i))как следующее в этом forцикле. Затем вы делаете свое дело dataRow. Но тогда вам понадобится это else, если вы найдете нуль. Если вы это сделаете, вам нужно выяснить тип добавляемого столбца и соответствующим образом присвоить значение null (то есть вы можете назначить, String.Emptyесли он имеет тип System.String, но вам нужно назначить, 0если это System.Int16(логическое поле) или System.Decimal.
vapcguy
13

Если вы пытаетесь загрузить DataTable, используйте SqlDataAdapterвместо этого:

DataTable dt = new DataTable();

using (SqlConnection c = new SqlConnection(cString))
using (SqlDataAdapter sda = new SqlDataAdapter(sql, c))
{
    sda.SelectCommand.CommandType = CommandType.StoredProcedure;
    sda.SelectCommand.Parameters.AddWithValue("@parm1", val1);
    ...

    sda.Fill(dt);
}

Вам даже не нужно определять столбцы. Просто создайте DataTableи Fillэто.

Вот cStringваша строка подключения и sqlкоманда хранимой процедуры.

Майк Перрено
источник
1
Единственная проблема здесь в том, что если вы обнаружите, что столбец / значение вызывает исключение во время заполнения, оно не дает вам никаких подробностей, например, вы могли бы использовать SqlDataReaderи читать их, используя цикл по полям.
vapcguy
9

Как заявил Саги в своем ответе, DataTable.Load - хорошее решение. Если вы пытаетесь загрузить несколько таблиц из одного считывателя, вам не нужно вызывать DataReader.NextResult. Метод DataTable.Load также перемещает читателя к следующему набору результатов (если есть).

// Read every result set in the data reader.
while (!reader.IsClosed)
{
    DataTable dt = new DataTable();
    // DataTable.Load automatically advances the reader to the next result set
    dt.Load(reader);
    items.Add(dt);
}
Ghawkes
источник
5

Я тоже изучил это, и после сравнения метода SqlDataAdapter.Fill с функциями SqlDataReader.Load я обнаружил, что метод SqlDataAdapter.Fill более чем в два раза быстрее с наборами результатов, которые я использовал

Используемый код:

    [TestMethod]
    public void SQLCommandVsAddaptor()
    {
        long AdapterFillLargeTableTime, readerLoadLargeTableTime, AdapterFillMediumTableTime, readerLoadMediumTableTime, AdapterFillSmallTableTime, readerLoadSmallTableTime, AdapterFillTinyTableTime, readerLoadTinyTableTime;

        string LargeTableToFill = "select top 10000 * from FooBar";
        string MediumTableToFill = "select top 1000 * from FooBar";
        string SmallTableToFill = "select top 100 * from FooBar";
        string TinyTableToFill = "select top 10 * from FooBar";

        using (SqlConnection sconn = new SqlConnection("Data Source=.;initial catalog=Foo;persist security info=True; user id=bar;password=foobar;"))
        {
            // large data set measurements
            AdapterFillLargeTableTime = MeasureExecutionTimeMethod(sconn, LargeTableToFill, ExecuteDataAdapterFillStep);
            readerLoadLargeTableTime = MeasureExecutionTimeMethod(sconn, LargeTableToFill, ExecuteSqlReaderLoadStep);
            // medium data set measurements
            AdapterFillMediumTableTime = MeasureExecutionTimeMethod(sconn, MediumTableToFill, ExecuteDataAdapterFillStep);
            readerLoadMediumTableTime = MeasureExecutionTimeMethod(sconn, MediumTableToFill, ExecuteSqlReaderLoadStep);
            // small data set measurements
            AdapterFillSmallTableTime = MeasureExecutionTimeMethod(sconn, SmallTableToFill, ExecuteDataAdapterFillStep);
            readerLoadSmallTableTime = MeasureExecutionTimeMethod(sconn, SmallTableToFill, ExecuteSqlReaderLoadStep);
            // tiny data set measurements
            AdapterFillTinyTableTime = MeasureExecutionTimeMethod(sconn, TinyTableToFill, ExecuteDataAdapterFillStep);
            readerLoadTinyTableTime = MeasureExecutionTimeMethod(sconn, TinyTableToFill, ExecuteSqlReaderLoadStep);
        }
        using (StreamWriter writer = new StreamWriter("result_sql_compare.txt"))
        {
            writer.WriteLine("10000 rows");
            writer.WriteLine("Sql Data Adapter 100 times table fill speed 10000 rows: {0} milliseconds", AdapterFillLargeTableTime);
            writer.WriteLine("Sql Data Reader 100 times table load speed 10000 rows: {0} milliseconds", readerLoadLargeTableTime);
            writer.WriteLine("1000 rows");
            writer.WriteLine("Sql Data Adapter 100 times table fill speed 1000 rows: {0} milliseconds", AdapterFillMediumTableTime);
            writer.WriteLine("Sql Data Reader 100 times table load speed 1000 rows: {0} milliseconds", readerLoadMediumTableTime);
            writer.WriteLine("100 rows");
            writer.WriteLine("Sql Data Adapter 100 times table fill speed 100 rows: {0} milliseconds", AdapterFillSmallTableTime);
            writer.WriteLine("Sql Data Reader 100 times table load speed 100 rows: {0} milliseconds", readerLoadSmallTableTime);
            writer.WriteLine("10 rows");
            writer.WriteLine("Sql Data Adapter 100 times table fill speed 10 rows: {0} milliseconds", AdapterFillTinyTableTime);
            writer.WriteLine("Sql Data Reader 100 times table load speed 10 rows: {0} milliseconds", readerLoadTinyTableTime);

        }
        Process.Start("result_sql_compare.txt");
    }

    private long MeasureExecutionTimeMethod(SqlConnection conn, string query, Action<SqlConnection, string> Method)
    {
        long time; // know C#
        // execute single read step outside measurement time, to warm up cache or whatever
        Method(conn, query);
        // start timing
        time = Environment.TickCount;
        for (int i = 0; i < 100; i++)
        {
            Method(conn, query);
        }
        // return time in milliseconds
        return Environment.TickCount - time;
    }

    private void ExecuteDataAdapterFillStep(SqlConnection conn, string query)
    {
        DataTable tab = new DataTable();
        conn.Open();
        using (SqlDataAdapter comm = new SqlDataAdapter(query, conn))
        {
            // Adapter fill table function
            comm.Fill(tab);
        }
        conn.Close();
    }

    private void ExecuteSqlReaderLoadStep(SqlConnection conn, string query)
    {
        DataTable tab = new DataTable();
        conn.Open();
        using (SqlCommand comm = new SqlCommand(query, conn))
        {
            using (SqlDataReader reader = comm.ExecuteReader())
            {
                // IDataReader Load function
                tab.Load(reader);
            }
        }
        conn.Close();
    }

Полученные результаты:

10000 rows:
Sql Data Adapter 100 times table fill speed 10000 rows: 11782 milliseconds
Sql Data Reader  100 times table load speed 10000 rows: 26047 milliseconds
1000 rows:
Sql Data Adapter 100 times table fill speed 1000 rows: 984  milliseconds
Sql Data Reader  100 times table load speed 1000 rows: 2031 milliseconds
100 rows:
Sql Data Adapter 100 times table fill speed 100 rows: 125 milliseconds
Sql Data Reader  100 times table load speed 100 rows: 235 milliseconds
10 rows:
Sql Data Adapter 100 times table fill speed 10 rows: 32 milliseconds
Sql Data Reader  100 times table load speed 10 rows: 93 milliseconds

Для проблем с производительностью использование метода SqlDataAdapter.Fill намного эффективнее. Так что, если вы не хотите выстрелить себе в ногу, используйте это. Он работает быстрее для небольших и больших наборов данных.

Martijn
источник