Archivo

Archive for 27 octubre 2010

Error 9002 después de un intento por encoger la base de datos


El día de hoy ha sido terrible. Resulta que el proyecto de GeoSEP que tengo está en un servidor de desarrollo compartido. Pero ya no hay espacio en disco duro. Me meto al servidor y menudo lío: solo 9MB de espacio disponible. Ni pex, tiempo de liberar espacio.

Entro a la dirección de SQL Server ($SQLSERVER\MSSQL10.MSSQLSERVER\MSSQL\DATA) y veo que mi base de datos tiene un log de 500MB. Pero además hay una base de datos cuyo MDF mide 3GB y su archivo log LDF mide 2GB! Así que aquí está el pan. Basta con hacer una reducción de ambas bases de datos para ganar 2.5GB nuevamente.

Así pues, abro el Management Studio y expando las bases de datos. Selecciono la mía y hago clic secundario y selecciono “Tasks->Shrink->Database”. Se ejecuta la transacción y listo, 500GB menos. Perfecto. Hago lo propio con la otra base de datos… ¡Y patatús! Resulta que en lugar de reducirse el archivo LDF, ¡éste comienza a aumentar de tamaño! de 2GB pasó a 2.5GB en unos segundos… y lanza error porque ya no hay espacio en disco duro. Me da el patatús.

¿De quién cuernos es esa base de datos? Investigo, y resulta que no es de nadie, ya no se usa. Pero no se puede desaparecer, hay que respaldarla. Bien. Voy al server de nuevo e intento hacerle un “detach” a la base de datos… y me lanza el siguiente error.

The transaction log for database ‘xxx’ is full. To find out why space in the log cannot be reused, see the log_reuse_wait

Me dá el patatús. Ni jota idea del por qué. Pero el mensaje dice que revise log_reuse_wait de la tabla sys.databases, así que hay voy. El campo log_reuse_wait_desc tiene el valor ‘LOG_BACKUP’, que no dice absolutamente nada. Vale, vamos a la documentación a buscar algo. Encuentro un artículo en MSDN que no da información útil.

Así que regreso a la base de datos. Intento hacer un respaldo y me sale el mismo error. Intento ver las propiedades, error. Intento hacer cualquier cosa, y error. Comienzo a perder la paciencia, así que salgo a comer para despejarme.

Regresando, a continuar. Comienzo a buscar en Google y los foros de MSDN, sin tener mucho resultado. Algunas soluciones proponen que se haga un detach/attach, pero es precisamente lo que no puedo hacer. Intento también volver a correr el comando para reducir la base de datos, y el mismo error.

Abro mi explorador y navego a la página de foros de MSDN. Localizo el foro para SQL Server, a ver si alguien me puede ayudar. Comienzo a redactar la pregunta, y cuando regreso al servidor para copiar el mensaje de error, noto algo que no había visto: “Microsoft SQL Server Error 9002”. ¡Ah, hay un código de error! Abro Google y a buscar. Y finalmente encontré una solución.

1. Ejecutar el procedimiento almacenado sp_resetstatus, así:

exec sp_resetstatus 'mi_basededatos'

2. Ejecutar el comando para recuperar una base de datos:

 DBCC DBRECOVER ('mi_basededatos')

Intento correr el detach… ¡y ahora sí funciona! ¡Uf! Me recargo en la pared, supongo que no ganaré el premio al mejor DBA del año. Pero bueno, ya quedó. Dejo un par de enlaces que me dieron la solución, por si alguien más con este problema los quiere leer.

Bueno, ahora sí, de vuelta al trabajo.

Anuncios

Cómo crear una aplicación MFC sin usar el wizard de Visual C++


Buenas noches entusiastas de la programación de C++. Hacía rato que no blogueaba de noche, así que heme aquí, retomando las buenas costumbres.

