Mostrando entradas con la etiqueta C#. Mostrar todas las entradas
Mostrando entradas con la etiqueta C#. Mostrar todas las entradas

martes, 12 de enero de 2010

Getting SQL Servers instances and databases’ names with .NET (VB.NET and C#)

Versión en castellano

This is an example of a form that show SQL Server data sources (some like the “Add Connection” in Visual Studio).

Create a new Windows Forms project, set name “MyConnections”, and create a new form frmConnections. In this form add 2 comboboxes: cmbServers and cmbDataBases (yes, I’m working a lot the names :P).

Add this code to the form:

VB.NET


Imports System.Data
Imports System.Data.Sql
Imports System.Data.SqlClient

Public Class frmConnections
Private servers As SqlDataSourceEnumerator
Private tableServers As DataTable
Private server As String


Public Sub New()
InitializeComponent()
servers = SqlDataSourceEnumerator.Instance
tableServers = New DataTable()
End Sub

End Class


C#

using System;
using System.Windows.Forms;
using System.Data;
using System.Data.Sql;
using System.Collections.Generic;

namespace MyConnections
{
public partial class frmConnections: Form
{
SqlDataSourceEnumerator servers;
System.Data.DataTable tableServers;
String server;

public frmConnections ()
{
InitializeComponent();
servers = SqlDataSourceEnumerator.Instance;
tableServers = new DataTable();
}
}
}

To get a list of available instances of SQL Server use the SqlDataSourceEnumerator.Instance object, namely the getDataSource() method that returns a DataTable with the characteristics of instances: server name, instance name, if it is part of a cluster and its version ("8.00.x" for SQL Server 2000 and "9.00.x" for SQL Server 2005).

Now, the cmbServers click event:

VB.NET

Private Sub cmbServers_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmbServers.Click


' Check if datatable is empty
If tableServers.Rows.Count = 0 Then

' Get a datatable with info about SQL Server 2000 and 2005 instances
tableServers = servers.GetDataSources()

' List that will be combobox’s datasource
Dim listServers As List(Of String) = New List(Of String)

' For each element in the datatable add a new element in the list
For Each rowServer As DataRow In tableServers.Rows

' SQL Server instance could have instace name or only server name,
' check this for show the name
If String.IsNullOrEmpty(rowServer ("InstanceName").ToString()) Then
listServers.Add(rowServer ("ServerName").ToString())
Else
listServers.Add(rowServer ("ServerName") & "\\" & rowServer ("InstanceName"))
End If

Next

'Set servers list to combobox’s datasource
Me.cmbServers.DataSource = listServers
End If

End Sub


C#

private void cmbServers_Click(object sender, EventArgs e)
{

// Check if datatable is empty
if (tableServers.Rows.Count == 0)
{
// Get a datatable with info about SQL Server 2000 and 2005 instances
tableServers = servers.GetDataSources();


// List that will be combobox’s datasource
List listServers = new List();


// For each element in the datatable add a new element in the list
foreach (DataRow rowServer in tableServers.Rows)
{

// Server instance could have instace name or only server name,
// check this for show the name
if (String.IsNullOrEmpty(rowServidor["InstanceName"].ToString()))
listServers.Add(rowServer ["ServerName"].ToString());
else
listServers.Add(rowServer["ServerName"] + "\\" + rowServer["InstanceName"]);
}

// Set servers list to combobox’s datasource
this.cmbServers.DataSource = listServers;
}
}



For the combobox with the databases list we must execute a query againts sys.databases view:

VB.NET

Private Sub cmbDataBases_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmbDataBases.Click

Dim listDataBases As List(Of String) = New List(Of String)
Dim connectString As String
Dim selectSQL As String

' Check if user was selected a server to connect
If Me.cmbServers.Text = "" Then
MsgBox("Must select a server")
Return
End If

server = Me.cmbServers.Text

'Set connection string with selected server and integrated security
connectString = "Data Source=" & server & " ;Integrated Security=True;Initial Catalog=master"


Using con As New SqlConnection(connectString)

' Open connection
con.Open()

'Get databases names in server in a datareader
selectSQL = "select name from sys.databases;"

Dim com As SqlCommand = New SqlCommand(selectSQL, con)
Dim dr As SqlDataReader = com.ExecuteReader()


While (dr.Read())
listDataBases.Add(dr(0).ToString())
End While

'Set databases list as combobox’s datasource
Me.cmbDataBases.DataSource = listaBasesDatos

End Using

End Sub


C#

private void cmbBasesdeDatos_Click(object sender, EventArgs e)
{
string select;
string connectString;
List listDataBases = new List();

// Check if user was selected a server to connect
if (this.cmbServers.Text == "")
{
MessageBox.Show("Must select a server");
Return;
}

this.server = this.cmbServers.Text;

// Set connection string with selected server and integrated security
connectString = "Data Source=" + this.server + ";Integrated Security=True;Initial Catalog=master";

using (System.Data.SqlClient.SqlConnection con = new System.Data.SqlClient.SqlConnection(connectString))
{
// Open connection
con.Open();

//Get databases names in server in a datareader
select = "select name from sys.databases;";

System.Data.SqlClient.SqlCommand com =
new System.Data.SqlClient.SqlCommand(select, con);
System.Data.SqlClient.SqlDataReader dr = com.ExecuteReader();

while (dr.Read())
{
listDataBases.Add(dr[0].ToString());
}

// Set databases list as combobox’s datasource
this.cmbDataBases.DataSource = listDataBases;
}
}


And that’s all, we have a form that show SQL Server instances and theirs databases.


Note: if you don’t use integrated security, must change connection string to add user and password attributes.

Happy codding ;)

lunes, 19 de enero de 2009

He leído: C# 3.0 Coobook de Jay Hilyard y Stephen Teilhet


Título: C# 3.0 CookBook
Autores: Jay Hilyard y Stephen Teilhet
Editorial: O'Reilly
Idioma: Inglés
ISBN-10: 059651610X
ISBN-13: 978-0596516109
Páginas: 886
En la web de O’reilly
En Amazon

Me gustó: este no es un libro de programación al uso, no habla sobre el CLR, la orientación a objetos y los ensamblados, sino que es un libro de “recetas”, sí, recetas como las de cocina de toda la vida. Por ejemplo, pongamos que tenemos que enviar ficheros por FTP usando C# y no tenemos ni idea, pues lo buscamos en el libro y con un poquito de suerte lo tendremos explicado, con su ejemplo de código y en muchos casos, diferentes formas de hacerlo.


