Wypełnij tabelę danych z czytnika danych


103

Robię podstawową rzecz w C # (MS VS2008) i mam pytanie bardziej o właściwy projekt niż o konkretny kod.

Tworzę datatable, a następnie próbuję załadować datatable z datareader (który jest oparty na procedurze składowanej SQL). Zastanawiam się, czy najbardziej efektywnym sposobem załadowania datatable jest wykonanie instrukcji while, czy też istnieje lepszy sposób.

Dla mnie jedyną wadą jest to, że muszę ręcznie wpisywać pola, które chcę dodać w moim oświadczeniu while, ale i tak nie wiem, jak to zautomatyzować, ponieważ nie chcę, aby wszystkie pola z SP po prostu zaznaczały , ale to nie jest wielka sprawa w moich oczach.

Załączam fragmenty kodu poniżej całości tego, co robię, chociaż dla mnie sam kod nie jest niezwykły ani nawet to, o co pytam. Moreso zastanawiając się nad moją metodologią, będę później szukał pomocy w kodzie, jeśli moja strategia jest zła / nieefektywna.

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());
}

Odpowiedzi:


283

Możesz załadować plik DataTablebezpośrednio z czytnika danych przy użyciu Load()metody, która akceptuje plik IDataReader.

var dataReader = cmd.ExecuteReader();
var dataTable = new DataTable();
dataTable.Load(dataReader);

2
Uratowałeś mój dzień (Y)
Uzair Xlade

1
Właśnie tego szukałem przez tydzień!
TheTechy

17

Sprawdź poniższy kod. Automatycznie zostanie przekonwertowany na 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();
        }

    }

Istnieje prosta opcja, aby załadować datareader do datatable, więc po co ktoś miałby tego używać?
Abbas

@sarathkumar Dobra robota .. Szukałem takiego kodu
SimpleGuy

@Abbas Coz, wbudowane ładowanie danych jest bardzo wolne
SimpleGuy

dt.Load(reader)też nie zawsze działa - dostaję te brzydkie Object reference not set to an instance of an objectbłędy, prawdopodobnie wtedy, gdy nie odzyskam żadnych wierszy. Przydaje się taka instrukcja. Próbowałem go i musiał pozbyć się tych column.linii w dtSchema foreachpętli, ponieważ stwierdzono, że był nielegalny oddanych do booldnia (bool)drow["IsUnique"]. Nie potrzebowałem ich, wystarczy pobrać nazwy kolumn, aby wypełnić nowe DataTable. To pomogło mi rozwiązać ds.Fill(adapter)problem polegający na tym, że nie mogłem załadować dużego stołu SELECT * FROM MyTable.
vapcguy

Jedno zastrzeżenie - jeśli w którejkolwiek z kolumn znajdują się wartości null, należy je obsłużyć, w przeciwnym razie ta funkcja spowoduje wyjątek. Muszę sprawdzić if (!dr.IsDBNull(i))jako następną rzecz w tej forpętli. Następnie robisz swoje dataRow. Ale wtedy potrzebujesz elsetego, na wypadek, gdybyś znalazł null. Jeśli to zrobisz, musisz określić typ dodawanej kolumny i odpowiednio przypisać wartość null (tj. Możesz przypisać, String.Emptyjeśli jest typu System.String, ale musisz przypisać, 0jeśli jest to System.Int16(pole logiczne) lub System.Decimal.
vapcguy

13

Jeśli próbujesz załadować DataTable, użyj SqlDataAdapterzamiast tego:

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);
}

Nie musisz nawet definiować kolumn. Po prostu stwórz DataTablei Fillto.

Oto parametry cStringpołączenia i sqlpolecenie procedury składowanej.


1
Jedynym problemem jest to, że jeśli zauważysz, że kolumna / wartość powoduje wyjątek podczas wypełniania, nie podaje żadnych szczegółów, na przykład możesz uzyskać za pomocą a SqlDataReaderi odczytać je za pomocą pętli przez pola.
vapcguy

9

Jak stwierdził Sagi w swojej odpowiedzi DataTable.Load jest dobrym rozwiązaniem. Jeśli próbujesz załadować wiele tabel z jednego czytnika, nie musisz wywoływać DataReader.NextResult. Metoda DataTable.Load również przesuwa czytnik do następnego zestawu wyników (jeśli istnieje).

// 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);
}

5

Przyjrzałem się temu również i po porównaniu metody SqlDataAdapter.Fill z funkcjami SqlDataReader.Load stwierdziłem, że metoda SqlDataAdapter.Fill jest ponad dwa razy szybsza od używanych przeze mnie zestawów wyników

Używany kod:

    [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();
    }

Wyniki:

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

W przypadku problemów z wydajnością użycie metody SqlDataAdapter.Fill jest znacznie bardziej wydajne. Więc jeśli nie chcesz strzelić sobie w stopę, użyj tego. Działa szybciej w przypadku małych i dużych zestawów danych.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.