Afrontémoslo: C++ es el único lenguaje que obtiene todos los beneficios que ofrece Windows desde un inicio. Incluso C# y .NET tardan en acoplarse a las nuevas realidades. Para muestra, un botón: el famoso Ribbon del Fluent UI que se estrenó con Microsoft Office 2007, y que ha sido incorporado de forma nativa a Windows 7. Este elemento que hace vistosas nuestras interfaces gráficas para aplicaciones de escritorio llegó a nosotros a partir del Enhancement Pack de Visual Studio 2008 y viene incorporado de forma nativa en Visual C++ 2010. Sin embargo, no hay versiones oficiales, aunque hay muy buenos componentes por parte de terceros algunos, gratuitos como la versión del compatriota Menéndez Poo para Windows Forms o la versión que se distribuye con el WPF Toolkit en CodePlex para Windows Presentation Foundation. Sin embargo, aunque buenos, tienen el estigma de no ser oficiales de Microsoft.

Dicho esto, también hay que reconocer que la única forma para desarrollar aplicaciones con estas bondades en tiempos y costos razonables es necesario utilizar MFC. Ésta ha sido una gran herramienta, aunque su diseño, que data de 1991 las primeras versiones, es algo anticuado. Sin embargo, Qt aunque lejos todavía no ofrece todo lo que MFC en términos de componentes y otras librerías como wxWidgets o el impresionante Visual Component Framework del buen Jim Crafton les falta soporte general todavía, a mi parecer. Así que seguimos utilizando MFC, pese a todo.

Visual C++ es una gran ayuda para desarrollar con MFC, y está lleno de herramientas y asistentes para hacer el trabajo menos pesado. Pero seamos ciertos: a veces los asistentes generan mucho código basura. Esto es cierto en especial con Visual C++. ¿Recuerdan Visual C++ 6.0, que salió allá por 1998? El Class Wizard generaba tanta basura que era un dolor de cabeza terrible el mirar el código. Y es cierto, esto ha ido mejorando con cada nueva versión del IDE. En particular, he de decir que me siento mucho más cómo con la versión 2010 que con ningún otro entorno de desarrollo. Pero aún así los asistentes no son perfectos.

Tomemos como ejemplo el asistente de proyecto. Tantas opciones que hay: usar MDI, SDI, o tabs; soportar documento/vista, estilos visuales de Windows, MFC estándar, Internet Explorer, Visual Studio Office; usar barra de herramientas estándar, de navegación, Ribbon… en fin. Esto puede ser de gran ayuda, pero a veces el asistente genera tanta cosa que luego hay que pasarse horas limpiando todo para contar con un cascarón decente. Por ejemplo, el simple hecho de utilizar MFC genera infinidad de cadenas de texto en la tabla de cadenas del archivo de recursos, y usar el Ribbon hará que se generen decenas de imágenes, que luego uno tiene que editar y quitar.

WizardMFC

Así pues, a pesar de la ayuda que presta el asistente, a veces es mejor comenzar desde cero. Pero ¡oh sorpresa! A diferencia de los proyectos Win 32, cuyo asistente cuenta con la opción de “Proyecto vacío”, MFC no tiene nada de eso. Entonces, ¿cómo hacer para emular esto?

La solución radica en crear un proyecto nuevo para Win 32. En el asistente de proyecto nuevo, seleccionamos Win 32Project y dejamos las opciones por defecto.

NewProject

Cuando iniciemos, contamos con algunos archivos típicos: BlogTest.h (así se llama la aplicación que cree de ejemplo) no tendrá nada, Resource.h lo usa internamente el IDE, stdafx.h contiene algunos encabezados básicos y targetver tiene una llamada #include al archivo SDKDKVer.h. Por otra parte, stdafx.cpp tendrá un #include “stdafx.h” y será nuestro archivo precompilado, y BlogTest.cpp contendrá la función WinMain. Bien, pues ahora sí, manos a la obra. Lo primero a hacer es eliminar el fastidioso ReadMe.txt.

Acto seguido, necesitamos indicarle al compilador que vamos a trabajar con MFC, así que nos vamos al menú Project y seleccionamos la opción Propiedades de proyecto. En la ventana que nos aparece, expandimos el árbol de “Propiedades de configuración” y nos vamos al nodo “General”. En las propiedades, buscamos la que dice “Uso de MFC” y establecemos el valor a “Usar MFC en librería compartida”. Le damos clic al botón “Aplicar” y repetimos los pasos ahora para la configuración de “Release”.

ProjectProperties

