Füllen Sie die Datentabelle aus dem Datenleser

103

Ich mache eine grundlegende Sache in C # (MS VS2008) und habe eine Frage mehr zum richtigen Design als zu spezifischem Code.

Ich erstelle eine Datentabelle und versuche dann, die Datentabelle von einem Datenleser zu laden (der auf einer gespeicherten SQL-Prozedur basiert). Ich frage mich, ob der effizienteste Weg zum Laden der Datentabelle darin besteht, eine while-Anweisung auszuführen, oder ob es einen besseren Weg gibt.

Für mich ist der einzige Nachteil, dass ich die Felder, die ich in meine while-Anweisung einfügen möchte, manuell eingeben muss, aber ich weiß auch nicht, wie ich das automatisieren kann, da ich nicht möchte, dass alle Felder aus dem SP nur diejenigen auswählen , aber das ist in meinen Augen keine große Sache.

Ich habe Codefragmente unterhalb der Gesamtheit meiner Aktivitäten eingefügt, obwohl der Code selbst für mich nicht bemerkenswert ist oder auch nur das, worüber ich frage. Moreso wundert sich über meine Methodik und werde später um Code-Hilfe bitten, wenn meine Strategie falsch / ineffizient ist.

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());
}
Ryan Ward
quelle
Doppelte Frage: stackoverflow.com/questions/4089471/…
vapcguy

Antworten:

282

Sie können eine DataTabledirekt von einem Datenleser laden, indem Sie eine Load()Methode akzeptieren, die eine akzeptiert IDataReader.

var dataReader = cmd.ExecuteReader();
var dataTable = new DataTable();
dataTable.Load(dataReader);
Sagi
quelle
2
Du hast meinen Tag gerettet (Y)
Uzair Xlade
1
Das habe ich eine Woche lang gesucht!
TheTechy
17

Bitte überprüfen Sie den folgenden Code. Automatisch wird es als DataTable konvertiert

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

    }
Sarathkumar
quelle
Es gibt eine einfache Option, um Datenleser in datatable zu laden. Warum sollte dies dann jemand verwenden?
Abbas
@ Sarathkumar Gute Arbeit .. Ich suchte nach einem solchen Code
SimpleGuy
@Abbas Coz, eingebaute Daten laden ist sehr langsam
SimpleGuy
dt.Load(reader)funktioniert auch nicht immer - ich würde diese lästigen Object reference not set to an instance of an objectFehler bekommen, wahrscheinlich wenn ich keine Zeilen zurück bekomme. So etwas Handbuch ist praktisch. Ich versuchte es und hatte diese loszuwerden column.in den Linien dtSchema foreachSchleife , weil sie sagte , es wäre eine illegale Besetzung zu boolauf (bool)drow["IsUnique"]. Ich brauchte sie nicht, es reicht aus, die Spaltennamen zu erhalten, um die neuen zu DataTablefüllen. Dies hat mir geholfen, ein ds.Fill(adapter)Problem zu lösen, bei dem ich keine große Tabelle laden konnte SELECT * FROM MyTable.
Vapcguy
Eine Einschränkung: Wenn in einer der Spalten Nullwerte vorhanden sind, müssen diese behandelt werden, oder diese Funktion verursacht eine Ausnahme. Müssen if (!dr.IsDBNull(i))als nächstes in dieser forSchleife überprüfen . Dann machst du deine dataRowSachen. Aber dann brauchen Sie eine else, falls Sie eine Null finden. Wenn Sie dies tun, müssen Sie den Typ der Spalte herausfinden, die Sie hinzufügen, und die Null entsprechend zuweisen (dh Sie können zuweisen, String.Emptyob sie vom Typ ist System.String, aber Sie müssen zuweisen, 0ob sie System.Int16(Boolesches Feld) oder System.Decimal.
vapcguy
13

Wenn Sie versuchen, a zu laden DataTable, nutzen Sie SqlDataAdapterstattdessen Folgendes :

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

Sie müssen nicht einmal die Spalten definieren. Erstellen Sie einfach das DataTableund Filles.

Hier cStringist Ihre Verbindungszeichenfolge und sqlder Befehl für gespeicherte Prozeduren.

Mike Perrenoud
quelle
1
Das einzige Problem hierbei ist, dass, wenn Sie feststellen, dass eine Spalte / ein Wert während des Füllens eine Ausnahme verursacht, Sie keine Details erhalten, z. B. wenn Sie a verwenden SqlDataReaderund diese mithilfe einer Schleife durch die Felder einlesen können.
Vapcguy
9

Wie Sagi in ihrer Antwort feststellte, ist DataTable.Load eine gute Lösung. Wenn Sie versuchen, mehrere Tabellen von einem einzigen Reader zu laden, müssen Sie DataReader.NextResult nicht aufrufen. Die DataTable.Load-Methode bringt den Leser auch zur nächsten Ergebnismenge (falls vorhanden).

// 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
quelle
5

Ich habe mich auch damit befasst und nach dem Vergleich der SqlDataAdapter.Fill-Methode mit den SqlDataReader.Load-Funktionen festgestellt, dass die SqlDataAdapter.Fill-Methode mit den von mir verwendeten Ergebnismengen mehr als doppelt so schnell ist

Verwendeter Code:

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

Ergebnisse:

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

Bei Leistungsproblemen ist die Verwendung der SqlDataAdapter.Fill-Methode weitaus effizienter. Also, es sei denn, Sie möchten sich in den Fuß schießen, verwenden Sie das. Es funktioniert schneller für kleine und große Datenmengen.

martijn
quelle