Alguno diréis, “pues si no sé hacer algo lo busco en Internet”, y posiblemente lo encontréis mejor o peor explicado pero lo encontraréis. Así pues, ¿qué ofrece este libro? Básicamente que está muy bien explicado, abarca muchos temas y está muy bien organizado. Además, siempre te puede salvar de un problema si te quedas sin conexión :P


No me gustó: últimamente estoy teniendo mucha suerte con los libros que compro porque no les suelo encontrar muchas pegas, y eso cuando te gastas 40 o 50 € en algo se agradece y mucho :) A este libro tampoco le puedo poner muchos peros, igual podrían profundizar más en las explicaciones, pero creo que se perdería la esencia del libro.


Le falta: tratar temas como WCF (Windows Communication Foundation) y WF (Windows Workflow Foundation), pero tampoco es algo muy habitual y hay otros libros que los tratan en profundidad.


Lo recomiendo para: tenerlo siempre a mano porque nos puede sacar de más de un apuro. Pero ojo, no es un libro para aprender, es más para desarrolladores con una cierta experiencia que necesitan una solución para un problema puntual.

miércoles, 14 de enero de 2009

He leído: Pro C# 2008 and the .NET 3.5 Platform de Andew Troelsen


Título: Pro C# 2008 and the .NET 3.5 Platform
Autor: Andrew Troelsen
Editorial: Apress
ISBN10: 1-59059-884-9
ISBN13: 978-1-59059-884-9
Páginas: 1370


En la web de Apress
En Amazon


Lo cierto es que no lo he leído en su totalidad, pero es que son más de 1300 páginas y 4 apéndices extra descargables desde la web de la editorial... :S Pero sí que leí las partes que más me interesaban y creo que ya tengo suficientes elementos de juicio para valorar este libro.

Me gustó: Este sí que es un libro realmente completo, abarca prácticamente todo lo que un desarrollador tiene que saber sobre .NET, C# y las novedades que introduce el Framework 3.5 (LINQ, expresiones lambda, tipos anónimos, novedades en WCF, WF, WPF, ASP.NET ...). Además está todo muy bien explicado, con multitud de ejemplos (también se pueden descargar los proyectos de ejemplo de la web de Apress) e imágenes que ayudan a la comprensión.


El tono del libro es bastante ameno y está bien organizado. Antes de cada capítulo el autor hace una pequeña introducción de lo que se tratará, por lo que si no te interesa puedes pasar directamente al siguiente. Y también hay un resumen al final, que está muy bien para aclarar los conceptos del capítulo.

Para que os hagáis una idea de lo completo que es este libro, tiene 2 apéndices, uno sobre interoperabilidad con componentes COM y el otro sobre MONO :o

No me gustó: Ufff, es difícil encontrarle una pega a este libro, está realmente bien y es muy completo ... por decir algo, que es un tocho de más de 1300 páginas y pesa un montón :P Pero bueno, siempre viene bien para hacer un poco de brazo en la oficina o en casa :D

Le falta: que alguien lo traduzca al castellano, porque leerlo en inglés es un poquito duro, pero bueno, ya estamos acostumbrados y el autor no se hace pesado en ningún momento.

Lo recomiendo para: todo el que quiera profundizar en el desarrollo de aplicaciones en .NET con C# y el Framework 3.5, es un gran libro tanto de aprendizaje como para tenerlo de referencia (además, fardas un montón con eso del “Pro C#” :P )

lunes, 12 de enero de 2009

Obtener instancias de SQL Server y bases de datos disponibles desde .NET (C#)

Por si alguna vez se os ha planteado la necesidad de mostrar un formulario con los orígenes de datos de SQL Server disponibles (algo como el formulario Agregar conexión de Visual Studio) voy a plantear un pequeño ejemplo.

Creamos un nuevo proyecto de Windows Forms al que llamaremos Conexiones, y en él un formulario frmConexiones en el que añadimos 2 combobox: cmbServidores y cmbBasesdeDatos (esta vez me estoy currando los nombres, no os quejaréis :P).

En el código del formulario añadimos lo siguiente:




using System;
using System.Windows.Forms;
using System.Data;
using System.Data.Sql;
using System.Collections.Generic;

namespace Conexiones
{
public partial class frmConexiones : Form
{
SqlDataSourceEnumerator servidores;
System.Data.DataTable tablaServidores;
String servidor;

public frmConexiones()
{
InitializeComponent();
servidores = SqlDataSourceEnumerator.Instance;
tablaServidores = new DataTable();
}
}
}



Ahora hay que añadir el código para el evento click de cada uno de los combobox:
Para obtener la lista de instancias de SQL Server disponibles usaremos el objeto SqlDataSourceEnumerator.Instance, concretamente el método GetDataSources() que nos devuelve un DataTable con las características de las instancias: nombre del servidor, nombre de instancia, si forma parte de un clúster y la versión (8.00.x para SQL Server 2000 y 9.00.x para SQL Server 2005):




private void cmbServidores_Click(object sender, EventArgs e)
{

// Comprobamos que no se haya cargado ya el combobox
if (tablaServidores.Rows.Count == 0)
{
// Obtenemos un dataTable con la información sobre las instancias visibles
// de SQL Server 2000 y 2005
tablaServidores = servidores.GetDataSources();


// Creamos una lista para que sea el origen de datos del combobox
List listaServidores = new List();


// Recorremos el dataTable y añadimos un valor nuevo a la lista con cada fila
foreach (DataRow rowServidor in tablaServidores.Rows)
{

// La instancia de SQL Server puede tener nombre de instancia
//o únicamente el nombre del servidor, comprobamos si hay
//nombre de instancia para mostrarlo
if (String.IsNullOrEmpty(rowServidor["InstanceName"].ToString()))
listaServidores.Add(rowServidor["ServerName"].ToString());
else
listaServidores.Add(rowServidor["ServerName"] + "\\" + rowServidor["InstanceName"]);
}

// Asignamos al origen de datos del combobox la lista con
// las instancias de servidores
this.cmbServidores.DataSource = listaServidores;
}
}



Y para el que muestra las bases de datos tenemos que ejecutar una consulta sobre la vista sys.databases:




private void cmbBasesdeDatos_Click(object sender, EventArgs e)
{
string select;
string cadenaConexion;
List listaBasesdatos = new List();

this.servidor = this.cmbServidores.Text;

// Componemos la cadena de conexión con el servidor seleccionado
// con seguridad integrada
// Si se conecta con usuario de SQL Server hay que cambiar
// la cadena de conexión
cadenaConexion = "Data Source=" + this.servidor + ";Integrated Security=True;Initial Catalog=master";

using (System.Data.SqlClient.SqlConnection con = new System.Data.SqlClient.SqlConnection(cadenaConexion))
{
// Abrimos la conexión
con.Open();

// Obtenemos los nombres de las bases de datos que haya en el servidor
// se pueden filtrar para no mostrar las bases de datos de sistema
select = "select name from sys.databases;";

// Obtenemos un dataReader con el resultado
System.Data.SqlClient.SqlCommand com =
new System.Data.SqlClient.SqlCommand(select, con);
System.Data.SqlClient.SqlDataReader dr = com.ExecuteReader();

// Recorremos el dataReader y añadimos un elemento nuevo
// por cada registro
while (dr.Read())
{
listaBasesdatos.Add(dr[0].ToString());
}

// Asignamos la lista de bases de datos como origen de datos del combobox
this.cmbBasesdeDatos.DataSource = listaBasesdatos;
}
}



Y ya está, con esto tenemos un formulario que permite seleccionar la instancia de SQL Server y la base de datos a la que nos queremos conectar :)