Ya que hicimos estos pasos, estamos listos para utilizar MFC. Necesitaremos a continuación comenzar a escribir código a manita, y mi recomendación sería por comenzar a limpiar los archivos con los que contamos. Comencemos por stdafx.h. En primer lugar, nos deshacemos de los comentarios, dejamos el #include hacia targetver.h y reemplazamos los #includes hacia los archivos de Windows con los de MFC. Al final, deberías tener algo como esto:


#pragma once

#ifndef VC_EXTRALEAN
#define VC_EXTRALEAN            
#endif

#include "targetver.h"

#include <afxwin.h>
#include <afxext.h>
#include <afxdtctl.h>
#include <afxcmn.h>  
#include <afxcontrolbars.h>
#include <afxwinappex.h>
#include <afxdialogex.h>

Luego nos vamos a targetver.h. Este archivo se usa para definir las versiones de Windows que planeamos soportar. En Visual C++ 2008 el asistente generaba algunos símbolos, que ahora han sido reemplazados por un archivo que viene dentro del SDK de Windows, llamado sdkdkver.h. Lo único que tenemos que hacer, pues, es definir el símbolo _WIN32_WINNT con la versión mínima de Windows que queremos soportar y luego dejar el include a sdkdkver. Si no añadimos nada, por defecto asumirá soporte para Windows 7. En mi caso, me gustaría que el soporte mínimo fuera para Windows Vista, por lo que mi targetver.h luce así:


#pragma once

#define _WIN32_WINNT	0x0600

#include <sdkddkver.h>

Perfecto. Resource.h se usa de forma interna, por lo que de momento lo dejamos de lado y pasamos a BlogTest.h. Dado que comenzamos con un proyecto Win32, no habrá nada, pero como sabes para iniciar una aplicación MFC necesitamos crear una clase derivada de CWinApp ó CWinAppEx e instanciarla en algún objeto global. Bien, pues manos a la obra. Creamos dicha clase que debe incluir por lo menos la sobrecarga de InitInstance y ExitInstance. Alternativamente podemos sobreescribir OnAppAbout y de requerirse, PreLoadState, LoadCustomState y SaveCustomState. En mi ejemplo mantendré éstas tres últimas, aunque no hagan nada.

#pragma once

class CBlogTestApp : public CWinAppEx
{
    DECLARE_MESSAGE_MAP();

    public:
        CBlogTestApp();
        virtual ~CBlogTestApp();

        virtual BOOL InitInstance();
        virtual int ExitInstance();

        virtual void PreLoadState();
        virtual void LoadCustomState();
        virtual void SaveCustomState();

        virtual void OnAppAbout();
    
};

extern CBlogTestApp g_objApp;

Bien, ya terminamos nuestros encabezados, vamos ahora con los archivos de implementación. Primero, limpiamos stdafx.cpp de los comentarios y dejamos solo el #include “stdafx.h”. Pasamos ahora al BlogTest.cpp y borramos todo lo que haya. Y a escribir se ha dicho.

  1. Primero, incluimos los archivos de cabecera stdafx.h, resource.h y blogtest.h. Creamos el constructor (donde establecemos si queremos utilizar el Restart Manager que viene con MFC 10, y el ID de nuestra aplicación), el destructor y el mapa de mensajes, que dejaremos con las opciones tradicionales para el AppAbout, Archivo nuevo, Abrir archivo e Imprimir. Luego, instanciamos nuestro objeto global.
  2. Segundo, escribimos el método InitInstance. Tendremos que inicializar los controles comunes, llamar a la versión base de CWinAppEx, inicializar OLE, inicializar cuestiones avanzadas de la interfaz gráfica y establecer las llaves del registro de Windows donde se guardarán nuestras configuraciones. Nota que como de momento no usamos una ventana  o un diálogo ni nada, no incluimos las llamadas al parseo de la línea de comandos o del shell, ni el registro del documento, la vista y el marco hijo (arquitectura documento/vista) ni nada, pero eventualmente necesitaremos añadir el soporte, según veamos necesario.
  3. Finalmente, escribimos el OnAppAbout, el ExitInstance y demás métodos que tengamos.

He aquí el código.


#include "stdafx.h"
#include "Resource.h"
#include "BlogTest.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

