20 de febrero de 2012

Uso del CommandBuilder

 En este ejemplo además utilizaré otros objetos, tales como BindingSource y BindingNavigator, asi que les dejo una explicación de cada uno con el código de ejemplo al final


CommandBuilder


Para utilizar un CommandBuilder deberan ejecutar la consulta de selección de registros con un DataAdapter ya que el comando SELECT es necesario para que el CommandBuilder sepa cómo debe crear los comandos tal como determina la ayuda de MSDN:

El requisito mínimo para que la generación automática de comandos funcione correctamente consiste en establecer la propiedad SelectCommand. El esquema de tabla que recupera la propiedad SelectCommand determina la sintaxis de las instrucciones INSERT, UPDATE y DELETE generadas automáticamente.

Para generar instrucciones SQL automáticamente para un DataAdapter, defina en primer lugar la propiedadSelectCommand del DataAdapter y, a continuación, cree un objeto CommandBuilder y especifique como argumento el DataAdapter para el que CommandBuilder generará automáticamente las instrucciones SQL.


Aquí dejo unos ejemplos de código de la misma página de MSDN

VB
' Assumes that connection is a valid SqlConnection object
' inside of a Using block.
Dim adapter As SqlDataAdapter = New SqlDataAdapter( _
  "SELECT * FROM dbo.Customers", connection)
Dim builder As SqlCommandBuilder = New SqlCommandBuilder(adapter)
builder.QuotePrefix = "["
builder.QuoteSuffix = "]"

C#
// Assumes that connection is a valid SqlConnection object
// inside of a using block.
SqlDataAdapter adapter = new SqlDataAdapter(
  "SELECT * FROM dbo.Customers", connection);
SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
builder.QuotePrefix = "[";
builder.QuoteSuffix = "]";

La consulta de selección se realiza porque el CommandBuilder funciona sobre un objeto DataAdapter para crear los comandos de Transact SQL (TSQL) que son necesarios para actualizar y para insertar registros en el origen de datos, los cuales son cuatro objetos Command. Uno para selección de registros, el SelectCommand(SC), uno para cuando se ingresó un registro en un DataTable saber que hacer en la base de datos, el InsertCommand (IC), uno para cuando se modificó un registro en el DataTable, el UpdateCommand (UC) y uno para cuando el registro fue eliminado del DataTable, el DeleteCommand (DC).


Además, el DataAdapter tiene otros objetos llamados los TableMapping y ColumnMapping. Estos objetos te mapean los datos de los esquemas de las bases de datos a los datos de los esquemas de los DataSets.

El DataAdapter debe tener para funcionar por lo menos el SC, este te genera los TableMappings y los ColumnMappings y te permite relacionar la consulta del SC con los datos de los DataTables. Pero cuando haces acciones sobre los DataTables y vas a actualizar la base de datos debes tener los otros tres objetos Command establecidos. El problema es que en este caso las sentencias se complican un poco por que debe saber exáctamente como iba el registro originalmente y como está actualmente. Es decir, debes por así decirlo, crear un mapeo hacia atrás con las sentencias IC, UC y DC.

El CommandBuilder permite que una vez tengas una sentencia SC, que se haga sobre una sola tabla y tenga dentro de los campos selecionados los campos de llave primaria y los campos que no tienen admiten null, puedas generar automáticamente las demás sentencias IC, UC y DC.


El código es más o menos así:

[C#]
SqlConnection cn = new SqlConnection("");
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Tabla", cn);
// Crear CommandBuilder. Al pasar como parámetro el
// DataAdapter toma el SC y genera los demás comandos
SqlCommandBuilder cb = new SqlCommandBuilder(da);
da.InsertCommand = cb.GetInsertCommand();
da.UpdateCommand = cb.GetUpdateCommand();
da.DeleteCommand = cb.GetDeleteCommand();
// Crear el DataSet y llenarlo
DataSet ds = new DataSet();
da.Fill(ds);
.
.
.
// Después de trabajar desconectado se actualizan los datos.
// El DataAdapter ya sabe como actualizarlos por que tiene los comandos IC,
UC y DC
da.Update(ds);

[VB]
' Crear conexión y DataAdapter
Dim cn As New SqlConnection("")
Dim da As New SqlDataAdapter("SELECT * FROM Tabla", cn)
' Crear CommandBuilder. Al pasar como parámetro el
' DataAdapter toma el SC y genera los demás comandos
Dim cb As New SqlCommandBuilder(da)
da.InsertCommand = cb.GetInsertCommand()
da.UpdateCommand = cb.GetUpdateCommand()
da.DeleteCommand = cb.GetDeleteCommand()
' Crear el DataSet y llenarlo
Dim ds As New DataSet()
da.Fill(ds)
.
.
.
' Después de trabajar desconectado se actualizan los datos.
' El DataAdapter ya sabe como actualizarlos por que tiene los comandos IC,
UC y DC
da.Update(ds)


Cosas sobre CommandBuilder:
  • El CommandBuilder solo es útil cuando trabajas con una sola tabla y además la tabla debe tener definida una llave primaria.
  • SelectCommand también debe devolver como mínimo una clave principal o una columna única. Si no hay ninguna, se genera una excepción InvalidOperation y no se genera ningún comando.
  • Cuando se asocia con un objeto DataAdapter, el CommandBuilder genera automáticamente las propiedadesInsertCommand, UpdateCommand y DeleteCommand del objeto DataAdapter si son referencias nulas. Si ya existe algún objeto Command para una propiedad, se utilizará el objeto Command existente.



BindingSource

Es un objeto que hace de intermediario entre el control y el conjunto de datos. Simplifica la conexión facilitando la actualización del contenido, la notificación de cambios, etc. Se incluye la navegación, ordenación, filtrado y actualización. El origen de datos subyacente se fija a través de uno de los siguientes mecanismos:         
  •        Usar el método Add para añadir un elemento al componente BindingSource
  •        Asignar a su propiedad DataSource una lista, objeto o un tipo.
Su funcionamiento permite enlazar universalmente todos los controles de formularios Windows a orígenes de datos muy diversos.

Podemos imaginarnos al objeto BindingSource como el objeto que nos permite movernos dentro de los registros existentes en el origen de datos al cual se encuentra enlazado. Tal y como se indica en la ayuda de Visual Studio, el objeto BindingSource está encapsulando el origen de datos que se ha asignado a su propiedad DataSource, normalmente se tratará de un objeto DataTable, que es el verdadero objeto que contiene los datos que han sido recuperados de la base de datos fisica.


BindingNavigator

A partir de la versión 2005, Microsoft incorporó un nuevo objeto llamado BindingNavigator el cual es un control basado en un  objeto ToolStrip,  que permite realizar funciones de navegación por el conjunto de datos. Estas funciones son: Primer registro,último, siguiente, anterior, número de registros totales en el conjunto de datos y posición actual.



MissingSchemaAction.AddWithKey de DataAdapter


El objeto DataAdapter está optimizado para los escenarios de sólo lectura de forma predeterminada. El método Fill sólo recupera la parte del esquema necesaria para llenar un objeto DataSet. Para obtener el esquema adicional necesario para actualizar o validar los objetos DataSet se debe utilizar la enumeración AddWithKey de la propiedad MissingSchemaAction del DataAdapter.

Establecer la propiedad MissingSchemaAction del DataAdapter en AddWithKey es agregar información al esquema acerca de las claves principales, los campos AutoIncrement, los campos que aceptan valores NULL y los índices únicos.



El programa


Se presentarán los datos de la tabla img en la base de datos imagenes, para esto se creara un archivo app.config para guardar información de la conexión, los datos a presentar son tres: nombre, comentarios y ruta.


<connectionStrings>
        <add name="conexion" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=imagenes;Integrated Security=True"
            providerName="System.Data.SqlClient" />
    connectionStrings>


Esta será la aplicación, con su interfaz y el código, como ven se usa el Bindingnavigator para movilizarse entre los datos obtenidos por la consulta select, pero también se logra con los botones abajo de los TextBox, lo dejé así para que ustedes seleccionen el que prefieran porque obvio que es redundante utilizar los botones teniendo el Bindingnavigator, asi que fíjense en los comentarios del evento click de cada botón aparece la manera de moverse entre registros utilizando el BindingSource o el BindingContext (ambos necesitan el bs !!!)



Imports System.Data
Imports System.Data.SqlClient
Imports System.Configuration
Public Class Form1
    Dim sql As SqlConnection
    Dim da As SqlDataAdapter
    Dim ds As New DataSet
    Dim bs As New BindingSource
    Dim dt As New DataTable
     Dim s As String = ConfigurationManager.ConnectionStrings("conexion").ConnectionString.ToString

   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        CargarDatos()
    End Sub

    Public Sub Cargardatos()

        sql = New SqlConnection(s)
        ConfigurarAdaptadorDatos()
        da.Fill(dt)
        bs.DataSource = dt

        DataGridView1.DataSource = bs
        BindingNavigator1.BindingSource = bs
        TextBox1.DataBindings.Clear()

        TextBox3.DataBindings.Clear()
        TextBox4.DataBindings.Clear()
        TextBox1.DataBindings.Add("text", bs, "nombre")
        TextBox3.DataBindings.Add("text", bs, "comentarios")
        TextBox4.DataBindings.Add("text", bs, "ruta")

    End Sub

    Private Sub ConfigurarAdaptadorDatos()

        Try
            da = New SqlDataAdapter("select * from img", sql)
            da.MissingSchemaAction = MissingSchemaAction.AddWithKey
            Dim cb As New SqlCommandBuilder(da)

            cb.QuotePrefix = "["
            cb.QuoteSuffix = "]"

            da.InsertCommand = cb.GetInsertCommand()
            da.UpdateCommand = cb.GetUpdateCommand()
            da.DeleteCommand = cb.GetDeleteCommand()

        Catch ex As Exception
            MessageBox.Show(ex.Message)

        End Try

    End Sub


    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        bs.MoveLast()
        'Me.BindingContext(bs).Position = Me.BindingContext(bs).Count - 1    aqui no se necesita el bs
    End Sub

    Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click

        If bs.Position + 1 < bs.Count Then
            bs.MoveNext()
        End If
        'Me.BindingContext(ds).Position += 1            aqui no se necesita el bs
    End Sub

    Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
        ' Me.BindingContext(ds).Position -= 1           aqui no se necesita el bs
        bs.MovePrevious()
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        bs.MoveFirst()
        'Me.BindingContext(ds).Position = 0              aqui no se necesita el bs
    End Sub

    Private Sub BNuevo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BNuevo.Click
        bs.AddNew()
        DataGridView1.Focus()
    End Sub

    Private Sub BGuardar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BGuardar.Click
        Try
            Me.Validate()
            Me.bs.EndEdit()
            Dim n As Integer = Me.da.Update(Me.dt)

            MessageBox.Show("Nº de registros afectados: " & CStr(n))
            Cargardatos()
        Catch ex As Exception
            MessageBox.Show("Update failed")

        End Try
    End Sub
End Class




lunes, febrero 20, 2012