Si en vez de seguridad integrada usamos un usuario y password de SQL Server sólo hay que cambiar la cadena de conexión.

Happy codding ;)

PD: Si no os gusta C# tenéis el mismo código en VB.NET aquí.

jueves, 8 de enero de 2009

Imprimir el contenido de un DataGridView con PrintDocument

Igual alguna vez os habéis encontrado en la situación de querer imprimir el contenido de un DataGridView y poder presentárselos a alguien o tenerlos a mano sin necesidad de tener la aplicación en pantalla.

Está claro que hay formas muy sencillas de hacerlo, por ejemplo ese botón “Imprimir pantalla” que hay en el teclado y que mucha gente no sabe para qué sirve, crear informes con Crystal Reports u otro creador de informes, apuntarlos a mano en un papel, memorizarlos ...

Algunas de estas opciones nos mostrarán datos de más, otras de menos y otras dependerán de la cantidad de neuronas que nos queden disponibles en ese momento (bueno, y de las que cada uno traiga de fábrica :P) pero hay una opción bastante sencilla que nos puede sacar de más de un apuro: usar PrintDocument con el contenido del DataGridView.

Supongamos que tenemos un formulario con un DataGridView que se llama dataGridView1 (original, ¿a qué sí?) que tiene como origen de datos ... lo que sea, nos da igual porque lo que nos interesa es el contenido no si el origen de datos es un DataSet, un fichero XML o hemos metido los datos por código.

Bien, añadimos al formulario un objeto de tipo PrintDocument (está en el Cuadro de Herramientas en el apartado Impresión) con el nombre printDocument1 (para ser coherentes con la nomenclatura :P).

Ahora añadimos un botón (¿a qué no adivináis como se llama?) pues sí, button1 y en su evento click escribimos lo siguiente:


printDocument1.Print();



En el evento PrintPage de printDocument1 vamos a recorrer el contenido de dataGridView1 e imprimir una línea por cada fila del grid. Es muy importante saber que el evento se dispara por cada página que se vaya a imprimir, esto quiere decir que si tenemos 5 páginas el evento se disparará 5 veces y por tanto hay que saber en qué punto nos hemos quedado para continuar desde ahí y no volver a empezar.

// Variable a nivel de clase para recordar en qué punto nos hemos quedado
// la inicializamos a -1 para imprimir también las cabeceras de las columnas
private int i = -1;

private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
// La fuente que vamos a usar para imprimir.
Font printFont = new Font("Arial", 10);
float topMargin = e.MarginBounds.Top;
float yPos = 0;
float linesPerPage = 0;
int count = 0;
string texto = "" ;
DataGridViewRow row;

// Calculamos el número de líneas que caben en cada página.
linesPerPage = e.MarginBounds.Height / printFont.GetHeight(e.Graphics);


// Recorremos las filas del DataGridView hasta que llegemos
// a las líneas que nos caben en cada página o al final del grid.
while (count < linesPerPage && i < this.dataGridView1.Rows.Count)
{
row = dataGridView1.Rows[i];
texto = "";

foreach (DataGridViewCell celda in row.Cells)
{
texto += "\t" + celda.Value.ToString();
}

// Calculamos la posición en la que se escribe la línea
yPos = topMargin + (count * printFont.GetHeight(e.Graphics));

// Escribimos la línea con el objeto Graphics
e.Graphics.DrawString(texto, printFont, Brushes.Black, 10, yPos);

count++;
i++;
}

// Una vez fuera del bucle comprobamos si nos quedan más filas
// por imprimir, si quedan saldrán en la siguente página
if (i < this.dataGridView1.Rows.Count)
e.HasMorePages = true;
else
{
// si llegamos al final, se establece HasMorePages a
// false para que se acabe la impresión
e.HasMorePages = false;

// Es necesario poner el contador a 0 porque, por ejemplo si se hace
// una impresión desde PrintPreviewDialog, se vuelve disparar este
// evento como si fuese la primera vez, y si i está con el valor de la
// última fila del grid no se imprime nada
i = 0;
}

}




Con esto conseguimos imprimir todos los datos que hay en un grid, incluidas las cabeceras de las columnas, pero ¿habría forma de imprimir únicamente lo que seleccionemos en el grid?

La respuesta es sí y es bastante sencillo, simplemente hay que cambiar:


texto += "\t" + celda.Value.ToString();



por este otro:


if (celda.Selected)
{
texto += "\t" + celda.Value.ToString();
}
if (String.IsNullOrEmpty(seleccion) == false)
{
yPos = topMargin + (count * printFont.GetHeight(e.Graphics));
e.Graphics.DrawString(texto, printFont, Brushes.Black, 10, yPos);
count++;
}



Con esto sólo imprimimos las celdas que se hayan seleccionado, que pueden ser, varias filas, varias columnas o celdas sueltas, y además evitamos que se impriman filas en blanco.