CBlogTestApp::CBlogTestApp()
{
	m_dwRestartManagerSupportFlags = 
        AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS;
	SetAppID(_T("Fermasmas.BlogTest.BlogTest.1"));
}

CBlogTestApp::~CBlogTestApp()
{
}

BEGIN_MESSAGE_MAP(CBlogTestApp, CWinAppEx)
	ON_COMMAND(ID_APP_ABOUT, &CBlogTestApp::OnAppAbout)
	ON_COMMAND(ID_FILE_NEW, &CWinAppEx::OnFileNew)
	ON_COMMAND(ID_FILE_OPEN, &CWinAppEx::OnFileOpen)
	ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinAppEx::OnFilePrintSetup)
END_MESSAGE_MAP()

CBlogTestApp g_objApp;

BOOL CBlogTestApp::InitInstance()
{
	INITCOMMONCONTROLSEX InitCtrls;
	InitCtrls.dwSize = sizeof(InitCtrls);
	InitCtrls.dwICC = ICC_WIN95_CLASSES;
	InitCommonControlsEx(&InitCtrls);

	CWinAppEx::InitInstance();

	if (!AfxOleInit())
	{
		AfxMessageBox(_T("Init failed"));
		return FALSE;
	}

	AfxEnableControlContainer();
	EnableTaskbarInteraction();

	SetRegistryKey(_T("Fermasmas Blog Test"));
	LoadStdProfileSettings(5);  

	InitContextMenuManager();
	InitShellManager();
	InitKeyboardManager();

	InitTooltipManager();
	CMFCToolTipInfo ttParams;
	ttParams.m_bVislManagerTheme = TRUE;
	g_objApp.GetTooltipManager()->SetTooltipParams(
        AFX_TOOLTIP_TYPE_ALL, 
        RUNTIME_CLASS(CMFCToolTipCtrl), 
        &ttParams);

	return TRUE;
}

int CBlogTestApp::ExitInstance()
{
	AfxOleTerm(FALSE);

	return CWinAppEx::ExitInstance();
}

void CBlogTestApp::OnAppAbout()
{
	AfxMessageBox(_T("Fermasmas BlogTest 1.0"));
}

void CBlogTestApp::PreLoadState()
{
}

void CBlogTestApp::LoadCustomState()
{
}

void CBlogTestApp::SaveCustomState()
{
}


Ahora ya está todo listo, y aunque nuestra aplicación no hace nada ya tenemos una aplicación funcional. O casi. Si compilamos, seguramente tendremos una advertencia de compilación que nos dice que IDC_STATIC ya ha sido redefinido. Esto pasa porque el archivo de recursos que el asistente de Win32 nos genera está pensado para proyectos Win32 y no MFC. Necesitamos editar este archivo. Así que primero, abrimos Resource.h. Necesitamos eliminar la línea con el símbolo de que no se soporta MFC: _APS_NO_MFC. Y luego, eliminar la definición de IDC_STATIC. Al hacerlo, quedaría así:

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by BlogTest.rc
//

#define IDS_APP_TITLE            103

#define IDR_MAINFRAME            128
#define IDD_BLOGTEST_DIALOG    102
#define IDD_ABOUTBOX            103
#define IDM_ABOUT                104
#define IDM_EXIT                105
#define IDI_BLOGTEST            107
#define IDI_SMALL                108
#define IDC_BLOGTEST            109
#define IDC_MYICON                2
//#ifndef IDC_STATIC
//#define IDC_STATIC                -1
//#endif
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

//#define _APS_NO_MFC                    130
#define _APS_NEXT_RESOURCE_VALUE    129
#define _APS_NEXT_COMMAND_VALUE        32771
#define _APS_NEXT_CONTROL_VALUE        1000
#define _APS_NEXT_SYMED_VALUE        110
#endif
#endif

Y ahora sí, abrimos el archivo de recursos desde el editor de código. Lo primero que notamos es que en las líneas 13-15 se hace referencia a los archivos de Windows. Mala cosa, necesitamos cambiarla por referencias a MFC. Además, necesitamos añadir la referencia a los recursos de MFC estándares y para el Ribbon y demás cosas. Sin embargo, tenemos el pequeño problema de que si sobreescribimos este archivo, Visual C++ se encargará amablemente de regenerarlo como estaba antes. Patatús.

