25 de marzo de 2010

CodeRush plugin to extract NHibernate's hql queries as Named Queries in a separate xml file

I have created a DevExpress CodeRush plugin to extract hql queries as Named Queries to an xml mapping file.

The idea is to write the hql query in code, like this:


var query = session.CreateQuery("from Order o where o.Amount > :amount");
query.SetParameter("amount", 100);
return query.List<Order>();


Then, position the cursor over the hql query, in this case "from Order o where o.Amount > :amount", and select the refactor option "Extract Hql Named Query".



The plugin then tries to locate the mapping file, and adds the query as a Named query.



After the query is added in the xml file, you can give a name to the named query, which will be synchronized in both files (like que CodeRush's rename F2)



Please note that after the query has been extracted, you should manually change the CreateQuery method with GetNamedQuery

Currently the plugin supports only a fixed name for the xml mapping file, and two strategies to locate that file:

* In current project only (default)
* First in current project, then in solution

This can be configured in the plugin options page.



Also, the refactor command can be configured with a key shortcut



Information about the plugin and download link is placed in the CR_ExtractHqlNamedQuery wiki page on the DXCore Community Plugins project.

Special thanks to Rory Becker for all the information he has shared about DXCore plugin development

21 de marzo de 2010

Detectando errores de bindeo de assemblies

A veces, cuando la aplicación que estamos desarrollando esta dividida en muchos assemblies, detectar un problema de referencias (ya sea un problema de versionado o directamente la falta de un determinado assembly) puede resultar bastante difícil.

El error que el runtime muestra es algo parecido a lo siguiente:

System.IO.FileLoadException: Could not load file or assembly 'B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29' or one of its dependencies.
The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
File name: 'B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29'
at A.Class1..ctor()
at ConsoleApplication1.Program.Main(String[] args) in C:\VSProjects\AssemblyBindingTest\ConsoleApplication1\Program.cs:line 17

WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

En este ejemplo, una aplicación de consola referencia a un assembly A.dll, y este assembly a su vez referencia a otro assembly B.dll
La versión correcta de B.dll debería ser la 1.1.0.0, pero en la carpeta donde se ejecuta la aplicación, deliberadamente hice deploy de la versión 2.1.0.0
Lo único que este mensaje de error me dice, es que no se pudo levantar el assembly 'B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29', o alguna de sus dependencias.

Lo cual no me dice mucho para resolver mi problema, en especial si esto ocurre en un ambiente con decenas de assemblies e inclusive algunos quizás no desarrollados por mi, sino por terceros.

Lo importante del mensaje es lo siguiente:
WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.

Aquí me dice que asignando el valor 1 a la entrada del registro HKLM\Software\Microsoft\Fusion!EnableLog se puede habilitar el "Assembly binding logging", vamos a hacerlo y veamos como cambia el mensaje de error que nos muestra el runtime:

System.IO.FileLoadException: Could not load file or assembly 'B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29' or one of its dependencies.
The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
File name: 'B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29'
at A.Class1..ctor()
at ConsoleApplication1.Program.Main(String[] args) in C:\VSProjects\AssemblyBindingTest\ConsoleApplication1\Program.cs:line 17

=== Pre-bind state information ===
LOG: User = WinXP\Jorge
LOG: DisplayName = B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29
(Fully-specified)
LOG: Appbase = file:///C:/VSProjects/AssemblyBindingTest/ConsoleApplication1/bin/Debug/
LOG: Initial PrivatePath = NULL
Calling assembly : A, Version=1.1.0.0, Culture=neutral, PublicKeyToken=10f50b7c12e8c3d1.
===
LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using machine configuration file from c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
LOG: Post-policy reference: B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29
LOG: Attempting download of new URL file:///C:/VSProjects/AssemblyBindingTest/ConsoleApplication1/bin/Debug/B.DLL.
WRN: Comparing the assembly name resulted in the mismatch: Major Version
ERR: Failed to complete setup of assembly (hr = 0x80131040). Probing terminated.

La primer parte del mensaje es igual a lo que obteníamos antes, pero ahora nos indica además el assembly "llamador":
Calling assembly : A, Version=1.1.0.0, Culture=neutral, PublicKeyToken=10f50b7c12e8c3d1.

Y lo mas importante, el motivo por el cual no se pudo realizar el bindeo.
LOG: Attempting download of new URL file:///C:/VSProjects/AssemblyBindingTest/ConsoleApplication1/bin/Debug/B.DLL.
WRN: Comparing the assembly name resulted in the mismatch: Major Version

Con esto sabemos que el assembly que no se pudo bindear es B.dll (y no alguna de sus dependencias, como sugería el mensaje original) y específicamente sabemos que el problema es de versionado (la dll que el runtime encontró es de una versión mayor a la requerida por la aplicación)

Por último, no olvidemos lo que nos decia el mensaje a modo de advertencia sobre habilitar Assembly Binding Logging:
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

Así que para evitar problemas de performance, luego de haber descubierto y solucionado nuestro problema, deshabilitamos el assembly binding logging asignando un 0 a HKLM\Software\Microsoft\Fusion!EnableLog

Espero que sea útil, cualquier comentario será bienvenido !

18 de marzo de 2010

Asociar un schema (xsd) a los archivos de configuracion de NHibernate y Spring.net en Visual Studio

Este es el primer post de muchos otros que estarán por venir... o al menos eso espero ;)

