Remplir la table de données à partir du lecteur de données

103

Je fais une chose basique en C # (MS VS2008) et j'ai une question plus sur la conception appropriée que sur le code spécifique.

Je crée un datatable, puis j'essaie de charger le datatable à partir d'un datareader (qui est basé sur une procédure stockée SQL). Ce que je me demande, c'est si le moyen le plus efficace de charger la table de données est de faire une instruction while ou s'il existe un meilleur moyen.

Pour moi, le seul inconvénient est que je dois taper manuellement les champs que je veux ajouter dans ma déclaration while, mais je ne connais pas non plus le moyen d'automatiser cela de toute façon car je ne veux pas que tous les champs du SP, il suffit de les sélectionner , mais ce n'est pas très grave à mes yeux.

J'ai inclus des extraits de code sous la totalité de ce que je fais, bien que pour moi le code lui-même ne soit pas remarquable ni même ce que je demande. Moreso s'interrogeant sur ma méthodologie, je demanderai de l'aide au code plus tard si ma stratégie est erronée / inefficace.

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
la source
Question en double: stackoverflow.com/questions/4089471/…
vapcguy

Réponses:

283

Vous pouvez charger un DataTabledirectement à partir d'un lecteur de données en utilisant la Load()méthode qui accepte un IDataReader.

var dataReader = cmd.ExecuteReader();
var dataTable = new DataTable();
dataTable.Load(dataReader);
Sagi
la source
2
Vous avez sauvé ma journée (Y)
Uzair Xlade
1
C'est ce que j'ai passé une semaine à chercher!
TheTechy
17

Veuillez vérifier le code ci-dessous. Il sera automatiquement converti en 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();
        }

    }
Sarathkumar
la source
Il existe une option simple pour charger le lecteur de données dans un datable, alors pourquoi quelqu'un utiliserait-il cela?
Abbas
@sarathkumar Bon travail .. je cherchais un tel code
SimpleGuy
@Abbas Coz, le chargement des données intégré est très lent
SimpleGuy
dt.Load(reader)aussi ne fonctionne pas toujours - j'obtiendrais ces Object reference not set to an instance of an objecterreurs embêtantes , probablement quand je ne récupère aucune ligne. Quelque chose de manuel comme celui-ci est utile. Je l' ai essayé et a dû se débarrasser de ces column.lignes dans la dtSchema foreachboucle , car il a dit qu'il était un casting illégal boolsur (bool)drow["IsUnique"]. Je n'en avais pas besoin, il suffit de récupérer les noms des colonnes pour remplir les nouvelles DataTable. Cela a réussi à m'aider à surmonter un ds.Fill(adapter)problème avec lequel je ne pouvais pas charger une grande table SELECT * FROM MyTable.
vapcguy
Une mise en garde - s'il y a des valeurs nulles dans l'une des colonnes, elles doivent être gérées ou cette fonction provoque une exception. Je dois vérifier if (!dr.IsDBNull(i))que la prochaine chose à l'intérieur de cette forboucle. Vous faites ensuite vos dataRowaffaires. Mais alors vous avez besoin d'un elsesur cela, au cas où vous trouveriez un null. Si vous le faites, vous devez déterminer le type de la colonne que vous ajoutez et attribuer la valeur nulle en conséquence (c'est-à-dire que vous pouvez attribuer String.Emptysi elle est de type System.String, mais vous devez attribuer 0si c'est System.Int16(champ booléen) ou System.Decimal.
vapcguy
13

Si vous essayez de charger un DataTable, utilisez SqlDataAdapterplutôt le:

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

Vous n'avez même pas besoin de définir les colonnes. Créez simplement le DataTableet Fillle.

Voici cStringvotre chaîne de connexion et sqlla commande de procédure stockée.

Mike Perrenoud
la source
1
Le seul problème ici est que si vous trouvez qu'une colonne / valeur provoque une exception pendant le remplissage, cela ne vous donne aucun détail, comme vous pourriez être en mesure d'utiliser a SqlDataReaderet de les lire en utilisant une boucle à travers les champs.
vapcguy
9

Comme Sagi l'a déclaré dans sa réponse, DataTable.Load est une bonne solution. Si vous essayez de charger plusieurs tables à partir d'un seul lecteur, vous n'avez pas besoin d'appeler DataReader.NextResult. La méthode DataTable.Load avance également le lecteur vers le jeu de résultats suivant (le cas échéant).

// 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);
}
autours
la source
5

J'ai également examiné cela et après avoir comparé la méthode SqlDataAdapter.Fill avec les fonctions SqlDataReader.Load, j'ai trouvé que la méthode SqlDataAdapter.Fill est plus de deux fois plus rapide avec les ensembles de résultats que j'utilise

Code utilisé:

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

Résultats:

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

Pour les problèmes de performances, l'utilisation de la méthode SqlDataAdapter.Fill est beaucoup plus efficace. Donc, à moins que vous ne vouliez vous tirer une balle dans le pied, utilisez-le. Il fonctionne plus rapidement pour les petits et grands ensembles de données.

martijn
la source