Si nos vamos a las líneas 102-130, veremos algo como lo que sigue.

#ifdef APSTUDIO_INVOKED
///////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#ifndef APSTUDIO_INVOKED\r\n"
    "#include ""targetver.h""\r\n"
    "#endif\r\n"
    "#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
    "#include ""windows.h""\r\n"
    "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

Esto, señoras y señores, es el texto con lo que Visual C++ generará en automático. Como ves, aquí tenemos los #define’s e #include’s a cosas de windows.h, por lo que vamos a reemplazarlo por los #include’s a los archivos de MFC. Algo así debería quedar ese bloque.

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#ifndef APSTUDIO_INVOKED\r\n"
    "#include ""targetver.h""\r\n"
    "#endif\r\n"
    "#include ""afxres.h""\r\n"
    "#include ""verrsrc.h""\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "#define _AFX_NO_OLE_RESOURCES\r\n"
    "#define _AFX_NO_TRACKER_RESOURCES\r\n"
    "#define _AFX_NO_PROPERTY_RESOURCES\r\n"
    "\r\n"
    "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n"
    "LANGUAGE 9, 1\r\n"
    "#include ""afxres.rc""      // Standard components\r\n"
    "#include ""afxprint.rc""    // printing/print preview resources\r\n"
    "#if !defined(_AFXDLL)\r\n"
    "#include ""afxribbon.rc""  // MFC ribbon and control bar resources\r\n"
    "#endif\r\n"
    "#endif\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

El primer bloque queda como está. En el segundo, cambiamos la referencia a windows.h  por la de afxres.h y añadimos versrc.h. El tercer bloque contiene #include’s al archivo de recursos estándar (afxres.rc), a uno para soporte para impresión (afxprint.rc) y otro más para el Ribbon (afxribbon.rc). Una vez que hayamos hecho este cambio, guardamos y cerramos los archivos de recursos, y lo abrimos nuevamente desde la vista de recursos en el IDE. En cuanto hagamos desde ahí algún cambio y guardemos el archivo, Visual C++ lo regenerará con la nueva información.

NOTA: es posible que al intentar abrir la vista de recursos te mande un error, diciendo que IDC_STATIC no está definido. Sucede que seguramente en el archivo de recursos tienes la definición para una ventana de diálogo que contiene el “Acerca de”. Y como eliminamos la definición de IDC_STATIC y el archivo de recurso todavía no se ha generado con los nuevos #include’s pues el IDE protestará. No problemo. Editamos el archivo de recurso y comenta todas las referencias a IDC_STATIC –ya luego las agregarás de nuevo.

Bueno pues, ahora sí tenemos nuestra solución lista para ser desarrollada. Si vas a hacer alguna aplicación basada en diálogos, crea tus clases derivadas de CDialog o CDialogEx y haz el DoModal desde el InitInstance de tu clase derivada de CWinAppEx. Si vas a utilizar una ventana con soporte SDI/MDI/TDI, ya sabes: a derivar de CFrameWndEx / CMDIFrameWndEx, CChildFrameEx / CMDIChildFrameEx, etc., así como de CDocument y CView y hacer todo lo que uno hace.

Conclusiones a las dos de la mañana:

  1. C++ sigue siendo la única forma de acceder a las últimas características que ofrece Windows de forma natural, y MFC la única forma comercialmente viable de desarrollar con C++ para Windows.
  2. Visual C++ cuenta con asistentes para facilitar el desarrollo en MFC, pero a veces puede ser un Pain In The Ass® por todo el código y archivos adicionales que le mete, de tal suerte que podemos pasar mucho tiempo limpiando nuestro código.
  3. Existe una forma de solucionar lo anterior, aunque implica escribir algo de código a manita, lo cual sigue siendo mejor que limpiar lo generado por el asistente. Esta forma consiste en crear un proyecto para Win32, agregarle soporte para MFC en las propiedades del proyecto, cambiar el WinMain por una clase derivada de CWinApEx y ajustar los archivos de recursos.
  4. En adelante, lo que queda es derivar las clases que vamos a usar y en general construir nuestra aplicación.