Advertencia: que nadie piense que con esto vamos a sacar unos informes espectaculares con las columnas perfectamente alineadas y todo precioso, NO NO NO, esto es únicamente para hacer una impresión rápida de los valores que aparecen en un DataGridView, sin muchas florituras pero también sin muchas complicaciones.


Happy codding ;)


Bola extra: PrintDocument imprime directamente por la impresora predeterminada, para cambiar la impresiona, su configuración y la configuración del papel echarle un vistazo a PrintDialog y PageSetudDialog. Y para sacar la vista previa: PrintPreviewDialog.

miércoles, 11 de junio de 2008

DataBindings automágicos (II): una solución quiero!!!!

Bueno, seguimos con el tema, después de la presentación del problema voy a intentar ofrecer una solución aceptable y rápida.

Está claro que el método que guarda los registros no hace lo que nos gustaría que hiciese, así que vamos a cambiarlo un poco. Nos vamos al método pedidoBindingNavigatorSaveItem_Click y añadimos el siguente código:

private void pedidoBindingNavigatorSaveItem_Click(object sender, EventArgs e)
{

this.Validate();

this.pedidoBindingSource.EndEdit();

this.pedidoTableAdapter.Update(this.dataSet1.Pedido);

// Después de hacer el Update, llamamos al método Fill para que
// recargue los registros de la tabla de Pedido


this.pedidoTableAdapter.Fill(this.dataSet1.Pedido);


// Obtenemos el identificador del último registro que haya en el Pedido del Dataset
// que se debería corresponder con el que acabamos de insertar


int id;
id = (int)this.dataSet1.Pedido.Rows[this.dataSet1.Pedido.Rows.Count - 1]["id"];


// Ahora vamos a las líneas de detalle y le cambiamos el idPedido sólo a las
// que acabamos de añadir


foreach (System.Data.DataRow row in this.dataSet1.Detalle_Pedido.Rows)
{

if (row.RowState == DataRowState.Added)
row["idPedido"] = id;

}


this.detalle_PedidoBindingSource.EndEdit();


// Forzamos el Update

this.detalle_PedidoTableAdapter.Update(this.dataSet1.Detalle_Pedido);


// y nos volvemos a traer los registros de Detalle_Pedido, con lo que tendremos
// los identificadores actualizados


this.detalle_PedidoTableAdapter.Fill(this.dataSet1.Detalle_Pedido);


// Nos movemos al último registro de la cabecera
// que debería ser el que acabamos de añadir


this.pedidoBindingSource.MoveLast();

this.Refresh();

}


¿Qué problemas plantea esta solución?

Para empezar, es una solución para salir del paso (una ñapa como una casa :P), y está claro que no se puede tomar como definitiva. En entornos en los que la concurrencia sea un problema hay que usar técnicas de concurrencia y bloqueos sin remisión con lo que en vez de un problema pasamos a tener 2, la concurrencia y los interbloqueos (deadlocks y similares).

Segundo, el principal problema es que estamos suponiendo que el último registro es efectivamente el nuestro, pero eso puede ser cierto o no. Tendríamos que tener la certeza absoluta de que es así, lo que implicaría hacer consultas sobre la base de datos o bloquear registros.

Y para acabar, el último que guarda en este escenario de concurrencia tiene constancia de todos los registros en cuanto guarda, pero el primero que guarde no obtiene todos los registros hasta que introduzca uno nuevo. Esto puede ser problemático o no, dependerá de nuestro escenario particular.


Conclusiones

- Las cosas fáciles no suelen tener en cuenta los problemas complejos, que suelen ser con los que nos tenemos que pelear en la vida real. Hacer databinding con drag-and-drop están muy bien para hacer pequeños proyectos, pero cuando entra en juego la concurrencia tenemos que saber qué es lo que hay por debajo, y eso sólo se consigue si tenemos muy claro cómo trabaja ADO.NET y sus clases.

- No te preocupes, que lo que te está pasando seguro que le pasó a un millón de programadores antes y casi todos encontraron una solución. Sólo te queda encontrar la tuya, que dependerá de tu problema concreto mucho más de lo que te imaginas.

- Vamos, que no hay una solución definitiva para el tema del acceso a datos y mucho menos el de la concurrencia. Yo personalmente recomiendo usar un ORM para que mapee el modelo de base de datos en clases, que los hay muchos y muy buenos por ahí.

Eso sí, a la espera que el Entity Framework sea lo que todos esperamos que sea ;)

lunes, 19 de mayo de 2008

DataBindings automágicos (I)

(No, no me he equivocado quería escribir autoMÁGICOS no automáticos :P el porqué ya ser verá en el post)

Hace un par de semanas me mandó un mail un lector del blog (Germán) comentándome una duda con el uso de Datasets tipados como origen de datos en Windows Forms.


Dado que ya me había pasado algo similar en algún proyecto en el que trabajé pero del que no recuerdo cómo lo solucionamos, decidí ponerme manos a la obra para hacer un proyecto que tenga la problemática que me planteó Germán y tratar de encontrar una solución óptima. Y como le comenté a Germán, lo pongo aquí para que le sirva a más gente ;) Vamos a empezar:

Tenemos una base de datos a la que llamaré PRUEBAS, con únicamente 2 tablas: Pedido y DetallePedido, con el siguiente esquema:

Pedido
id -Identity PRIMARY_KEY
Descripcion -Varchar(10)

DetallePedido
idDetalle - Identity PRIMARY_KEY
idPedido - Int
Cantidad - Int
Precio - Float

Típicas tablas maestro-detalle, relacionadas por Pedido.id – DetallePedido.idPedido


Creamos un proyecto Windows Form en Visual Studio 2005, y añadimos un Dataset donde agregamos las 2 tablas, que nos quedará de la siguiente forma:





Ahora nos vamos al formulario, y desde el explorador de Origenes de Datos, arrastramos los campos de la tabla Pedidos y los relacionados de DetallesPedido (Atención: tiene que ser el que cuelga de Pedido para que nos mantenga la relación)





Con lo que el formulario nos quedará más o menos así:


Ahora nos vamos a código y buscamos el evento pedidoBindingNavigatorSaveItem_Click que es el que se produce cuando presionamos el botón de Guardar Cambios, y añadimos las 2 líneas en rojo para que guarde también las líneas de detalle:

private void pedidoBindingNavigatorSaveItem_Click(object sender, EventArgs e)
{
this.Validate();
this.pedidoBindingSource.EndEdit();
this.pedidoTableAdapter.Update(this.dataSet1.Pedido);
this.detalle_PedidoBindingSource.EndEdit();
this.detalle_PedidoTableAdapter.Update(this.dataSet1.Detalle_Pedido);
}

Si lo probamos debería funcionar perfectamente, permitiendo movernos entre registros, agregar nuevos pedidos y sus líneas de detalle correspondientes. Ahora viene el problema que nos plantea Germán, ¿y si hay 2 usuarios añadiendo pedidos a la vez?

Pues vamos a probarlo, compilamos, arrancamos 2 veces el ejecutable y creamos un nuevo pedido en cada instancia. ¿Qué sucede? Como es normal asigna un valor al campo id del pedido, que obtiene de sumarle 1 al último valor más grande que exista en la tabla ese momento (ojo: no es el valor que le corresponda al IDENTITY) con lo que nos dará el mismo valor para las 2 instancias.




No vamos a hacer nada más por ahora, guardamos y vemos que pasa.



Como es normal nos ha actualizado el id del pedido con el valor que le ha asignado en la tabla de la base de datos. Bien, es el comportamiento normal y esperado.

Vale, ahora vamos a introducir líneas en estos 2 pedidos que acabamos de guardar:



Como en el caso de las cabeceras de pedido, nos asigna un valor al campo idDetalle, que vuelve a ser el valor máximo del campo en la tabla sumándole 1, con lo que vuelve a ser el mismo valor para las 2 instancias. Guardamos y nos asigna el valor que le corresponde en la tabla.



Vale, todo va bien, hemos añadido una cabecera y una línea y no hay ningún problema, ¿seguro que no hay problemas?

Vamos a hacer lo mismo que antes pero cambiando un poco la forma de hacerlo, ahora vamos a guardar después de añadir la línea de detalle, a ver qué pasa.

Vaya sorpresa, nos ha desaparecido la línea del 2º pedido que guardemos, ahora si nos movemos por los registros esa línea no aparece en ninguna de las 2 instancias, pero si nos vamos a la tabla veremos que está con el identificador que le asignó a la primera instancia que se guardó.

Si cerramos los formularios y volvemos a abrirlos nos encontraremos un pedido con 2 líneas y otro sin líneas. Esto se debe a que al asignar el valor del identificador a las cabeceras también lo hace con las líneas, pero al guardar no actualiza el identificador del pedido en las líneas, con lo que se queda el que tenía en un principio ... ups, vaya lío que hemos montado, ¿no?

Bueno, creo que ya se puede ver por donde van los tiros: ¿nos fiamos o no nos fiamos del código generado automáticamente al arrastar elementos desde los orígenes de datos?

Como este post ya es bastante largo, os presentaré más cosas sobre DataBinding, más problemas y cómo solucionarlos en un próximo post, permanecer atentos al blog :)

viernes, 7 de septiembre de 2007

Bloqueo exclusivo de objetos en C# y VB.NET

Leyendo el libro Microsoft Windows Workflow Foundation Step by Step escrito por Kenn Scribner me encontré con una instrucción para realizar un bloqueo de exclusión mutua en objetos. Posiblemente esto sea a lo que se refiere Ciro Franz el comentario que me hizo en mi blog en BcnGeeks, y como me quedaría un comentario muy largo, prefiero hacer un post.


C# (lock)

Vamos a ver de qué se trata, en C# se realiza con la instrucción lock() que según MSDN: marca un bloque de instrucciones como una sección crucial, para lo cual utiliza el bloqueo de exclusión mutua de un objeto, la ejecución de una instrucción y, posteriormente, la liberación del bloqueo.

Vaya, otra definición extraña de MSDN, pero si seguimos leyendo lo aclaran un poco más: La instrucción lock permite garantizar que un subproceso no va a entrar en una sección crucial de código mientras otro subproceso ya se encuentre en ella. Si otro subproceso intenta entrar en un código bloqueado, esperará, o se bloqueará, hasta que el objeto se libere.

Y esto no es ni más ni menos que una forma de control de concurrencia sobre un objeto, pero lo más interesante es la forma de usarlo, esta instrucción contiene un bloque de código que se ejecuta mientras el exista el bloqueo. Mejor me explico con código:



Object objeto;

lock(objeto)
{
//codigo....
//codigo....
}

Si otro proceso intenta ejecutar la instrucción lock(), y el objeto está bloqueado, tendrá que esperar a que se acabe de ejecutar todo el bloque de código.

Vale, vamos a poner un ejemplo práctico en el que no hay bloqueos, para luego demostrar como funciona la instrucción lock. Como casi siempre, será una aplicación de consola en C#, a la que añadimos el siguiente código:




using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace PruebaLock
{
class Program
{
static void Main(string[] args)
{
//Declaramos 10 hilos que ejecuten en método EjecutarMetodoEstatico
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(ClaseEstatica.EjecutarMetodoEstatico));
threads[i] = t;
}

//Ejecutamos los hilos
for (int i = 0; i < 10; i++)
{
threads[i].Start();
}

//Esperamos a que se pulse una tecla para finalizar
Console.WriteLine("Pulse una tecla para salir...");
Console.ReadKey();
}
}

public static class ClaseEstatica
{
public static void EjecutarMetodoEstatico()
{

Console.WriteLine("Ejecutando bloque de código...");
// La instrucción Sleep suspende la ejecución del hilo,
// la utilizo en vez de un bloque de código que tarde
// 1 segundo en ejecutarse
Thread.Sleep(1000);
Console.WriteLine("Fin bloque de código.");
}
}
}

Ejecutamos (F5), y se obtiene este resultado:



Como podemos ver, los objetos se ejecutan en paralelo y finalizan a la vez.

Bien, vamos a probar el bloqueo, cambiamos el código que teníamos antes por este, fijaos en que el bloque en el que está la instrucción Sleep(), empieza con lock(objetoBloqueo):



using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace PruebaLock
{
class Program
{
static void Main(string[] args)
{
//Declaramos 10 hilos que ejecuten en método EjecutarMetodoEstatico
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(ClaseEstatica.EjecutarMetodoEstatico));
threads[i] = t;
}

//Ejecutamos los hilos
for (int i = 0; i < 10; i++)
{
threads[i].Start();
}

//Esperamos a que se pulse una tecla para finalizar
Console.WriteLine("Pulse una tecla para salir...");
Console.ReadKey();
}
}

