martes, 8 de marzo de 2016

Error "Supports transactions, row-level locking, and foreign keys" al intentar crear clave foránea en MySQL

Ésta es como una nota mental o algo así, porque después de perder un buen rato resolviendo este problema y sabiendo que la solución era tan simple y no encontré la respuesta puntual en Internet, siento la necesidad de dejar el recordatorio, no sea que vuelva a pasar en un futuro.

Contexto:

Intentar crear una clave foránea entre dos tablas en MySQL 5, usando una sentencia como la siguiente:

ALTER TABLE tabla1 ADD CONSTRAINT `FK_tabla1_tabla2_id` FOREIGN KEY (tabla2_id) REFERENCES tabla2(id) ON DELETE RESTRICT ON UPDATE CASCADE;

Al ejecutar la sentencia aparece un error como el siguiente (en phpMyAdmin):

#1005 - Can't create table 'db_test.#sql-51c_d7f' (errno: 150) 

Lo cual para MySQL es la siguiente advertencia:

Supports transactions, row-level locking, and foreign keys


Puntos a revisar:
  • Que el nombre de la clave no esté repetido: correcto
  • Que el nombre del campo y la tabla destino y origen estén bien: correcto
  • Que ambas tablas sean InnoDB: correcto
  • Que ambos campos sean del mismo tamaño: correcto
  • ... muchas más pruebas desesperadas: correcto

... y la solución real: 


Puede parecer sencillo pero verla, por lo simple, fue una odisea. El campo origen (tabla1.tabla2_id) NO ERA UNSIGNED mientras que el campo destino SI lo era :'(

Un error sutil pero que hace que ambos campos no sean idénticos y por ende no puedan compartir una clave foránea. Solución: modificar el campo origen para hacerlo no signado (UNSIGNED) y que de esa manera ambos campos sean completamente idénticos.

jueves, 24 de enero de 2013

Unir varios PDF con ItextSharp al tiempo que se llenan campos de formularios en los PDF

Escribo esto para ver si evito dolores de cabeza a otras personas que puedan pasar por el mismo problema que yo... si por lo menos una persona evita perder varios días buscando la solución (como me tocó a mi), entonces se librará esta entrada :D

Primero el contexto:
  1. Se requiere llenar desde C# los valores de campos en varios PDF, los campos están incrustados como formularios en el PDF.
  2. Se precisa unir los PDF resultantes para guardarlo en un solo archivo o desplegarlo en un navegador.
  3. Se está trabajando con las librerías de ITextSharp en su versión OpenSource: Community.
¿Y cuál es el problema? Que se despliegan varios errores al intentar usar las funciones de copia que vienen con las librerías si previamente se han modificado campos en alguno de los PDF. El error más común es una excepción del tipo:

System.NullReferenceException: Referencia a objeto no establecida como instancia de un objeto.
   en iTextSharp.text.pdf.PdfReader.GetNormalizedRectangle(PdfArray box) en  d:\itextsharp-all-5.3.5\itextsharp-src-core\iTextSharp\text\pdf\PdfReader.cs:línea 538

-- ó ---
System.InvalidCastException: No se puede convertir un objeto de tipo 'iTextSharp.text.pdf.PdfArray' al tipo 'iTextSharp.text.pdf.PRIndirectReference'.
   en iTextSharp.text.pdf.PdfReader.DuplicatePdfObject(PdfObject original, PdfReader newReader) en d:\itextsharp-all-5.3.5\itextsharp-src-core\iTextSharp\text\pdf\PdfReader.cs:línea 3079

=================================================================

Pues bueno, lo primero es decir que la librería en la versión 5.3.5 corrige varios de los problemas que se presentaban en la versión anterior al momento de hacer la copia... que suerte ya que esta versión es muy reciente, tanto que inicié el proyecto con la versión anterior y tuve que hacer el cambio en el camino :|

Bueno, la solución para evitar inconvenientes es sencilla (como casi siempre que uno pierde varios días buscando la solución ¬¬ ), básicamente consiste en crear un nuevo Stream que asuma el PDF con los nuevos valores de campo como si fuera el PDF original. El código es el siguiente:

[...]
                /*
                 * target es el Stream resultado. 
                 * Dependiendo de lo que se pretenda se crea uno u otro tipo de Stream. Lo demás es normal hasta el momento del cierre que se puede ver al final de este ejemplo, marcado como Primera opción o Segunda Opción*/

                // Si se va a almacenar en un archivo se utiliza un FileStream
                Stream target = new FileStream("ruta/nombre_archivo_destino.pdf", FileMode.Create);

                // Si se va a motrar en el navegador se utiliza un MemoryStream
                Stream target = new MemoryStream();

                 // Se instancian el MemoryStream que almacenará la información en cada PDF 
                // y el Documento que será el que almacene los PDF unidos
                MemoryStream ms = new MemoryStream();
                Document doc = new Document();

                // Se utiliza un PDFCopy para unir las hojas de los PDF
                // Se instancian el MemoryStream que almacenará la información en cada PDF 
                using (PdfCopy copy = new PdfCopy(doc, target)) {
                   // Importante abrir el documento para poder modificarlo
                    doc.Open();
                    copy.SetLinearPageMode();
                    PdfReader readerX = null;
                    ms = new MemoryStream();


                   // Se lee el primer archivo PDF en el cual se harán modificaciones, asignando valores a los campos
                    PdfReader r = new PdfReader("archivo1.pdf");

                   // Para poder modificar los campos se necesita trabajar con un PDFStamper
                    PdfStamper stamp = new PdfStamper(r, ms);
                    AcroFields fields = stamp.AcroFields;

                   // Se insertan los valores necesarios a los campos
                    fields.SetField("Clave_del_campo1", "Valor del campo1");

                    // Con Yes se selecciona un check
                    fields.SetField("Clave_de_un_check", "Yes");
                     //... asignación de otros campos

                  // Opcional: los siguientes valores permiten cerrar el PDF para que no se pueda modificar manualmente
                    stamp.FormFlattening = true;
                    stamp.FreeTextFlattening = true;
                    stamp.Writer.CloseStream = false;


                   // Hay que cerrar el PDFStamper para que los cambios se reflejen en el PDF
                    stamp.Close();

                   // Se cierra el reader para que el archivo no quede amarrado a la aplicación
                    r.Close();

                   // ESTE ES EL TRUCO IMPORTANTE
                   // Se crea un nuevo PDFReader con los byte[] del Stream que almacenaba el documento modificado
                    readerX = new PdfReader(ms.GetBuffer());

                  // Se copian cada una de las páginas del nuevo PDFReader
                    for (int i = 1; i <= readerX.NumberOfPages; i++) {
                        copy.AddPage(copy.GetImportedPage(readerX, i));
                    }

                   // Se lee el segundo archivo PDF
                    r = new PdfReader("archivo2.pdf");

                    // Se vuelve a cargar la memoria del Stream para el nuevo PDF
                    ms = new MemoryStream();

                   stamp = new PdfStamper(r, ms);

                   // Para mi caso no tuve que hacer modificaciones pero se puede hacer lo mismo del primer PDF
                    stamp.Close();

                   // Se cierra el reader para que el archivo no quede amarrado a la aplicación
                    r.Close();

                   // NUEVAMENTE
                   // Se crea un nuevo PDFReader con los byte[] del Stream que almacenaba el documento
                    readerX = new PdfReader(ms.GetBuffer());

                  // Se copian cada una de las páginas del nuevo PDFReader, las cuales se adicionarán al documento por debajo de las que ya se adicionaron.
                    for (int i = 1; i <= readerX.NumberOfPages; i++) {
                        copy.AddPage(copy.GetImportedPage(readerX, i));
                    }

                    // Se ejecutan un par de acciones de limpieza
                    if (readerX != null) {
                        readerX.Close();
                    }
                    ms.Dispose();

                   // Se cierra el documento para que se termine correctamente
                    if (doc != null) {
                        doc.Close();
                    }

                 }// Fin del using

                  /* En este momento ya se tiene en la variable target el Stream de todos los documentos unidos.
                  Ahora, se puede almacenar en un archivo o mostrarlo en el navegador. Lo anterior porque es una aplicación ASPX + C#, si es una aplicación de consola o WindowsForm o algo así pues se tendrán que hacer las modificaciones pertinentes al siguiente código.*/

                  // Primer opción: si se quiere almacenar en un archivo                        
                     target.Close();

                  // Segunda opción: si se quiere mostrar en el navegador
                      Response.ContentType = "application/pdf";

                        if (!target.CanRead) {
                            target = new MemoryStream(((MemoryStream)target).GetBuffer());
                        }

                        Byte[] bytes;
                        target.Seek(0, SeekOrigin.Begin);

                        bytes = new Byte[target.Length];
                        target.Read(bytes, 0, (int)target.Length);
                        Response.OutputStream.Write(bytes, 0, bytes.Length);
                        Response.End();
                        target.Close();

[...]


Bueno, eso es todo... si hay algún error en el código (que al pasarlo acá puede suceder) me lo informan por favor para corregirlo.