Para romper el hielo, voy a empezar con algo sencillo, pero que puede llegar a resultar bastante útil.

En Visual Studio, si uno quiere que un determinado archivo xml se valide contra un schema y además tener disponible la funcionalidad de Intellisense, es necesario asociar el xml al xsd en cuestión. Esto se puede hacer fácilmente mediante el menú Xml\Schemas, luego se selecciona el xsd de la lista o se busca un archivo xsd en disco. Hasta ahí todo bien, el problema es que es necesario realizar esta asociación en cada xml, uno por uno, y aun peor es el hecho de que esas asociaciones quedan registradas en el archivo de opciones de usuario de la solución (.suo), así que si me llevo un proyecto a otra solución estaría perdiendo todas las asociaciones entre xml y xsd que tenia dentro del proyecto.

Este problema se puede evitar siguiendo los siguientes pasos (voy a ejemplificar usando archivos xsd de NHibernate y Spring.net, pero la solución aplica para otros casos)

Buscar la carpeta Xml\Schemas, dentro de la ruta donde fue instalado el Visual Studio, y copiar los archivos xsd que queremos asociar a nuestros xmls (por ejemplo "C:\Program files\Microsoft Visual Studio 9.0\Xml\Schemas\").

En este caso, copiamos los archivos xsd para configuración de Spring.net y los archivos xsd de configuración y de mapping de NHibernate:

spring-objects-1.3.xsd
nhibernate-configuration.xsd
nhibernate-mapping.xsd

Luego buscar el archivo Catalog.xml que, siguiendo el ejemplo anterior, debería encontrarse en "C:\Program files\Microsoft Visual Studio 9.0\Xml\Schemas\Catalog.xml" y agregar estas tres lineas dentro del tag SchemaCatalog.


<SchemaCatalog xmlns="http://schemas.microsoft.com/xsd/catalog">
...
<Schema href="%InstallRoot%/xml/schemas/nhibernate-mapping.xsd"
targetNamespace="urn:nhibernate-mapping-2.2"/>
<Schema href="%InstallRoot%/xml/schemas/nhibernate-configuration.xsd"
targetNamespace="urn:nhibernate-configuration-2.2"/>
<Schema href="%InstallRoot%/xml/schemas/spring-objects-1.3.xsd"
targetNamespace="http://www.springframework.net"/>
...
</SchemaCatalog>


Una vez hecho esto, cuando abramos un archivo xml que tenga asociado alguno de los namespaces identificados en el atributo targetNamespace, automáticamente Visual Studio nos va a asociar el schema xsd que corresponda.

Pongo como ejemplo un archivo de configuración de Spring.net, donde el namespace se asigna mediante el atributo xmlns:


<objects xmlns="http://www.springframework.net">
<object name="MyMovieLister"
type="Spring.Examples.MovieFinder.MovieLister, Spring.Examples.MovieFinder">
</object>
</objects>


Espero que sea útil, cualquier comentario será bienvenido !