public static class ClaseEstatica
{
// Declaramos el objeto que se va a bloquear, debe ser private
private static object objetoBloqueo = new object();

public static void EjecutarMetodoEstatico()
{
lock (objetoBloqueo)
{
Console.WriteLine("Ejecutando bloque de código...");
//La instrucción Sleep suspende la ejecución del hilo,
//la utilizo en vez de un bloque de código que tarde 1 segundo en ejecutarse
Thread.Sleep(1000);
}
Console.WriteLine("Fin bloque de código.");
}
}
}



Y el resultado:



La instrucción Console.WriteLine("Pulse una tecla para salir..."); se ejecuta la primera, porque los hilos están ocupados con los bloqueos, y no se ejecuta un hilo hasta que haya acabado el anterior, con lo que ya tenemos montado una especie de control de concurrencia pesimista.


VB.NET (Synclock)

En VB.NET la instrucción que realiza el bloqueo es Synclock() y su uso es idéntico al de lock() de C#, lo curioso es que en MSDN, esté descrita de la siguiente forma: Adquiere un bloqueo exclusivo para un bloque de instrucciones antes de ejecutar el bloque.

Si alguien lo entiende, que me lo explique, porque la definición de lock() es complicada, pero por lo menos se entiende.

Bueno, vamos a olvidarnos de las definiciones de MSDN, y vamos al mismo código de ejemplo pero en VB.NET:




Imports System.Threading

Module Module1

Sub Main()
'Declaramos 10 hilos que ejecuten en método EjecutarMetodoEstatico
Dim threads(10) As Thread
For i As Integer = 0 To 10
Dim t As Thread = New Thread(New ThreadStart(AddressOf ClaseEstatica.EjecutarMetodoEstatico))
threads(i) = t
Next

' Ejecutamos los hilos

For i As Integer = 0 To 10
threads(i).Start()
Next

' Esperamos a que se pulse una tecla para finalizar
Console.WriteLine("Pulse una tecla para salir...")
Console.ReadKey()
End Sub

End Module

Public Class ClaseEstatica

' Declaramos el objeto que se va a bloquear, debe ser private
Private Shared objetoBloqueo As Object = New Object()

Public Shared Sub EjecutarMetodoEstatico()
SyncLock objetoBloqueo

Console.WriteLine("Ejecutando bloque de código...")
' La instrucción Sleep suspende la ejecución del hilo,
' la utilizo en vez de un bloque de código que tarde 1 segundo en ejecutarse
Thread.Sleep(1000)

End SyncLock
Console.WriteLine("Fin bloque de código.")
End Sub
End Class


Si lo ejecutáis, funciona exactamente igual que el ejemplo de C#.


Conclusión

Como siempre alguno se puede preguntar ¿y para qué quiero bloquear el acceso a un bloque de código?

Pues en el libro de Kevin Scribner sirve para hacer una clase singleton que devuelve una única instancia de WorkflowRuntime a todas los procesos que llamen a una clase estática, que es una solución que a mi me parece muy interesante.

Pero está claro que si no estás realizando un proceso multi-hilo, la concurrencia no te debería importar demasiado, pero, a poco que crezca tu aplicación, los hilos serán imprescindibles y el control de concurrencia puede ser muy útil.

Aunque el uso que le deis queda en vuestras manos, o más bien abierto a vuestra imaginación.

miércoles, 29 de agosto de 2007

Implementar una interface con un solo clic

Hace tiempo prometí explicar cómo se puede implementar una interface con sólo 2 clics, pero me he dado cuenta de que únicamente hace falta 1.

Qué es una interface y qué los dos tipos de implementación que podemos usar lo tenéis muy bien explicado en esta página.
Una vez vista la teória, voy a ir a un ejemplo mucho más práctico para demostrar como funciona otra de las ayudas de Visual Studio 2005. Supongamos que tenemos la siguiente interface:
Una interface muy sencilla con 2 métodos, bien, creamos una clase que la va a implementar:


Si os fijáis, el IDE de Visual Studio hace que aparezca un pequeño rectángulo azul debajo del nombre de la interface (cuando el cursor está sobre antes, después o en medio del nombre de la interface), este rectángulo será el que nos ayude a implementarla.
Vamos a pasar el ratón sobre el rectángulo, y veamos que pasa:


(clic en la imagen para ampliar)

Vaya, se despliega este menú para implementar la interface, hagamos clic en la primera opción:


(clic sobre la imagen para ampliar)

Ya tenemos la interface implementada con tan solo 1 clic. Bueno, falta escribir el código de los métodos, pero en principio ya funcionan (aunque sólo lancen una excepción).

Pero alguno puede pensar, “vaya tontería, esos 2 métodos no me cuesta tanto escribirlos”, y tiene razón, pero
- ¿qué pasa si la interface tiene 200 métodos?
o
- ¿qué pasa si no tenemos documentación sobre los métodos porque es una interface del sistema o de un tercero?
En estos casos el IDE de Visual Studio viene en nuestra ayuda, como hemos visto.

Casi se me olvida, ¿qué pasaría si a la interface le añadiésemos un método?, tal que así:



Pues lo único que tenemos que hacer es volver a realizar los mismos pasos, cursos sobre el nombre de la interface, ratón sobre el rectángulo azul y clic sobre la opción:



(clic en la imagen para ampliar)

viernes, 24 de agosto de 2007

Estoy Leyendo...

De vuelta de unos días de descanso he decidido hacer unos cambios en el blog. Si os fijáis en el panel de la derecha, debajo de las etiquetas, he añadido una sección con los libros que estoy leyendo actualmente.

Me parece que la primera vez que vi algo así fue en el blog de Rodrigo Corral y me pareció muy buena idea, sobre todo porque Rodrigo también comenta los libros, yo intentaré imitarle (siempre hay que copiar de los mejores) y cuando acabe de leerlos o los tenga muy avanzados escribiré un post, aunque supongo que las más 500 páginas de WCF step by step y las más de 700 de CLR via C# me tendrán entretenido una buena temporada.

La verdad es que también me gustaría tener un apartado de "Jugando a..." como César Reneses pero no dispongo de tiempo, tal vez cuando esté de vacaciones.