Gracias a la gente de ItextSharp por tan buena librería y gracias a todos los blog que dan luces para arreglar estos problemas, aunque este haya sido tan particular que no pude encontrar la solución pero si muchas ayudas parciales :( ... por eso devuelvo el favor escribiendo esta solución :D ¡ya está!.

sábado, 15 de diciembre de 2012

Depurando una Web app en Sharpdevelop

En aras de mantener la coherencia entre la forma de pensar y la de hacer las cosas, no cedí a la idea de instalar las herramientas que en otrora pudiera haber instalado para hacer un desarrollo con .Net sino que busqué la manera de hacerlo con software libre. Hay que aclarar que cuento con la licencia original de Windows professional que venía de fábrica con mi portátil, lo cual fue de gran utilidad.

Desde hace un tiempo vengo utilizando SharpDevelop para crear mis programas, principalmente de consola. Es un IDE muy útil y robusto, pero principalmente me gusta por estar en la onda libre y ser OpenSource. Sin embargo, hasta el día de hoy no había tenido la necesidad de utilizarlo en una aplicación Web con ASP.Net y ¡oh sorpresa!, no puedo depurar mis sitios directamente con ese IDE por falta de un servidor de aplicaciones Web. Así que tuve que empezar a buscar una solución, esperando no tener que eludir el problema, aunque su solución me tomara más tiempo del disponible.

Después de buscar un poco me encontré con una herramienta llamada UltiDevWeb (otrora Cassini) el cual es un servidor tanto para aplicaciones como sitios HTML estáticos, que entre sus principales características tiene el hecho de ser libre para su utilización y distribución y si bien no aparece como OpenSource su licencia no me limitaba para mis necesidades.

¡Listo! instalado el UltiDevWeb, configurado, creado el sitio y lo demás como lo indicaban en foros como éste y éste. Efectivamente el sitio quedaba publicado y visible desde el navegador, sin embargo no hubo forma de habilitar la depuración desde el SharpDevelop (#D) ya que siempre presenta un error al no reconocer el servidor. Así que por este lado es posible para poner disponible el sitio pero no lo es para la depuración desarrollando con el #D.

Segunda opción: IIS o IIS Express. Al revisar mi equipo, el cual vino con Windows, me encontré con que al ser una versión professional viene con la opción de habilitar el IIS7. Se habilita fácilmente desde el panel de control, en la opción de "Activar o desactivar las características de Windows". Luego de habilitarlo, en el proyecto de #D asocié el servidor IIS dirigiéndome a las opciones de servidor Web disponibles en las propiedades del proyecto (Ver Imagen 1).

Imagen 1: Habilitar IIS en SharpDevelop

Uno esperaría tener solamente que presionar F5 y poder depurar la aplicación, sin embargo, ocurrieron algunos errores que presento a continuación:

Primero: la aplicación no existía en IIS. Para solucionarlo se agrega el sitio en el administrador del IIS apuntando a la carpeta donde se encuentra el proyecto. En la Imagen 2 puede verse el sitio creado en el IIS, para mi caso se llama ccmapdf.

Imagen 2: Sitio creado en el IIS

Nota importante: Para que no se presente un error con la interpretación del ASP se requiere convertir el sitio en una aplicación. Para ello se da clic derecho sobre el nombre del proyecto (no el principal sino aquel que tiene el mismo nombre y se encuentra al interior del sitio creado) y allí se selecciona la opción "Agregar aplicación", esto abrirá una ventana a la cual se le da Aceptar y listo, se convierte el sitio en una aplicación. Al ser una aplicación se verá con un ícono como unas hojas con un mundo como el de la Imagen 2.


Segundo: El error "A 32 bit processes cannot access modules of a 64 bit process.". Ésta es fácil, se soluciona indicando en el IIS que la aplicación soporta 32 bit. En la Imagen 3 se ven los pasos para habilitar la aplicación a que se ejecute como de 32 bit, lo cual se hace en el grupo de aplicaciones donde se encuentra el proyecto.

Imagen 3: Habilitar aplicaciones en el IIS a 32-bit

Nota: en la misma ventana de propiedades se puede especificar la versión del framework a utilizar y otros valores interesantes.

Tercero: el error:
Error HTTP 500.21 - Internal Server Error
El controlador "PageHandlerFactory-Integrated" tiene una condición previa incorrecta "ManagedPipelineHandler" en su lista de módulos

Éste fue tremendo, resulta que no siempre queda instalado el interprete de ASP.Net. La solución es muy simple, basta con ingresar a la carpeta: c:\Windows\Microsoft.NET\Framework\v4.0.30319 utilizando la consola de comandos y ejecutar el comando:
aspnet_regiis.exe -i

Esta solución la encontré aquí.

Ahora sí, utilizando herramientas libres (y la versión de Windows professional :( ... por ahora) y sin instalar ningún paquete o programa pirata, estoy haciendo mis desarrollos de ASP.Net.

--------------------------------------------------------------------------------------------------------

Ver también:
Full web server (IIS 5.1, 7 & IIS Express) support in SharpDevelop
SharpDevelop Classic ASP.NET websites using IIS Express