Bueno jóvenes, eso ha sido todo por hoy. Espero que le encuentre utilidad. Ciao bambini!

 

Post Scriptum. Desde hace mucho tiempo he tenido la intención de escribir un tutorial de MFC. El problema es que hacer algo así es algo monumental que lleva mucho tiempo, por lo que no me he decidido a dar el paso. Pues bien, este artículo quizás sirva de base para comenzar. Quién sabe, ya veremos. Pero antes necesito saber: ¿creen que sería bueno hacer el tutorial, o con los que ya existen tenemos? Espero sus comentarios. 

Categorías:C++, Cómo hacer, WIN-32 Etiquetas: ,

Mi fuente de datos no regresa valores con parámetros nulos


Me recargo en la pared. Benditos sean los dioses. Hoy viernes, día que deberíamos consagrar exclusivamente a Freyja (Friday, Freitag, etc.), tomando cerveza y comiendo carne asada de venado, me he topado con un problema que me ha impedido celebrar como se debe a la adorada diosa.

Pues resulta que en el trabajo, ando creando una aplicación web de geo-codificación. En esencia, la aplicación lee una base de datos de la Secretaría de Educación Pública y obtiene todas las escuelas registradas, se aplican algunos filtros según el deseo del usuario, para luego mostrarlas en un mapa de Google. Pan comido (y de muerto, ahora que comienza la temporada).

Así las cosas, el primer paso es realizar el filtro. Mi procedimiento almacenado es sencillo: un vil select con unas validaciones y listo.

create procedure SchoolSearch
    @Key nvarchar(12) = null,
    @StateId nvarchar(4) = null,
    @MunicipalityId nvarchar(5) = null,
    @Name nvarchar(82) = null
as
begin

    if (isnull(@Name, '') = '') begin set @Name = '%' end
    else begin set @Name = '%' + rtrim(@Name) + '%' end
    if (rtrim(isnull(@Key, '')) = '') begin set @Key = '%%' end
    else begin set @Key = '%' + rtrim(@Key) + '%' end
    if (isnull(@StateId, '') = '') begin set @StateId = '%' end
    else begin set @StateId = '%' + rtrim(@StateId) + '%' end
    if (isnull(@MunicipalityId, '') = '') begin set @MunicipalityId = '%' end
    else begin set @MunicipalityId = '%' + rtrim(@MunicipalityId) + '%' end

    select CCT, NOM_CCT, NOMBRETUR, TIPO, NIVEL, X, Y , ENTIDAD, NOMBREMUN
    from Escuelas
    where
        CCT like @Key
        and ENT like @StateId
        and MUNICIPIO like @MunicipalityId
        and NOM_CCT like @Name
    order by ENTIDAD, NOMBREMUN

end

¡Pffft! Papita. Segunda parte, en mi página ASP.NET, creo una vil fuente de datos usando SqlDataSource. Algo así.

<asp:SqlDataSource ID="_searchDataSource" runat="server" ConnectionString="<%$ ConnectionStrings:GeoSepAnonymousDatabase %>"
    SelectCommand="SchoolSearch" SelectCommandType="StoredProcedure">
    <SelectParameters>
        <asp:Parameter Name="Key" DbType="String" Size="12" ConvertEmptyStringToNull="false"   />
        <asp:Parameter Name="StateId" DbType="String" Size="4" ConvertEmptyStringToNull="true" />
        <asp:Parameter Name="MunicipalityId" DbType="String" Size="5" ConvertEmptyStringToNull="true" />
        <asp:Parameter Name="Name" DbType="String" Size="82" ConvertEmptyStringToNull="true" />
    </SelectParameters>
</asp:SqlDataSource>

¡Pffft! Kindergarten. Tercera parte, un poquito de código C# que actualiza la fuente de datos en base a los filtros seleccionados en otros controles (_stateList y _municipalityList son DropDownList, para finalmente hacer el DataBind sobre el GridView (_catalogueView) y mostrar todo en una ventana modal (ModalPopupExtender) del Ajax Control Toolkit (_showCataloguePopup).