miércoles, 8 de agosto de 2007

Montar un servicio WCF en 10 pasos

Para los que no conozcáis Windows Comunication Foundation (WCF para los amigos) os dejo unos cuantos enlaces muy recomendables:

Visión Global de Arquitectura de Windows Communication Foundation

Artículos de Aaron Skonard en MSDN Magazine (aunque los veáis en inglés, redirecciona a los artículos en castellano)

Artículos de Juval Lowy en MSDN Magazine (lo mismo que los de Skonnard)

Fundamentals of WCF Security – Michele Leroux Bustamante (en inglés, pero merece el esfuerzo)

Blog de Oskar Álvarez en Geeks.ms

En este post me voy a centrar en la implementación de un servicio WCF muy básico y con pasos muy concretos (algunos mucho más fáciles que otros).

Empecemos, en Visual Studio creamos una solución en blanco (a mi me gusta trabajar así, cada cual con sus manías), a la que añadimos dos proyectos de consola (con C#): Host y Cliente.

Y ahora vamos por partes:

Proyecto Host

1. Agregar referencia al ensamblado System.ServiceModel.

2. Definir una interface con el atributo [ServiceContract] para indicar que es la que define el contrato de WCF. Los métodos de esta interface que se vayan a exponer en deben incluir el atributo [OperationContract].




[ServiceContract]
public interface IClase
{
[OperationContract]
string Funcion1();

[OperationContract]
int Funcion2();

// Este método no se expone porque no tiene el atributo
//[OperationContract]
void MetodonoImplementado();
}


3. Definir una clase que implemente la interface IClase (en un futuro post explicaré cómo se implementa una interface con sólo dos clics).



class Clase : IClase
{
string IClase.Funcion1() { return "Respuesta"; }

int IClase.Funcion2(){ return 10; }

void IClase.MetodonoImplementado(){ return; }
}




4. Añadimos un fichero de configuración (app.config) al proyecto, para establecer las propiedades del servicio.


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<!--Comportamiento se define un poco más abajo, en la sección BEHAVIORS-->
<service name="Host.Clase" behaviorConfiguration="Comportamiento">
<host>
<!--Se establece la URL del servicio-->
<baseAddresses>
<add baseAddress="http://localhost:8000/WCF/"/>
</baseAddresses>
</host>
<!--En el enpoint se define el ABC de los servicios WCF, Address, Binding y Contract-->
<endpoint address="Clase"
binding="wsDualHttpBinding"
contract="Host.IClase"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="Comportamiento">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>




5. En el método Main() iniciamos el servicio.



class Program
{
static void Main(string[] args)
{
Type tipo = typeof(Clase);
using (ServiceHost miHost = new ServiceHost(tipo))
{
miHost.Open();
Console.WriteLine("Servicio en ejecución");
Console.WriteLine("Pulse una tecla para salir...");
Console.ReadKey();

miHost.Close();
}
}




6. Ejecutamos únicamente el proyecto Host.

7. Desde la consola de Visual Studio * ejecutamos la herramienta svcutil.exe, que creará los ficheros que servirán de proxy entre el cliente y el servicio.




Se crean 2 ficheros:
- Clase.cs - definición del proxy.
- Output.config - fichero de configuración, que debe ser renombrado como
app.config para los proyectos Windows (web.config para proyectos Web)

* Consola de Visual Studio, la podemos encontrar en: Inicio - Todos los programas - Microsoft Visual Studio 2005 - Herramientas de Visual Studio


Paramos la ejecución y pasamos al otro proyecto.

Proyecto Cliente

8. Agregamos la referencia al ensamblado System.ServiceModel, igual que el paso 1 del proyecto Host.

9. Añadimos los ficheros que genera la herramienta svcutil.exe (paso 7 del proyecto Host), Clase.cs y app.config.

10. En el método Main() iniciamos el proxy y ejecutamos los métodos.

class Program
{
static void Main(string[] args)
{
Console.WriteLine("Pulse cualquier tecla cuando el servicio esté iniciado...");
Console.ReadKey();

using (ClaseClient proxy = new ClaseClient(“WSDualHttpBinding_IClase”))
{
Console.WriteLine(proxy.Funcion1());
Console.WriteLine(proxy.Funcion2().ToString());
Console.WriteLine("Pulse cualquier tecla para salir...");
Console.ReadKey();
proxy.Close();
}
}
}




Ya lo tenemos todo listo, vamos a probarlo, para ello tenemos que ejecutar los dos proyectos al mismo tiempo, por lo que en las propiedades de la solución hay que establecer Proyectos de inicio múltiples y establecer la acción de ambos proyectos a Iniciar, y ejecutar (F5).



Por supuesto que WCF es mucho más complejo, pero como toma de contacto creo que está bien, en próximos post entraremos en profundidad en más características.

domingo, 22 de julio de 2007

Code snippets (I)

¿Qué son y para qué sirven?

Visual Studio 2005 tiene un par de novedades que hacen que la tarea de escribir código sea un poco menos pesada. Una de estas novedades son los code snippets.

Según MSDN son: "bloques de código reusables orientados a tareas". Vaya definición, así que vamos a poner un ejemplo para que se pueda ver más claro. Si programando en C# tenemos que crear una propiedad dentro de una clase tendremos que escribir algo parecido a esto:


private string _miPropiedad;

public string miPropiedad
{
get
{
return _miPropiedad;
}

set
{
_miPropiedad = value;
}
}


Bien, esto son 19 palabras (sí, las he contado), y unos 150 caracteres, y sólo para una propiedad. Imaginaos si hay que escribir una clase con 50 o 100 propiedades, la cantidad de tiempo que te puede llevar. ¿Y no habrá en algo en Visual Studio para ayudarnos? Pues esto es lo que hacen los code snippets.

Vamos a usar este mismo ejemplo y usar un code snippet para ver lo fácil que es. Escribimos “prop” y el Intellisense (si lo tenemos activado) nos muestra lo siguiente:


Si presionamos TAB, ocurrirá lo siguiente:


Podemos observar que aparece en un rectángulo verde el tipo de variable asociada a la propiedad, el nombre de esa variable y el nombre de la propiedad. Como se puede suponer, al cambiar el tipo de dato de la variable automáticamente se cambia el tipo de dato de la propiedad.
Para movernos entre los diferentes elementos que se pueden cambiar hay que presionar la tecla TAB y para volver al elemento anterior con Shift + TAB, y cuando finalicemos de modificar este código y tengamos la propiedad que deseamos sólo hay que pulsar ENTER para seguir con el resto del código.

Para obtener el primer fragmento de código de este post, tendremos que escribir: string (una sola vez), _miPropiedad, y miPropiedad. En total son 4 palabras y unos 33 caracteres. Vaya cambio, ¿no?

Pues esto es un code snippet, un fragmento de código que se activa con Intellisense mediante un “alias” que tiene cada snippet.

¿Y en VB.NET? Pues es algo parecido, puesto que después de escribir el alias o identificador del code snippet hay que poner un “?”, con lo que tenemos prop? y presionar TAB.

Hay otra forma de insertar un snippet es presionando el botón derecho en la ventana de código y eligiendo la opción de Insertar Snippet, con lo que tenemos:


Además Visual Studio nos permite modificar los snippets existentes o crear unos nuevos, pero eso será tema par otro post.

viernes, 13 de julio de 2007

Control de concurrencia pesimista con ADO.NET (C#)

Esta es la primera entrada seria del blog, después de la presentación, así que espero que me salga bien.

El tema a tratar es el control pesimista en ADO.NET como solución para la concurrencia entre varios usuarios al acceder a un mismo registro de una tabla. El caso concreto que voy a tratar corresponde a una base de datos SQL Server. Para otro tipo de bases de datos existen otras soluciones.

Para los que no conozcáis como maneja la concurrencia ADO.NET tenéis una introducción en este blog, o en esta entrada de MSDN.

En este post vamos a tratar el caso de la concurrencia pesimista, esto es, cuando un usuario accede a un registro éste quedará bloqueado para los demás usuarios que tengan acceso a la base de datos.

Para que os hagáis una idea de por donde van los tiros, se trata de especificarle a SQL Server que queremos bloquear un registro mientras estamos en una transacción.

¿Cómo se hace?

Vayamos por partes, lo primero que tengo que explicar es que en SQL Server existe una instrucción en la sentencia SELECT para que bloquee un registro cuando lo leemos. Esta instrucción se conoce como Table Hint y lo que hace es indicarle al optimizador de consultas (query optimizer) el método de bloqueo de la tabla o vista.

Por emplo: SELECT * FROM MiTabla WITH(Rowlock, Xlock) WHERE id = 10;

Rowlock -> fuerza el bloqueo de los registros que devuelva la instrucción.
Xlock -> indica que el bloqueo es exclusivo.

Y esta sentencia SELECT la tenemos que iniciar con una transacción abierta. El bloqueo existe mientras la transacción esté en curso, por tanto, hasta que no se finalice la transacción (Commit o Rollback), se salga de su alcance o se cierre la conexión, el bloqueo permanecerá activo. Para que el bloqueo sea efectivo el nivel de asilamiento de la transacción (isolationLevel) puede ser ReadCommitted, ReadUnCommitted, RepetableRead o Serializable, no aceptandose Chaos, ni SnapShot, ni Unspecified.



El código

Seguro que viendo el código lo entenderéis mejor de que va todo esto, así que aquí lo tenéis:




using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace BloqueoPesimista
{
class Program
{
static void Main(string[] args)
{

DataSet ds = new DataSet();
SqlConnection conexion = new SqlConnection();
SqlTransaction transaccion;
SqlCommand comando = new SqlCommand();
SqlDataAdapter da = new SqlDataAdapter();

Console.WriteLine("Pulse una tecla para iniciar...");
Console.ReadKey();

try
{

// La cadena de conexión indica que atacamos a la base de datos Northwind del servidor local
conexion = new SqlConnection("Data Source=.;Initial Catalog=Northwind;Integrated Security=True");

comando.Connection = conexion;
comando.CommandType = CommandType.Text;

// El SELECT tiene un parámetro para evitar SQL Injection
comando.CommandText = "SELECT ProductID, ProductName FROM Products WITH (Rowlock,Xlock) " +
"WHERE ProductID = @ID";
comando.Parameters.AddWithValue("@ID", 1);
da.SelectCommand = comando;

//Abrimos la conexión y ejecutamos en comando
//Nota: también se puede hacer con la sentencia Using() que nos evita abrir y cerrar la conexión
conexion.Open();

// La transacción no tiene constructor, se crea a partir de la conexión a través de un Class Factory
transaccion = conexion.BeginTransaction(IsolationLevel.ReadCommitted);

// Una vez creada la transacción la asignamos al comando
comando.Transaction = transaccion;

// Ejecutamos el comando mediante el SQLDataAdapter
da.Fill(ds, "Products");

// Mostramos el registro bloqueado
Console.WriteLine("Registro con ProductID = {0} está bloqueado.", ds.Tables["Products"].Rows[0]["ProductID"]);

Console.WriteLine("Pulse una tecla para finalizar el bloqueo...");
Console.ReadKey();

// Finalizamos la transacción, aquí ya se deshace el bloqueo
transaccion.Rollback();

// Cerramos la conexión
conexion.Close();
}
catch (Exception e)
{
if (conexion.State != ConnectionState.Closed)
conexion.Close();

Console.WriteLine(e.Message);
Console.ReadKey();
}
}
}
}



Se trata de una aplicación de consola en C#, en un futuro post pondré el código en VB.NET. Así que sólo hay que copiarlo en un proyecto de consola.


Vamos a probarlo

Para poder pobrarlo, hay que Generar el proyecto, con lo que nos creara un fichero .EXE en la carpeta Bin/Debug del proyecto.
Ejecutamos el exe una vez y pulsamos una tecla para que se inicie el bloqueo.







Si volvemos a ejecutar el exe e iniciamos el bloqueo, la aplicación se quedará esperando que se libere el registro.






En cuanto finalicemos la primera ejecución, la segunda podrá acceder al registro y bloquearlo.


Y con esto finalizamos por hoy. Para ser la primera entrada me ha quedado un poco larga, pero espero que pueda ayudar a cualquiera de los que la lean.

Cualquier duda o sugerencia, por favor, en los comentarios.


PD: ¿Es el control de concurrencia pesimista la mejor opción? En absoluto, pero para algún caso en concreto puede ser LA SOLUCIÓN. Queda a vuestro criterio el usarlo o no.