Complete la tabla de datos del lector de datos

103

Estoy haciendo algo básico en C # (MS VS2008) y tengo una pregunta más sobre el diseño adecuado que sobre el código específico.

Estoy creando una tabla de datos y luego intento cargar la tabla de datos desde un lector de datos (que se basa en un procedimiento almacenado de SQL). Lo que me pregunto es si la forma más eficiente de cargar la tabla de datos es hacer una declaración while, o si hay una forma mejor.

Para mí, el único inconveniente es que tengo que escribir manualmente los campos que quiero agregar en mi declaración while, pero tampoco conozco la forma de automatizar eso de todos modos, ya que no quiero todos los campos del SP, solo seleccione unos , pero eso no es gran cosa en mi opinión.

He incluido fragmentos de código debajo de la totalidad de lo que hago, aunque para mí el código en sí no es notable o incluso lo que estoy preguntando. Además, preguntándome sobre mi metodología, pediré ayuda con el código más adelante si mi estrategia es incorrecta / ineficiente.

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
fuente
Pregunta duplicada: stackoverflow.com/questions/4089471/…
vapcguy

Respuestas:

283

Puede cargar un archivo DataTabledirectamente desde un lector de datos utilizando el Load()método que acepta un archivo IDataReader.

var dataReader = cmd.ExecuteReader();
var dataTable = new DataTable();
dataTable.Load(dataReader);
Sagi
fuente
2
Me salvaste el día (Y)
Uzair Xlade
1
¡Esto es lo que estuve buscando durante una semana!
TheTechy
17

Comprueba el siguiente código. Automáticamente se convertirá como 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
fuente
Hay una opción sencilla para cargar el lector de datos en la tabla de datos, entonces ¿por qué alguien usaría esto?
Abbas
@sarathkumar Buen trabajo ... estaba buscando ese código
SimpleGuy
@Abbas Coz, la carga de datos incorporada es muy lenta
SimpleGuy
dt.Load(reader)Tampoco siempre funciona: obtendría esos molestos Object reference not set to an instance of an objecterrores, probablemente cuando no obtengo ninguna fila. Algo manual como este es útil. Lo probé y tuve que deshacerme de esas column.líneas en el dtSchema foreachbucle porque decía que era un lanzamiento ilegal parabool en (bool)drow["IsUnique"]. No los necesitaba, obtener los nombres de las columnas para completar la nueva DataTablees suficiente. Esto me ayudó a superar un ds.Fill(adapter)problema en el que no podía cargar una tabla grande con SELECT * FROM MyTable.
vapcguy
Una advertencia: si hay valores nulos en cualquiera de las columnas, deben manejarse o esta función causa una excepción. Tienes que marcar if (!dr.IsDBNull(i))como lo siguiente dentro de ese forbucle. Luego haces tus dataRowcosas. Pero luego necesitas una elsesobre eso, en caso de que encuentres un nulo. Si lo hace, debe averiguar el tipo de columna que está agregando y asignar el nulo en consecuencia (es decir, puede asignar String.Emptysi es de tipo System.String, pero debe asignar 0si es System.Int16(campo booleano) o System.Decimal.
vapcguy
13

Si está intentando cargar un DataTable, aproveche el SqlDataAdapteren su lugar:

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

Ni siquiera necesita definir las columnas. Simplemente crea el DataTabley Filleso.

Aquí cStringestá su cadena de conexión y sqlel comando del procedimiento almacenado.

Mike Perrenoud
fuente
1
El único problema aquí es que si encuentra que una columna / valor causa una excepción durante el llenado, no le brinda ningún detalle, como si pudiera usar ay SqlDataReaderleerlos usando un bucle a través de los campos.
vapcguy
9

Como dijo Sagi en su respuesta, DataTable.Load es una buena solución. Si está intentando cargar varias tablas desde un solo lector, no necesita llamar a DataReader.NextResult. El método DataTable.Load también avanza al lector al siguiente conjunto de resultados (si lo hubiera).

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

También miré en esto, y después de comparar el método SqlDataAdapter.Fill con las funciones SqlDataReader.Load, descubrí que el método SqlDataAdapter.Fill es más del doble de rápido con los conjuntos de resultados que he estado usando

Código utilizado:

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

Resultados:

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

Para problemas de rendimiento, usar el método SqlDataAdapter.Fill es mucho más eficiente. Entonces, a menos que quieras dispararte en el pie, usa eso. Funciona más rápido para conjuntos de datos grandes y pequeños.

martijn
fuente