Terabitia

28/11/07

70-528. Resumen. ADO.net

Archivado en: Sin categoría — crisfervil @ 4:48 pm

Tercera parte del resumen sobre el temario para el examen de certificación 70-528 para desarrollo web.


Clases “Desconectadas” de ADO.net

DataTable
DataColumn
DataRow
Constraint
DataView
DataSet
DataRelation

Cata DataRow posee estos estados: Detached, Added, Unchanged, Modified, Deleted.

Cada DataRow mantiene hasta cuatro versiones del mismo registro:
Default, Original, Proposed y Current.

El método HasVersion de DataRow indica la existencia de una versión en particular.

El método AcceptChanges de DataRow cambia su estado a Unchanged.

El método GetChanges DataTable devuelve otro DataTable que contiene únicamente los registros actualizados.

El método Copy de DataTable devuelve una copia de la tabla y todos sus registros. (Qué estado tienen sus DataRows? r: Added)

El método Clone de DataTable devuelve una copia de la estructura de la tabla. Se omiten los registros.

El método Import de DataTable copia los datos de otro DataTable. Se copian la versión Current y la Original. Si ya existe algún registro con la misma PK, se lanza una ContraintException. La estructura de la DataTable origen y destino deben ser iguales.

El método WriteXml de DataTable serializa toda la tabla a un archivo Xml.

La propiedad ColumnMapping de DataRow indica la manera en que deben serializarse los valores de esa columna en un archivo XML. Puede ser Attribute, Element, Hidden, SimpleContent