protected void HandleSearchClick(object sender, EventArgs args)
{
    if (!string.IsNullOrEmpty(_stateList.SelectedValue))
        _searchDataSource.SelectParameters["StateId"].DefaultValue =
                 _stateList.SelectedValue;
    else
        _searchDataSource.SelectParameters["StateId"].DefaultValue =
                 string.Empty;

    if (!string.IsNullOrEmpty(_municipalityList.SelectedValue))
        _searchDataSource.SelectParameters["MunicipalityId"].DefaultValue =
                 _municipalityList.SelectedValue;
    else
        _searchDataSource.SelectParameters["MunicipalityId"].DefaultValue =
                 string.Empty;

    _catalogueView.DataBind();
    _showCataloguePopup.Show();
}

¡Pfft! Piece of… Scheisse! Pues sí, usando ASP.NET Framework 3.5 y C# 3, con SQL Server 2008, lo anterior no jala. Es decir, si yo selecciono un valor, me despliega los resultados como debería. Perfecto. Pero si no selecciono nada (es decir, si paso al procedimiento almacenado un valor nulo), no me regresa registro alguno.

No tiene sentido. Abro el SQL Server Management Studio, y corro el procedimiento con los cuatro parámetros en nulos. y funciona, como debe de ser. Luego, para probar que no estoy loco, me creo un SqlConnection, un SqlCommand, un SqlDataAdapter y a partir de éstos, relleno un DataSet: éste devuelve datos. Incluso si hago el enlace entre el GridView y el DataSet, todo es maravilloso. Pero no tiene sentido que no funcione el SqlDataSource. ¿Qué puede estar pasando?

Bueno, pues esto me llevó un ratillo hasta que di en el clavo. Para esto, abrí el Profiler de SQL Server 2008 y vi que no se ejecutaba ninguna consulta. Extrañado (y en este punto, ya algo molesto con el mundo) Comencé a poner mensajes y hacer rastreos, quitando propiedades y así. El problema tenía que estar en el SqlDataSource, porque demonios, todo lo demás parecía funcionar. Así que me puse a revisar todas las propiedades de este objeto. Y di con una que me llamó la atención: CanSelectOnNullParameter.

El nombre me resultó demasiado sospechoso como para ignorarlo. Así que cambié la definición de mi fuente de datos e incorporé semejante parámetro, estableciéndolo a false.

<asp:SqlDataSource ID="_searchDataSource" runat="server"
    ConnectionString="<%$ ConnectionStrings:GeoSepAnonymousDatabase %>"
    SelectCommand="SchoolSearch" SelectCommandType="StoredProcedure"
    CancelSelectOnNullParameter="false">
    <SelectParameters>
        <asp:Parameter Name="Key" DbType="String" Size="12" ConvertEmptyStringToNull="false"   />
        <asp:Parameter Name="StateId" DbType="String" Size="4" ConvertEmptyStringToNull="true" />
        <asp:Parameter Name="MunicipalityId" DbType="String" Size="5" ConvertEmptyStringToNull="true" />
        <asp:Parameter Name="Name" DbType="String" Size="82" ConvertEmptyStringToNull="true" />
    </SelectParameters>
</asp:SqlDataSource>

 

¡Y funcionó! Una vez que pasó mi anonadamiento me fui derechito a la documentación de MSDN, y encontré esto en la sección de Valor de Propiedad:

Tipo: System.Boolean.- true si se cancela una operación de la recuperación de datos cuando un parámetro contenido en la colección SelectParameters se evalúa como null; de lo contrario, false. El valor predeterminado es true.

¡Me lleva Pifas! Así que eso era: cuando uno tiene parámetros en un select y estos son nulos, el SqlDataSource simplemente decide abortar la operación. Por supuesto, si no es nulo (i.e. un espacio en blanco es suficiente) todo funciona bien. Establecer este valor a falso hace que la vida vuelva a cobrar sentido, ya que con esto no interrumpe nada y le pasa el valor nulo al procedimiento almacenado, el cual arreglará cualquier valor posible.

Así que bueno, ya no pude tomar cerveza y comer asado de venado por arreglar esto. Pero ya quedó y todo jala a las mil maravillas. Así que ya es hora de comenzar este fin de semana, y para ello pasaré al mercado por una enorme calabaza para hacerla zumo con lo que acompañaré mi pan de muerto.