El la propiedad RowStateFilter de DataView permite seleccionar sólo aquellas filas que cumplan un criterio de estado determinado. Se puede especificar (uno o más a la vez usando el operador | para c# ó or para vb ): Added, CurrentRows, Deleted, ModifiedCurrent, ModifiedOriginal, None, OriginalRows, Unchanged.

Los objetos DataSet poseen una colección de objetos DataRelation que permiten relacionar dos tablas dentro del DataSet. Estas relaciones permiten asegurar la integridad referencial.

El método WriteXml tanto del DataTable como del DataSet permiten almacenar los valores originales y modificados de las filas especificando el valor XmlWriteMode.DiffGram en el parámetro mode.

Para generar un Xml de tipo DiffGram que incluya únicamente los registros modificados, llamar primero al método GetChanges, y a continuación a WriteXml.

El DiffGram es generalmente utilizado en aquellos entornos en los que el acceso a datos es ocasionalmente conectado y se requiere realizar sincronizaciones en el momento de la conexión. Cuando hay conexión, se obtienen los datos del servidor. A continuación, se realizan las modificaciones convenientes. Si al finalizar la aplicación no se ha vuelto a realizar una sincronización, se persisten tanto los datos originales como los modificados, para que estén disponibles cuando se vuelva a iniciar la aplicación.

Al deserializar, si necesitamos deserializar desde un DifGram, debemos indicarlo en el parámetro mode de ReadXml.

XmlReadMode puede tomar los siguientes valores: Auto, DiffGram, Fragment, IgnoreSchema, InferSchema, InferTypedSchema, ReadSchema.

La propiedad RemotingFormat tanto en objetos DataSet como DataColumn permite especificar el formato, xml o binario, que se utilizará para la serialización.

La serialización binaria permite persistir la información completa del DataSet o el DataTable, incluyendo el RowState de los DataRows.

Tanto DataSet como DataTable proporcionan el método Merge, que permite sincronizar 2 objetos DataTable o DataSet. La sincronización se realiza a través de las claves principales. El parámetro preserveChanges indica si los cambios en el objeto destino sobreescriben lo que hubiere en el objeto copiado.
El parámetro MissingSchemaAction indica lo que hacer si las estructuras de los objetos sincronizados no coinciden. Este valor puede ser: Add, AddWithPrimaryKey, Error, Ignore. Si los objetos DataTable de los que vayamos a sincronizar no tienen PKs definidas, se lanzará una excepción.

Mientras escribía este resumen, he ido haciendo pruebas en una aplicación de consola. Este es el código:

// Cómo crear e instanciar una tabla
DataTable customers = new DataTable("Customers");

// Crear e instanciar una columna
DataColumn idCol = new DataColumn("Id");
idCol.Caption = "id";
// Así establecemos la longitud máxima de la columna
idCol.MaxLength = 5;
// Así agregamos la columna a la tabla
customers.Columns.Add(idCol);

DataColumn nameCol = new DataColumn("Name");
nameCol.Caption = "Nombre";
customers.Columns.Add(nameCol);

DataColumn lastNameCol = new DataColumn("LastName");
lastNameCol.Caption = "Apellidos";
customers.Columns.Add(lastNameCol);

DataColumn bornDateColumn = new DataColumn("BornDate");
bornDateColumn.Caption = "Fecha de nacimiento";
// Así establecemos un tipo de dato distinto de string
bornDateColumn.DataType = typeof(DateTime);
customers.Columns.Add(bornDateColumn);

// Esta es una columna cuyos valores son calculados (ExpressionColumn)
DataColumn nameAndLastNameCol = new DataColumn("NameAndLastName");
nameAndLastNameCol.Caption = "Nombre y Apellidos";
nameAndLastNameCol.Expression = "Name + ' ' + LastName";
customers.Columns.Add(nameAndLastNameCol);

// Así establecemos las columnas que forman parte de la PK
customers.PrimaryKey = new DataColumn[] { idCol };

// Así agregamos datos a la tabla
// Un item del array por cada columna
customers.Rows.Add(new object[] { "001", "Cristhian", "Fernández Villalba", new DateTime(1982,2,17), null });

// Esta es otra manera de agregar datos a la tabla
DataRow newCustomer = customers.NewRow();
newCustomer[idCol] = "002"; 
newCustomer[1] = "Pepito";
newCustomer["LastName"] = "Pérez";
customers.Rows.Add(newCustomer);

// Esta es otra manera de insertar.
// Con LoadDataRow también es posible Actualizar datos.
// Lo permite el segundo parámetro
customers.LoadDataRow(new object[] { "002", "Pepito", "Pérez Pérez", new DateTime(1982,2,17), null }, LoadOption.Upsert);

// Todo lo insertado hasta ahora es lo original
customers.AcceptChanges();
// A partir de este momento todas las filas tendrán estado Unchanged

// Cambiar el estado de una fila
// Qué RowState tiene la fila 0 antes del cambio?
Console.WriteLine(customers.Rows[0].RowState); //(Unchanged)
customers.Rows[0][nameCol] = "Cristhian Baltazar";
// Qué RowState tiene la fila cero después del cambio?
Console.WriteLine(customers.Rows[0].RowState); // (Modified)

// Si copio todos los datos a otra DataTable, con qué
// RowState se copiarán las filas modificadas?
DataTable customersCopy = customers.Copy();
// Miramos el RowState de la fila modificada
Console.WriteLine(customersCopy.Rows[0].RowState); // Modified
// Existe una versión Default para la fila cero? // True
Console.WriteLine(customersCopy.Rows[0].HasVersion(DataRowVersion.Default));
// Existe una versión Original para la fila cero? // True
Console.WriteLine(customersCopy.Rows[0].HasVersion(DataRowVersion.Original));
// Existe una versión Proposed para la fila cero? // False
Console.WriteLine(customersCopy.Rows[0].HasVersion(DataRowVersion.Proposed));
// Existe una versión Current para la fila cero? // True
Console.WriteLine(customersCopy.Rows[0].HasVersion(DataRowVersion.Current));

// Y cómo se serializan las versiones de las filas
customersCopy.WriteXml("Customers.xml");
// Al abrir el xml generado no existen versiones.

// Y al deserializar, en qué estado queda la fila cero?
// Ojo, como ReadXml carga registros, hay que eliminar los
// que existen, sino, se lanza una ConstraintException
customersCopy.Rows.Clear();
customersCopy.ReadXml("Customers.xml");
Console.WriteLine(customersCopy.Rows[0].RowState); // Added

// Volvemos a serializar. Esta vez incluyendo el esquema
customersCopy.WriteXml("Customers.xml", XmlWriteMode.WriteSchema);

// Agregamos algún dato más
customers.Rows.Add(new object[] { "003", "Jorge", "Jiménez", new DateTime(2000,6,6), null });

// Generamos una vista del DataTable
DataView olderCustomers = new DataView(customers);
// Establecemos un filtro
olderCustomers.RowFilter = "BornDate < '01/01/1990'";
// Y un criterio de ordenación
olderCustomers.Sort = "Name DESC";

// Podemos especificar un filtro de estado
olderCustomers.RowStateFilter = DataViewRowState.Added | DataViewRowState.Deleted;

// Así creamos un DataSet
DataSet dsPepitoSL = new DataSet("PepitoSL");

// Así le agregamos Tablas
dsPepitoSL.Tables.Add(customers);

// Creamos otra tabla
DataTable invoices = new DataTable("Facturas");

DataColumn invoiceIdCol = new DataColumn("IvoiceId",typeof(Guid));
invoices.Columns.Add(invoiceIdCol);

DataColumn customerIdCol = new DataColumn("CustomerId");
customerIdCol.MaxLength = 5;
invoices.Columns.Add(customerIdCol);

DataColumn amountCol = new DataColumn("Amount", typeof(Single));
invoices.Columns.Add(amountCol);

dsPepitoSL.Tables.Add(invoices);

// Así crearmos una relación entre dos tablas
DataRelation relCustomersInvoices = new DataRelation("CustomersInvoices", idCol, customerIdCol);

// Así la agregamos al DataSet
dsPepitoSL.Relations.Add(relCustomersInvoices);

// Serializamos el DataSet
// Se omitirán en la serialización los valores originales
// (Si la fila no ha cambiado se serializa la fial original)
dsPepitoSL.WriteXml("PepitoSL.xml");

// Si necesitamos serializar valores originales y modificados
dsPepitoSL.WriteXml("PepitoSL.xml", XmlWriteMode.DiffGram);

// Como estarán las filas modificadas si deserializamos en este momento?
dsPepitoSL.ReadXml("PepitoSL.xml", XmlReadMode.DiffGram);
Console.WriteLine(dsPepitoSL.Tables["Customers"].Rows[0].RowState); // Modified

// Serializamos el DataSet en binario
dsPepitoSL.RemotingFormat = SerializationFormat.Binary;
using (FileStream fs = new FileStream("PepitoSL.bin", FileMode.Create))
{
    BinaryFormatter bf = new BinaryFormatter();
    bf.Serialize(fs, dsPepitoSL);
}

// Al deserializar lo serializado en binario, cómo están
// los RowStates de las filas modificadas?
using(FileStream fs = new FileStream("PepitoSL.bin", FileMode.Open))
{
    BinaryFormatter bf = new BinaryFormatter();
    dsPepitoSL = (DataSet)bf.Deserialize(fs);
}
Console.WriteLine(dsPepitoSL.Tables["Customers"].Rows[0].RowState); // Modified !!

// Y cómo sincronizaría 2 DataSets?
// Copiamos solo la estructura...
DataSet disconnectedPepitoSL = dsPepitoSL.Clone();
// y a continuación sincronizamos...
disconnectedPepitoSL.Merge(dsPepitoSL);
// vemos qué tiene el DataSet desconectado...

 

DataSet tipados: Para agregar un DataSet tipado a la solución, menú Project/Add new item/DataSet y sobre la superficie del diseñador, arrastrar las tablas desde el explorador de servidores.

Clases “Conectadas” de ADO.net

DbConnection
DbCommand
DbDataAdapter
DbProviderFactory
DbProviderFactories

Proveedores de datos que vienen de serie en el .net Framework

OleDb (System.Data.OleDb)
Odbc (System.Data.Odbc)
SQL Server (System.Data.SqlClient)
Oracle (System.Data.OracleClient)

También existen aparte de estas, y fuera del .net Framework, proveedores de terceros que pueden ser descargados y utilizados, para db2, mySQL, etc.

Interfaces, clases base e implementaciones

Interface Clase Base
(System.Data.Common)
Clases Concretas
IDbConnection DbConnection SqlConnection
OracleConnection
OleDbConnection
OdbcConnection
IDbCommand DbCommand SqlCommand
OracleCommand
OleDbCommand
OdbcCommand
IDataReader/IDataRecord DbDataReader SqlDataReader
OracleDataReader
OleDbDataReader
OdbcDataReader
IDbTransaction DbTransaction SqlTransaction
OracleTransaction
OleDbTransaction
OdbcTransaction
IDbDataParameter DbParameter SqlParameter
OracleParameter
OleDbParameter
OdbcParameter
IDataParameterCollection DbParameterCollection SqlParameterCollection
OracleParameterCollection
OleDbParameterCollection
OdbcParameterCollection
IDbDataAdapter DbDataAdapter SqlDataAdapter
OracleParameter
OleDbDataAdapter
OdbcDataAdapter
  DbCommandBuilder SqlCommandBuilder
OracleCommandBuilder
OleDbCommandBuilder
OdbcCommandBuilder
  DbConnectionStringBuilder SqlConnectionStringBuilder
OracleConnectionStringBuilder
OleDbConnectionStringBuilder
OdbcConnectionStringBuilder
  DbDataPermission SqlPermission
OraclePermission
OleDbPermission
OdbcPermission

 

La gestión de la pool de conexiones se realiza mediante la cadena de conexión. Los parámetros que se pueden especificar son:

(http://msdn2.microsoft.com/es-es/library/system.data.sqlclient.sqlconnection.connectionstring(VS.80).aspx)

Connection Timeout: en segundos. Por defecto 15 segundos.

Min Pool Size: Por defecto 5. Es recomendable usar un valor no demasiado alto, cercano al valor por defecto.

Max Pool Size: Por defecto 100.

Pooling: Indica si se debe activar o no la pool de conexiones. Por defecto es true.

Connection Reset: Indica que la conexión debe ser reseteada cuando se extraiga de la pool. Por defecto es true.

Load Balancing Timeout, Connection Lifetime: El tiempo máximo en segundo que la conexión puede existir en la pool.  Este valor es evaluado en el momento en que la conexión vuelve a la pool. Este parámetro es muy útil a la hora de conectarse a un cluster con balanceador de carga, pues obliga a que cada cierto tiempo la conexión se destruya, y vuelva a crearse, usando el servidor activo en ese momento. Por defecto es cero.

Enlist: true indica que el agrupador de conexión da de alta automáticamente la conexión en el contexto de transacción actual del subproceso de creación (ein?? me he quedado igual). Creo que significa que la conexión se crea teniendo en cuenta si se está trabajando con transacciones o no. Por defecto true.

Reglas a tener en cuenta en la gestión de pool de conexiones:

La cadena de conexión debe ser EXACTAMENTE la misma.

El usuario debe ser el mismo para los que accedan a la pool. Esto aplica aunque el acceso se realice mediante autenticación windows. (entonces, si tengo una aplicación web, en la que el acceso se realiza mediante autenticación windows, cada usuario tiene un id distinto, la cadena de conexión es la misma, no puedo tener una pool única en toda la aplicación?)

El Id de proceso debe ser el mismo. No es posible compartir pool de conexiones entre procesos.

Encriptar configuración

Para encriptar el web.config, usar el comando aspnet_regiis de la línea de comandos de visual studio.

aspnet_regiis -pef “connectionStrings” “C:\…\EncryptWebSite”

Para desencriptarlo

aspnet_regiis -pdf “connectionStrings” “C:\…\EncryptWebSite”

 

SQL: Structured Query Languaje.

DML: Data Manipulation Languaje. Para la manipulación de datos. Selectet, Insert, Delete, Update, etc.

DDL: Data Definition Languaje. Para operar sobre los objetos de la base de datos. Crear tablas, etc.

DCL: Data Control Languaje. Para operar sobre el acceso a los datos. Asignar, denegar permisos, etc.

El objeto DbConnection expone el método CreateCommand, que es el que se recomienda sea usado para la creación de objetos DbCommand. De este modo se genera un código más desacoplado.

Del mismo modo, DbCommand expone el método Parameters.Add, que es el que debería usarse para crear comandos adecuados para él.

SQL Provider admite que los parámetros establecidos en la colección Parameters de DbCommand sigan cualquier orden, siempre que los nombres coincidan con los definidos en el procedimiento almacenado.

En cambio, OleDb Provider OBLIGA a que el orden sea el mismo al definido en el procedimiento almacenado.

DbCommand expone el método ExecuteNonQuery que debe ser usado cuando el comando a ejecutar no devolverá resultados. Este método sólo devuelve el número de filas afectadas.

ExecuteScalar el emétodo de DbCommand para ejecutar comandos en los que se espera un único resultado. Como un total, un valor, etc.

ExecuteReader en cambio debe ser usado para las situaciones en las que el comando devuelve un conjunto de registros. Este comando devuelve un DbDataReader.

DbDataReader es un cursor de servidor, de sólo lectura y de solo avance (forward-only).

MARS: Multiple Active Resulsets. Es una característica que permite tener más de un DbDataReader abierto a la vez. Esta se debe habilitar en la cadena de conexión, mediante la clave MultipleActiveResultSets=True. Habilitar esta característica tiene un impacto negativo en el rendimiento, así que de ser posible, debe ser evitado.

SqlBulkCopy. Esta  clase proporciona un método de alto rendimiento para la copia masiva de registros en una base de datos SQL Server. Este método es WriteToServer.

DbDataAdapter es la clase que permite obtener y actualizar datos entre un origen de datos y un DataTable.
DbDataAdapter necesita que le especifiquemos un SelectCommand, InsertCommand, UpdateCommand y DeleteCommand (todos de tipo DbCommand) que son los que usará para realizar el mantenimiento y la extracción de los datos.

El método Fill de DbDataAdapter es el que se encarga de la operación de consulta de los datos desde el origen al DataTable.
El método Update realiza las demás operacíones.
UpdateBachSize permite indicar al DbDataAdapter la manera en la que deberá gestionar el número de actualizaciones que enviará al servidor en cada llamada. Un valor 0, indica que intentará enviar el mayor número de actualizaciones cada vez. Un valor superior, indica el número de actualizaciones que se deberá enviar cada vez.

DbProviderFactory (System.Data.Common) . Clase base que permite la implementación de factorías para la creación de Clases específicas de acceso a datos. Las implementaciones de DbProviderFactory, al menos las que vienen de fábrica, suelen implementarse como Singleton, para mantener una única instancia.

La clase DbProviderFactories permite obtener la lista de todas las DbProviderFactory registradas y disponibles. Las DbProviderFactory se registran en %WINDIR%\Microsoft.NET\Framework\\v2.0.50727\CONFIG\machine.config. El método para obtenerlas es GetFactoryClasses.

Curiosidad:
En machine.config, las DbProviderFactory se registran mediante el elemento <add>, y esta configuración está disponible para todas las aplicaciones. Si necesitamos que una aplicación no permita un elemento dado, usaremos el elemento remove, de la forma <remove invariant=”System.Data.Odbc” />  y si necesitamos eliminar todos, <clear/>.

DbProviderFactory proporciona la propiedad CanCreateDataSourceEnumerator que nos indica si la factory admite la llamada a CreateDataSourceEnumerator. Este método, devuelve una instancia del correspondiente DbDataSourceEnumerator.

DbDataSourceEnumerator permite obtener los orígenes de datos disponible para un determinado Provider. La obtención se realiza mediante el método GetDataSources() que devuelve un DataTable con la información.

DbException es la clase de la que heredan todas las excepciones que se generan en las clases de Ado.net, con lo cual, en el código de acceso a datos de tipo genérico, bastará con capturar las excepciones de este tipo. Se puede utilizar la propiedad Data de la excepción para obtener información adicional, o bien, la propiedad Message.

SqlConnection posee el evento InfoMessage que puede ser utilizado para obtener información general y sobre errores en la conexión. El delegado de este evento admite recibirá un parámetro de tipo SqlInfoMessageEventArgs que expone la propiedad Message y Errors, que es una colección de SqlError.

SqlError (System.Data.SqlClient) expone las propiedades Class, LineNumber, Message, Number, Procedure, Server, Source y State.

Y.. cómo quedaría el código de acceso a datos para una aplicación “Provider Idependent”?? pues más o menos así:

// Obtener del archivo de configuraciones el atributo ProviderName de la 
// entrada con name="Main". Es necesario referenciar System.Configuration.dll
string providerName = ConfigurationManager.ConnectionStrings["Main"].ProviderName;
// Buscar un DbPRoviderFactory para el Provider Especificado
DbProviderFactory dbf = DbProviderFactories.GetFactory(providerName);
// Si no hay provider, no podemos hacer nada...
if (dbf == null) throw new Exception("Provider especificado en configuración no encontrado.");
// Intentar instanciar la conexión mediante el provider
using (DbConnection cnn = dbf.CreateConnection())
{
    // Aplicar la cadena de conexión especificada en el config
    cnn.ConnectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString;
    cnn.Open();
    // La conexión nos servirá para crear un DbCommand adecuado
    using (DbCommand cmd = cnn.CreateCommand())
    {
        // Configurar el comando...
        cmd.CommandText = "SELECT * FROM CUSTOMERS";
        // Volvemos a tirar de la ProviderFactory para crear un DataAdapter
        using (DbDataAdapter da = dbf.CreateDataAdapter())
        {
            // Configuramos el Adapter y cargamos un DataTable con él
            da.SelectCommand = cmd;
            DataTable customersTable = new DataTable("Customers");
            da.Fill(customersTable);
        }
    }
}

Este código requiere un App.config que tenga definida la sección connectionStrings:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
    </configSections>
    <connectionStrings>
        <add name="Main"
            connectionString="Data Source=XXXX;Initial Catalog=Customers;Integrated Security=True"
            providerName="System.Data.SqlClient" />
    </connectionStrings>
</configuration>

 

Transacciones

El método BeginTransaction de DbConnection inicia una transacción y devuelve una referencia a DbTransaction.

La instancia de DbTransaction devuelta debe asignarse a la propiedad Transaction del DbCommand para que el comando se ejecute en el contexto de dicha transacción.

Cómo debería estructurarse el código de acceso a datos para ejecutar un comando en el contexto de una transacción de una manera segura? más o menos así:

// GetConnection() se encarga de instanciar e inicializar la conexión
using (System.Data.Common.DbConnection cnn = GetConnection())
{
    using (System.Data.Common.DbTransaction trans = cnn.BeginTransaction())
    {
        using (System.Data.Common.DbCommand dc = cnn.CreateCommand())
        {
            try
            {
                // Configurar command
                // ...
                dc.Transaction = trans;
                // Ejecutar
                dc.ExecuteNonQuery();
                // Commit
                trans.Commit();
            }
            catch 
            {
                // RollBack
                trans.Rollback();
                throw;
            }
        }
    }

La ejecución asíncrona de comandos es una característica que viene de fábrica para el SqlProvider.

Para ejecutar comandos de manera asíncrona, es necesario que la cadena de conexión tenga establecida las claves Asynchronous Processing=true y async=true. De lo contrario se lanzará una excepción.

La clase SqlCommand expone los métodos BeginExecuteNonQuery, BeginExecuteReader que inician la ejecución asíncrona de un comando.

Para determinar el fin de la ejecución del comando, se debe recoger el valor IAsyncResult devuelto en el inicio de la ejecución. También se puede suscribir al evento StatementCompleted.

Más sobre ejecución asíncrona en: http://msdn2.microsoft.com/en-us/library/ms379553(VS.80).aspx.

Para la obtención de valores de columnas BLOB (Bynary Large Object) debe procederse igual que con columnas normales. Es decir, la conexión, el command y el dataReader se instancian y se inicializan de la misma manera. El tratamiento especial se aplicará en el momento de llamar al ExecuteReader, ya que debemos especificar el parámetro CommandBehavior.SequentialAccess. Para acceder a la columna que contiene el dato BLOB, el dataReader expone el método GetBytes(), en el que especificaremos el índice de la columna que necesitamos leer, el número de bytes y un array de bytes que será el buffer en el que se copiarán los datos. 

string providerName = ConfigurationManager.ConnectionStrings["Main"].ProviderName;
DbProviderFactory dbf = DbProviderFactories.GetFactory(providerName);
using (DbConnection cnn = dbf.CreateConnection())
{
    cnn.ConnectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString;
    cnn.Open();
    using (DbCommand cmd = cnn.CreateCommand())
    {
        cmd.CommandText = "SELECT DocumentID, FileExtension, Document FROM Production.Document";
        // Hasta este punto, la codificación es exactamente la misma que para cualquier otro caso
        // La primera diferencia viene en la siguiente línea: 
        // No olvidar especificar el parámetro CommandBehavior.SequentialAccess
        using (DbDataReader dr = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
        {
            while (dr.Read())
            {
                string documentId = Convert.ToString(dr["DocumentId"]);
                string fileExtension = (string)dr["FileExtension"];
                string fileName = string.Format("..\\..\\Documents\\{0}{1}", documentId, fileExtension);
                // escribir lo que haya en la columna con índice 2
                // en el archivo fileName
                SaveBlob(dr, 2, fileName);
            }
        }
    }
}

Para mantener apartadas las “complejidades” (tampoco es que sea demasiado complejo) hemos amontonado el código en la función SaveBlob().

private void SaveBlob(DbDataReader dr, int colIndex, string fileName)
{
    const int chunkSize = 1024; // (1Kb) número de bytes a leer en cada petición
    byte[] buffer = new byte[chunkSize];
    int pos = 0; // posición actual dentro del BLOB
    int readed = 0; // número de bytes leídos en el último acceso.

    using (FileStream fs = new FileStream(fileName, FileMode.Create))
    {
        // realizamos una primera lectura
        readed = (int)dr.GetBytes(colIndex, 0, buffer, 0, chunkSize);
        while (readed != 0) // mientras hayamos leído algo...
        {
            // escribimos lo leído én el archivo
            fs.Write(buffer, 0, readed);
            // indicamos la posición de inicio en la próxima lectura
            pos += readed;
            // Realizamos una nueva lectura.
            readed = (int)dr.GetBytes(colIndex, pos, buffer, 0, chunkSize);
        }
        // cerramos el archivo
        fs.Close();
    }
}

Para actualizar, procedemos de la misma manera. Hay que definir un parámetro del tipo adecuado, y al establecer su valor, asignar un array de bytes, que será el buffer usado en la lectura de BLOB.

string providerName = ConfigurationManager.ConnectionStrings["Main"].ProviderName;
DbProviderFactory dbf = DbProviderFactories.GetFactory(providerName);
using (DbConnection cnn = dbf.CreateConnection())
{
    cnn.ConnectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString;
    cnn.Open();
    using (DbCommand cmd = cnn.CreateCommand())
    {
        // Generamos la consulta SQL con parámetros
        // También podríamos usar un Procedimiento Almacenado
        cmd.CommandText = "UPDATE Production.Document SET Document=@DocumentContent WHERE DocumentID=@DocumentId";
        // Creamos el objeto que contendrá información sobre el primer parámetro
        DbParameter IdParam = cmd.CreateParameter();
        IdParam.ParameterName = "@DocumentId";
        IdParam.DbType = DbType.Int16;
        IdParam.Direction = ParameterDirection.Input;
        cmd.Parameters.Add(IdParam);
        // Creamos el objeto que contendrá información sobre el segundo parámetro
        DbParameter documentParameter = cmd.CreateParameter();
        documentParameter.ParameterName = "@DocumentContent";
        documentParameter.DbType = DbType.Binary;
        documentParameter.Direction = ParameterDirection.Input;
        cmd.Parameters.Add(documentParameter);
        // Establecemos los valores de los parámetros
        IdParam.Value = 1;
        string fileName = string.Format("..\\..\\Documents\\{0}{1}", 1, ".doc");
        // La función devuelve el fichero en forma de array de bytes
        documentParameter.Value = GetBlob(fileName);
        // Ejecutar la consulta
        cmd.ExecuteNonQuery();
    }
}

La funcion GetBlob simplemente devuelve el fichero en forma de array de bytes:

private static byte[] GetBlob(string fileName)
{
    using (FileStream fs = new FileStream(fileName, FileMode.Open,  FileAccess.Read))
    {
        const int chunkSize = 1024;
        byte[] buffer = new byte[chunkSize];
        // Stream que usaremos para ir guardando el fichero en memoria
        MemoryStream outStream = new MemoryStream();
        // realizamos una primera lectura
        int readed = fs.Read(buffer, 0, chunkSize);
        while (readed != 0) // si hemos conseguido leer algo...
        {
            // guardamos lo leído en stream de salida
            outStream.Write(buffer, 0, readed);
            // volvemos a intentar una nueva lectura...
            readed = fs.Read(buffer, 0, chunkSize);
        }
        // cerramos el stream del fichero
        fs.Close();
        // devolvemos lo que haya en el stream de salida
        return outStream.ToArray();
    }
}

Crossposted from crisfervil.com

About these ads

Dejar un comentario »

Aún no hay comentarios.

RSS feed para los comentarios de esta entrada. TrackBack URI

Deja un comentario

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

El tema Shocking Blue Green Blog de WordPress.com.

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

%d personas les gusta esto: