<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Jérôme De Cuyper</title>
	<atom:link href="http://www.jdecuyper.com/?feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://www.jdecuyper.com</link>
	<description>In order to understand recursion, one must first understand recursion.</description>
	<lastBuildDate>Wed, 18 Aug 2010 02:47:30 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Retrieve return value from executable inside a C# application</title>
		<link>http://www.jdecuyper.com/?p=304</link>
		<comments>http://www.jdecuyper.com/?p=304#comments</comments>
		<pubDate>Wed, 18 Aug 2010 02:47:30 +0000</pubDate>
		<dc:creator>jdecuyper</dc:creator>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[English]]></category>

		<guid isPermaLink="false">http://www.jdecuyper.com/?p=304</guid>
		<description><![CDATA[Recently, I encountered some difficulties trying to read the return value from an executable file. I&#8217;m was trying to call SpamAssassin from within a C# console application and couldn&#8217;t retrieve it&#8217;s return value. Since the exit code indicates whether an email is spam or ham, I had to find a way to receive that value. After struggling a bit, [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: justify;">Recently, I encountered some difficulties trying to read the return value from an executable file. I&#8217;m was trying to call SpamAssassin from within a C# console application and couldn&#8217;t retrieve it&#8217;s return value. Since the exit code indicates whether an email is spam or ham, I had to find a way to receive that value. After struggling a bit, I found out that instead of calling directly SpamAssassin, I had first to call cmd.exe and handle it, as an argument, the path to the SpamAssassin executable. The code ended as following:</p>
<pre class="brush: javascript">
Process p = new Process();
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.Arguments = @&quot; /C C:\spamassassin.exe -e -L &lt; C:\SPAM_TEST.MAI&quot;;
p.StartInfo.FileName = @&quot;C:\WINDOWS\System32\cmd.exe&quot;;
p.OutputDataReceived += (sender, arguments) =&gt; Console.WriteLine(&quot;Received output: {0}&quot;, arguments.Data);
p.Start();
p.BeginOutputReadLine();
p.WaitForExit();
Console.WriteLine(&quot;Exit code: &quot; + p.ExitCode);
p.Close();
</pre>
<p style="text-align: justify;">Here is a screenshot of the console after calling cmd.exe with a ping command:</p>
<p style="text-align: center;"><a href="http://www.jdecuyper.com/wp-content/uploads/2010/08/launch_exe_from_console.jpg" target="_blank"><img class="aligncenter size-full wp-image-310" title="launch_exe_from_console" src="http://www.jdecuyper.com/wp-content/uploads/2010/08/launch_exe_from_console.jpg" alt="launch_exe_from_console" width="481" height="252" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.jdecuyper.com/?feed=rss2&amp;p=304</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Query CouchDB from a C# application</title>
		<link>http://www.jdecuyper.com/?p=256</link>
		<comments>http://www.jdecuyper.com/?p=256#comments</comments>
		<pubDate>Mon, 16 Aug 2010 04:17:28 +0000</pubDate>
		<dc:creator>jdecuyper</dc:creator>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[CouchDB]]></category>

		<guid isPermaLink="false">http://www.jdecuyper.com/?p=256</guid>
		<description><![CDATA[For a couple of years now, I have been following Damien Katz&#8217;s blog entries.  I subscribed to it mostly because I got fascinated reading his story about how he got to rewrite the Lotus Notes&#8217;s Formula Engine. In a couple of months, he was able to make a complete re-write of a programming language and [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: justify;">For a couple of years now, I have been following <a href="http://damienkatz.net/" target="_blank">Damien Katz&#8217;s blog entries</a>.  I subscribed to it mostly because I got fascinated reading his story about how he got to rewrite the <a href="http://damienkatz.net/2005/01/formula-engine-rewrite.html" target="_blank">Lotus Notes&#8217;s Formula Engine</a>. In a couple of months, he was able to make a complete re-write of a programming language and make the engine more than 3 times faster!</p>
<p style="text-align: justify;">Damien Katz also happens to be the founder and CEO of <a href="http://couchdb.apache.org/" target="_blank">CouchDB</a>. For those how are not familiar with this technology, here is a brief description:  CouchDB is a free and open source documented-orientated database engine. It was designed mostly to serve as a database for web applications.  Traditionally, a relational database management system (RDBMS) presents the data to the user as relations i.e. as a set of tables with each table consisting of a collection of rows and columns. In change, CouchDB stores data as a collection of JSON documents. For example, here is how a document about different chocolates is stored on my local server:</p>
<p><a href="http://www.jdecuyper.com/wp-content/uploads/2010/08/couchdb_json_document.jpg" target="_blank"><img class="size-large wp-image-258  " title="couchdb_json_document" src="http://www.jdecuyper.com/wp-content/uploads/2010/08/couchdb_json_document-1024x270.jpg" alt="JSON document" width="819" height="216" /></a></p>
<p style="text-align: justify; ">Since data is stored as a flat collection of documents without any scheme describing its internal relations, the engine is capable of storing large scale objects.  But how can semi-unstructured data (i.e. without relations) be queried? The views allows you to define functions to query  the documents of a database but without affecting the underlying document.  For example, the simplest view returns a table containing all the documents in your CouchDB server. The function goes as follow:
<pre class="brush: javascript">
function(doc) {
emit(null, doc);
}</pre>
<p>And outputs my chocolate document:
<pre class="brush: javascript">
{&quot;total_rows&quot;:1,&quot;offset&quot;:0,&quot;rows&quot;:[
{&quot;id&quot;:&quot;chocolates&quot;,&quot;key&quot;:null,&quot;value&quot;:{&quot;_id&quot;:&quot;chocolates&quot;,&quot;_rev&quot;:&quot;2-4066442969&quot;,&quot;name&quot;:null,&quot;cacao_porcentage&quot;:null,&quot;country&quot;:null}}
]}</pre>
<p style="text-align: justify;">Since CouchDB was build on the web, it&#8217;s no surprise it has its own HTTP API to consult it. If you are a heavy Windows user like me, you will need to switch your old query analyzer for your browser. This is what makes CouchDB a fresh and interesting abstraction.  Everything (or almost) will now be done using the HTTP API. As Jeff Atwood once pointed out, <a href="http://www.codinghorror.com/blog/2009/05/the-web-browser-address-bar-is-the-new-command-line.html" target="_blank">the web browser address bar is becoming the new command line</a>. This is becoming so true with CouchDB it&#8217;s almost scary!</p>
<p style="text-align: justify;">Since CouchDB hit recently 1.0, I was tented to give it a try using a simple C# application. First you will need to download the latest build. I got mine from <a href="http://stackoverflow.com/questions/1050152/use-couchdb-with-net" target="_blank">a question on Stackoverflow</a> but you can also download it <a href="http://wiki.apache.org/couchdb/Installing_on_Windows" target="_blank">from the official wiki</a>. Although CouchDB is able to run on Windows, it is not yet officially supported. Once you have the files on your computer, you&#8217;re almost done! If you downloaded <a href="http://www.jdecuyper.com/wp-content/uploads/2010/08/jdecuyper_couchdb-0.9-windows.zip" target="_blank">the first build mentioned</a> you will only need to launch the couch_start.bat file inside the bin directory and the server will be loaded in seconds:</p>
<p style="text-align: center;"><a href="http://www.jdecuyper.com/wp-content/uploads/2010/08/couchdb_start.jpg" target="_blank"><img class="aligncenter size-full wp-image-285" title="couchdb_start" src="http://www.jdecuyper.com/wp-content/uploads/2010/08/couchdb_start.jpg" alt="couchdb_start" width="259" height="306" /></a></p>
<p style="text-align: justify;">The console should now appear and tell you to relax:</p>
<p style="text-align: center;"><a href="http://www.jdecuyper.com/wp-content/uploads/2010/08/couchdb_console.jpg" target="_blank"><img class="aligncenter size-full wp-image-289" title="couchdb_console" src="http://www.jdecuyper.com/wp-content/uploads/2010/08/couchdb_console.jpg" alt="couchdb_console" width="549" height="126" /></a></p>
<p style="text-align: justify;">You should now be able to navigate to your server at: http://127.0.0.1:5984/_utils. In order to query your CouchDB server, you will require a C# API. Several API already exists. I gave it a try with <a href="http://code.google.com/p/couchbrowse/source/browse/trunk/SharpCouch/SharpCouch.cs" target="_blank">SharpCouch</a>. I attached a <a href="http://www.jdecuyper.com/wp-content/uploads/2010/08/jdecuyper.com_CouchDB.rar" target="_blank">small console C# project</a> that uses SharpCouch to list all databases available inside your server:</p>
<p style="text-align: justify;"><img class="aligncenter size-full wp-image-295" title="couchDB_list_db" src="http://www.jdecuyper.com/wp-content/uploads/2010/08/couchDB_list_db1.JPG" alt="couchDB_list_db" width="477" height="140" /></p>
<p style="text-align: justify;">
]]></content:encoded>
			<wfw:commentRss>http://www.jdecuyper.com/?feed=rss2&amp;p=256</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Older version of StringEnum throws error when accessed by multiple threads</title>
		<link>http://www.jdecuyper.com/?p=206</link>
		<comments>http://www.jdecuyper.com/?p=206#comments</comments>
		<pubDate>Tue, 13 Jul 2010 22:30:43 +0000</pubDate>
		<dc:creator>jdecuyper</dc:creator>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[Enumeration]]></category>
		<category><![CDATA[StringEnum]]></category>
		<category><![CDATA[Threads]]></category>

		<guid isPermaLink="false">http://www.jdecuyper.com/?p=206</guid>
		<description><![CDATA[A C# enumeration refers to a set of named constants. Every value inside an enumerator list has a default integer type which starts at zero and, if not specified otherwise, gets automatically increased by one:

enum WebsiteType {
Blog = 0,
News = 1,
Youtube = 2  };

Which is completely equivalent to:

enum WebsiteType {
Blog,
News,
Youtube  };
But not to:

enum WebsiteType {
Blog = [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: justify;">A C# enumeration refers to a set of named constants. Every value inside an enumerator list has a default integer type which starts at zero and, if not specified otherwise, gets automatically increased by one:</p>
<pre class="brush: c#">
enum WebsiteType {
Blog = 0,
News = 1,
Youtube = 2  };
</pre>
<p>Which is completely equivalent to:
<pre class="brush: c#">
enum WebsiteType {
Blog,
News,
Youtube  };</pre>
<p>But not to:
<pre class="brush: c#">
enum WebsiteType {
Blog = 2,
News = 4,
Youtube = 6  };</pre>
<p style="text-align: justify;">Enumeration values can only be associated with integer values, if you want to associate an enum value with a string you will need to enhance the enumeration&#8217;s behavior. To achieve it,  I have been using the <a title="Code Associate (StringEnum)" href="http://www.codeassociate.com/caapi/" target="_blank">“StringEnum” class from the Code associate .NET project</a> for quite a while now.  The StringEnum class is a helper class that allows you to associate string value to enum values such as:</p>
<pre class="brush: c#">
public enum Animal {
[StringValue(&quot;Domestic cat&quot;)]
CAT = 0,
[StringValue(&quot;Domestic dog&quot;)]
DOG = 1,
[StringValue(&quot;Domestic golden fish&quot;)]
FISH = 3
}
</pre>
<p style="text-align: justify;">Once you need to retrieve the value associated with a specific value of the enumeration, you can do the following:</p>
<pre class="brush: c#">
string enumValue = &quot;&quot;;
enumValue = StringEnum.GetStringValue(Animal.CAT))
</pre>
<p style="text-align: justify;">It offers a very practical behavior that I have been using throughout a lot of different projects. Nevertheless, I recommend you to use the up-to date version of the class since the one I have been using is not thread safe. I downloaded it a while ago and never bothered to check for a newer version.</p>
<p style="text-align: justify;">Recently, I created a small ASP.NET image gallery for a contest around the anniversary of the Mexican revolution and independence. People were given the right to upload an image and to vote for an image. I configured <a href="http://logging.apache.org/log4net/index.html" target="_blank">log4net</a> in such a way that every error that got caught was sent to me by email. It is that way I discovered that an error is sometimes raised when different threads are working with the StringEnum class. I found it quite interesting since I have been using log4net and StringEnum on a lot of projects and the error had never be thrown before.</p>
<p style="text-align: justify;">For performance reason, the StringEnum class holds a static hashtable with all the string values from the enumeration. This helps reducing the look-up costs caused by the reading of the enumerator&#8217;s attributes. When you request the StringValue of a particular enumeration value, the StringEnum class queries its hashtable. If it founds the value then it returns it otherwise it adds the new requested value.</p>
<p style="text-align: justify;">The problem is that the hashtable is a static attribute which means it is shared by all the instances of the StringEnum class. Since there is no control over the insertion into the hashtable, when several threads were accessing it, a &#8220;System.ArgumentException&#8221; could be thrown:</p>
<p style="text-align: center;"><img class="aligncenter size-full wp-image-243" title="error_stringEnum" src="http://www.jdecuyper.com/wp-content/uploads/2010/07/error_stringEnum.jpg" alt="error_stringEnum" width="651" height="277" /></p>
<p style="text-align: justify;">Although the error message is shown in Spanish, it is quite clear that a thread was trying to add a value to the hashtable that already existed! This is not a big surprise since the <a href="http://msdn.microsoft.com/en-us/library/system.collections.hashtable.aspx" target="_blank">MSDN documentation</a> states that a &#8220;<em>Hashtable is thread safe for use by multiple reader threads and a single writing thread</em>&#8220;.  When working with multiple writing threads, things starts to go wrong.</p>
<p style="text-align: justify;"><a href="http://www.jdecuyper.com/wp-content/uploads/2010/07/jdecuyper_StressTestForStringEnum.rar" target="_blank">I uploaded a small 2008 Visual Studio solution</a> with a performance test that I applied to the old, the new version of the StringEnum class, and also to a custom version I wrote. Before writing to the hashtable, every thread must request a lock on it, this prevents multiple writing from happening. We need to make sure that while a thread is writing a new value to the hashtable, no other threads can come in and do the same thing. This is where you need to use a lock.</p>
<p style="text-align: justify;">The new version of  StringEnum from the Code Associate locks directly on the hashtable. Although this approach is working, I prefer to use an additional variable to lock on. Creating &#8220;lock variables&#8221; makes your code a bit cleaner and prevent from having two pieces of code blocking on the same object. In the <a href="http://www.yoda.arachsys.com/csharp/threads/lockchoice.shtml" target="_blank">words of John Skeet</a>: &#8220;<em>I believe this is a bad idea </em>(locking on a instance)<em>, because it means you have less control over your locks. Other code may well end up locking on the same object as you do within your code, which makes it far harder to ensure that you only obtain locks in an appropriate order</em>&#8220;.</p>
<p style="text-align: justify;">If you try out the solution, you will see that the old version of StringEnum (CA_old directory) fails while the newer version (CA_new directory) and mine (CA_custom directory) pass the test without throwing exception. Finally, if you look through my code you will see I added a line of code inside every StringEnum  class that forces the thread to sleep for 12 milliseconds. I added this line in order to provoke more quickly a writing/reading error from a thread. And for those how prefer not to download the project, the method looks like the following, where _stringValue is our hastable:</p>
<p style="text-align: center;"><img class="aligncenter size-full wp-image-248" title="GetStringValueWithSleep" src="http://www.jdecuyper.com/wp-content/uploads/2010/07/GetStringValueWithSleep.jpg" alt="GetStringValueWithSleep" width="808" height="412" /></p>
<p><a href="http://www.jdecuyper.com/wp-content/uploads/2010/07/jdecuyper_StressTestForStringEnum.rar" target="_blank">Download the 2008 Visual Studio solution with the test case</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jdecuyper.com/?feed=rss2&amp;p=206</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>DotNetWinService versión 1.0.0.0 liberado</title>
		<link>http://www.jdecuyper.com/?p=195</link>
		<comments>http://www.jdecuyper.com/?p=195#comments</comments>
		<pubDate>Sat, 01 May 2010 16:46:32 +0000</pubDate>
		<dc:creator>jdecuyper</dc:creator>
				<category><![CDATA[DotNetWinService]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Visual Studio 2008]]></category>
		<category><![CDATA[Windows Service]]></category>

		<guid isPermaLink="false">http://www.jdecuyper.com/?p=195</guid>
		<description><![CDATA[Acabo de liberar la versión 1.0.0.0 de DotNetWinService en codeplex. Escogí codeplex porque su navegador de código fuente es bastante práctico y claro para consultar archivos del proyecto:
DotNetWinService es una mezcla de varias tecnologías entras las cuales se encuentran log4Net, Spring.NET y Quartz.NET. Permite implementar de forma declarativa (con XML) tareas programadas adentro de un servicio Windows. Las [...]]]></description>
			<content:encoded><![CDATA[<p>Acabo de liberar <a href="http://dotnetwinservice.codeplex.com/" target="_blank">la versión 1.0.0.0 de DotNetWinService en codeplex</a>. Escogí codeplex porque su navegador de código fuente es bastante práctico y claro para consultar archivos del proyecto:</p>
<div id="attachment_197" class="wp-caption aligncenter" style="width: 621px"><img class="size-full wp-image-197" title="DotNetWinServiceCodePlexBrowsesource" src="http://www.jdecuyper.com/wp-content/uploads/2010/04/DotNetWinServiceCodePlexBrowsesource.jpg" alt="DotNetWinServiceCodePlexBrowsesource" width="611" height="494" /><p class="wp-caption-text">Navegador de archivos fuentes</p></div>
<p style="text-align: justify; ">DotNetWinService es una mezcla de varias tecnologías entras las cuales se encuentran <a href="http://logging.apache.org/log4net/" target="_blank">log4Net</a>, <a href="http://www.springframework.net/" target="_blank">Spring.NET</a> y <a href="http://logging.apache.org/log4net/" target="_blank">Quartz.NET</a>. Permite implementar de forma declarativa (con XML) tareas programadas adentro de un servicio Windows. Las tareas se definen adentro del archivo spring-objects.xml:</p>
<div id="attachment_198" class="wp-caption aligncenter" style="width: 622px"><img class="size-full wp-image-198   " title="DotNetWinServiceSpringContext" src="http://www.jdecuyper.com/wp-content/uploads/2010/04/DotNetWinServiceSpringContext.jpg" alt="Archivo spring-context.xml" width="612" height="298" /><p class="wp-caption-text">Archivo spring-objects.xml</p></div>
<p>Existen, por lo pronto, 4 tipos de tareas disponibles:</p>
<p><strong>TaskURL,</strong> para ejecutar una petición HTTP: cuando se trabaja sobre un sitio web en ASP.NET, a veces conviene mejor mantener toda la lógica de negocio adentro de una página que se ejecuta en un intervalo regular. Puede servir para generar un reporte y mandarlo por correo una vez por semana por ejemplo.</p>
<p><strong>TaskEXE,</strong> para ejecutar un archivo EXE o BAT: éste procesa se carga solamente si es el único cargado en memoria, no pueden existir varias instancias del mismo proceso corriendo al mismo tiempo. Puede servir para realizar tareas más pesadas como procesar imágenes o generar reportes que ocupen más tiempo de computación.</p>
<p><strong>TaskMethod,</strong> para disparar un método adentro de un assembly: solamente se soportan tipos primitivos y cadenas (System.String). En lugar de tener un archivo EXE, se carga el assembly en memoria, y mediante reflexión, se ejecuta un método (estático o de instancia).</p>
<p><strong>TaskMethodInterop,</strong> para intercambiar datos entre dos métodos en el mismo assembly o en assemblies diferentes: el valor de retorno del primer método se convierte en el valor de entrada del segundo método. El parámetro es de tipo System.String.</p>
<p>El proyecto está desarrollado con C# y Visual Studio 2008. No duden en comentar o en aportar código.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jdecuyper.com/?feed=rss2&amp;p=195</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>¿Por qué concatenar cadenas es muy, muy mala práctica?</title>
		<link>http://www.jdecuyper.com/?p=174</link>
		<comments>http://www.jdecuyper.com/?p=174#comments</comments>
		<pubDate>Sat, 10 Apr 2010 01:06:44 +0000</pubDate>
		<dc:creator>jdecuyper</dc:creator>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Regex]]></category>

		<guid isPermaLink="false">http://www.jdecuyper.com/?p=174</guid>
		<description><![CDATA[Hace unos días Kirill Osenkov, ingeniero que trabaja en el equipo del IDE Microsoft Visual Studio C#, posteó una pregunta de entrevista que se le hizo interesante:
En un string dado de .NET (en Unicode), considera que existen saltos de línea estándares en la forma \r\n (el equivalente de Environment.NewLine).
Escribe un método que inserte espacio entre dos [...]]]></description>
			<content:encoded><![CDATA[<p>Hace unos días Kirill Osenkov, ingeniero que trabaja en el equipo del IDE Microsoft Visual Studio C#, posteó una <a href="http://blogs.msdn.com/kirillosenkov/archive/2010/03/25/interview-question.aspx" target="_blank">pregunta de entrevista que se le hizo interesante</a>:</p>
<blockquote><p>En un string dado de .NET (en Unicode), considera que existen saltos de línea estándares en la forma \r\n (el equivalente de Environment.NewLine).<br />
Escribe un método que inserte espacio entre dos saltos de líneas consecutivos con el fin de separarlos.</p></blockquote>
<p style="text-align: justify;">Me llamó mucho la atención, en primer lugar, porque en agosto del 2009 había participado en el proceso de entrevistas para trabajar como intern en Redmond. No recibí ninguna oferta pero este año, lo intentaré otra vez <img src='http://www.jdecuyper.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> . Y en segundo lugar, porque desde mis entrevistas quedé muy aficionado a este tipo de preguntas técnicas sobre estructuras de datos o recursión entre otros. Dado que había encontrado el post poco tiempo después de su publicación, tuve tiempo de escribir una de las primeras respuestas. Tiene muchos defectos pero también contiene unos elementos interesantes:</p>
<pre class="brush: c#">
string text = &quot;hello \r\n\r\n\r\n world!&quot;;
string textResult = &quot;&quot;;
char[] caFromText = text.ToCharArray();
for (int i = 0; i &lt; caFromText.Length; ++i)
{
textResult += caFromText[i];
if ((int)caFromText[i] == CARRIAGE_RETURN)
{
// check if not reaching the end of the array
if (i + 3 &lt; caFromText.Length)
{
if ((int)caFromText[i + 1] == NEW_LINE
&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp; (int)caFromText[i + 2] == CARRIAGE_RETURN
&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp; (int)caFromText[i + 3] == NEW_LINE)
{
// two consecutive breaks where detected
textResult += &quot;\n &quot;;
// jump to the next break
++i;
}
}
}
}
</pre>
<p style="text-align: justify;">Mi idea fue transformar la cadena en un arreglo de caracteres. Luego, iterar sobre cada caracter buscando una secuencia consecutiva de saltos de líneas. Para evitar salirse del arreglo, utilicé un centinela que valida si ya estamos llegando al final del arreglo. Cuando se cumple la condición, se concatena un espacio a la cadena &#8216;textResult&#8217; que nos sirve para almacenar el resultado final.</p>
<p><a href="http://www.rikkus.info/" target="_blank">Rik Hemsley</a> recompiló todas las repuestas que se propusieron en una <a href="http://code.google.com/p/kirill-question/" target="_blank">bonita solución de Visual Studio</a>. Además, agregó una serie de test para comprobar la validez de todas las implementaciones:</p>
<p><img class="aligncenter size-full wp-image-176" title="HSBenchmakrPlayground" src="http://www.jdecuyper.com/wp-content/uploads/2010/04/HSBenchmakrPlayground.png" alt="HSBenchmakrPlayground" width="418" height="480" /></p>
<p style="text-align: justify;">Me dio gusto ver que mi algoritmo sí pasó todas las pruebas mínimas requeridas para agregar un espacio entre dos saltos de líneas consecutivos. Sin embargo, la velocidad de resolución no era nada buena dado que hace uso de una cadena para concatenar el resultado. Sabía que no era la forma correcta de hacerlo, de hecho lo mencioné <a href="http://blogs.msdn.com/kirillosenkov/archive/2010/03/25/interview-question.aspx#ctl00___ctl00___ctl01___Comments___Comments_ctl04_PermaLink" target="_blank">en mi comentario</a>, pero no me imaginaba lo malo que podría resultar ser la concatenación. Usando el benchmark de Rik Hemsley pude detectar que mi algoritmo lograba parsear un archivo de 2,106,233 caracteres en aproximadamente 107 minutos o 1 hora y 47 minutos!</p>
<p style="text-align: justify;">Para remediar al problema, cambié la cadena por la clase StringBuilder. Las cadenas en .NET framework son inmutables: cuando concatenamos una cadena, cada vez se crea un nuevo objecto de tipo String en memoria con el valor antiguo más el valor a concatenar. El método Append de la clase StringBuilder permite evitar la creación de una cadena a cada concatenación. A continuación, viene mi versión revisada:</p>
<pre class="brush: c#">
using NUnit.Framework;

namespace KirilQuestion.Implementations
{
[TestFixture]
public class JdecuyperRevisited : InsertSpacesFixture
{
private const char CARRIAGE_RETURN = &#039;\r&#039;;
private const char NEW_LINE = &#039;\n&#039;;

public override string InsertSpaceBetweenCrLfs(string input)
{
var textResult = &quot;&quot;;

var caFromText = input.ToCharArray();

for (var i = 0; i &lt; caFromText.Length; ++i)
{
textResult += caFromText[i];

if (caFromText[i] == CARRIAGE_RETURN)
{
// check if not reaching the end of the array

if (i + 3 &lt; caFromText.Length)
{
if (caFromText[i + 1] == NEW_LINE
&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp; caFromText[i + 2] == CARRIAGE_RETURN
&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp; caFromText[i + 3] == NEW_LINE)
{
// two consecutive breaks where detected
textResult += &quot;\n &quot;;

// jump to the next break
++i;
}
}
}
}
return input.Replace(&quot;\r\n\r\n&quot;, &quot;\r\n \r\n&quot;);
}
}
}
</pre>
<p style="text-align: justify;">Esta última versión de mi código procesó el mismo archivo en 58.5738 milisegundos, más de 10.000 veces más rápido que mi primera versión. Usar la concatenación genera un costo altísimo que más nos vale evitar. Sin embargo, no olviden que <a href="http://www.yoda.arachsys.com/csharp/stringbuilder.html" target="_blank">no en todas las situaciones se recomienda ocupar un StringBuilder</a>. No usen un StringBuilder cuando las concatenaciones son mínimas ya que seguramente harán un pequeño pedazo de código mucho más complejo de leer además de crear un objeto adicional en memoria. Aunque para pequeñas concatenaciones StringBuilder es más eficiente, nunca lo es de forma significativa.</p>
<p>Si descargan el benchmark, podrán ver que la solución más rápida es la de <a href="http://blog.jordanterrell.com/" target="_blank">Jordan Terrell</a> y hace uso de una expresión regular muy potente:</p>
<pre class="brush: c#">
string output = Regex.Replace(input, @&quot;(\r\n)(?=\r\n)&quot;, &quot;$1 &quot;);
</pre>
<p style="text-align: justify;">La expresión regular permite parsear el archivo en menos de 18 milisegundos! Aún 3 veces más rápido que mi última solución. Para entender la expresión regular, vamos a trabajar con un ejemplo un poco más sencillo donde remplacé &#8216;\r\n&#8217; por &#8216;_&#8217;. Nuestro texto de entrada será:</p>
<pre class="brush: c#">
string input = &quot;hello world __&quot;;
</pre>
<p>Aplicamos primero una expresión regular sencilla:</p>
<pre class="brush: c#">
string outWithoutGroups = Regex.Replace(input, @&quot;__&quot;, &quot;_ _&quot;);
</pre>
<p>El resultado se ve bastante bien ya que aparece un espacio entre los dos _:</p>
<pre class="brush: c#">
hello world _ _
</pre>
<p>Sin embargo, si cambiamos un poco el texto de entrada las cosas se complican:</p>
<pre class="brush: c#">
string input = &quot;hello world______&quot;;
</pre>
<p>La salida es ahora la siguiente:</p>
<pre class="brush: c#">
hello world _ __ __ _
</pre>
<p style="text-align: justify;">¿Que estará pasando? Después de las palabras &#8220;hello world&#8221; aparecen 6 carateres que vamos a nombrar 1, 2, 3, 4, 5 y 6 (son los 6 underscores). Cada vez que la expresión realiza un match, i.e. encuentra dos caracteres _ consecutivos, los reemplaza por _ _. El primer match procesa los caracteres 1 y 2. Luego se sigue con el 3 y el 4 y finalmente con el 5 y el 6. El problema es que con esta expresión, no se procesaron los caracteres 3 y 5. Y es por eso que no aparecen espacios entre el 2 y el 3 y entre el 4 y el 5.</p>
<p style="text-align: left;">Kirill Osenkov realizó una imagen que explica claramente lo que no está procesando nuestra expresión regular:</p>
<p style="text-align: center;"><img class="aligncenter size-full wp-image-192" title="reemplazar_underscore" src="http://www.jdecuyper.com/wp-content/uploads/2010/04/reemplazar_underscore.jpg" alt="reemplazar_underscore" width="410" height="150" /></p>
<p style="text-align: justify;">Para remediar ese problema, Jordan Terrell utilizó un constructor llamado <a href="http://www.regular-expressions.info/lookaround.html" target="_blank">lookahead assertion</a> que fue introducido en su tiempo por Perl 5. La aserción se representa mediante &#8216;?=&#8217; y permite hacer un match de un _ seguido de otro, sin embargo  el segundo _ no es parte del match y será evaluado nuevamente por su cuenta. Lo cual permite generar la salida correcta:</p>
<pre class="brush: c#">
hello world _ _ _ _ _ _
</pre>
<p>¿Alguien ha sido confrontado con preguntas de este tipo?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jdecuyper.com/?feed=rss2&amp;p=174</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>No olvide especificar ImageFormat al guardar una imagen en C#</title>
		<link>http://www.jdecuyper.com/?p=155</link>
		<comments>http://www.jdecuyper.com/?p=155#comments</comments>
		<pubDate>Fri, 19 Mar 2010 23:14:44 +0000</pubDate>
		<dc:creator>jdecuyper</dc:creator>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[ImageFormat]]></category>

		<guid isPermaLink="false">http://www.jdecuyper.com/?p=155</guid>
		<description><![CDATA[Ya que las imágenes son recursos generalmente pesados, siempre hay que poner mucha atención al manipularlas. Me di cuenta de lo anterior al generar thumbnails a partir de imágenes de una galería web. La primera versión de mi código era la siguiente:

Image imagenFormatoOriginal = Image.FromFile(Directory.GetCurrentDirectory() + &#34;//test.jpg&#34;);
Image imagenFormatoThumb = imagenFormatoOriginal.GetThumbnailImage(1000, 1000, null, IntPtr.Zero);
imagenFormatoOriginal.Dispose();
imagenFormatoThumb.Save(&#34;test_thumbnail.jpg&#34;);

La imagen original [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: justify;">Ya que las imágenes son recursos generalmente pesados, siempre hay que poner mucha atención al manipularlas. Me di cuenta de lo anterior al generar thumbnails a partir de imágenes de una galería web. La primera versión de mi código era la siguiente:</p>
<pre class="brush: c#">
Image imagenFormatoOriginal = Image.FromFile(Directory.GetCurrentDirectory() + &quot;//test.jpg&quot;);
Image imagenFormatoThumb = imagenFormatoOriginal.GetThumbnailImage(1000, 1000, null, IntPtr.Zero);
imagenFormatoOriginal.Dispose();
imagenFormatoThumb.Save(&quot;test_thumbnail.jpg&quot;);
</pre>
<p style="text-align: justify;">La imagen original pesa 1,002 KB y tiene dimensiones de 4096 por 6144 pixeles. La imagen thumbnail generada pesa 361 KB con dimensiones de 1000 por 1000. La nueva imagen representa un 36.02% del peso de la imagen original. Puede parecer un buen ratio pero en una galería de cientos de imágenes cada byte se vuelve importante tanto para el disco duro como para el ancho de banda. En el siguiente código, especificamos el formato en el cual deseamos crear el thumbnail:</p>
<pre class="brush: c#">
Image imagenFormatoOriginal = Image.FromFile(Directory.GetCurrentDirectory() + &quot;//test.jpg&quot;);
Image imagenFormatoThumb = imagenFormatoOriginal.GetThumbnailImage(1000, 1000, null, IntPtr.Zero);
imagenFormatoOriginal.Dispose();
imagenFormatoThumb.Save(&quot;test_thumbnail_con_formato.jpg&quot;, ImageFormat.Jpeg);
</pre>
<p style="text-align: justify;">Ahora, la imagen thumbnail generada pesa 29KB y tiene dimensiones de 1000 por 1000 pixeles, lo cual representa un 0,34% del peso de la imagen original ¿Que estará suciediendo? El <a href="http://msdn.microsoft.com/es-mx/library/ktx83wah.aspx">artículo de MSDN sobre el método &#8216;Save&#8217;</a> indica que cuando no se especifica ningún codificador, se ocupa por default el codificador del formato de gráficos de red portátiles (PNG/Portable Network Graphics). Creo que sería un mejor diseño obligar el programador a escoger siempre el formato en el cuál desea guardar su imagen o que el método &#8216;Save&#8217; trate de adivinar el formato de la imagen al guardarla. Si examinamos el header de nuestros dos thumbnails generados corroboramos lo anterior. El thumbnail generado sin formato tiene un header específico del formato <a href="http://es.wikipedia.org/wiki/Portable_Network_Graphics">PNG</a>:</p>
<div id="attachment_156" class="wp-caption aligncenter" style="width: 644px"><img class="size-full wp-image-156" title="imagen_formato_png_hex" src="http://www.jdecuyper.com/wp-content/uploads/2010/03/imagen_formato_png_hex.png" alt="Imagen en formato PNG" width="634" height="74" /><p class="wp-caption-text">Imagen en formato PNG</p></div>
<p>En cambio, el header de la segunda imagen presenta las características del formato JPG, mucho más compacto y de menor calidad que PNG:</p>
<div id="attachment_157" class="wp-caption aligncenter" style="width: 640px"><img class="size-full wp-image-157" title="imagen_formato_jpg_hex" src="http://www.jdecuyper.com/wp-content/uploads/2010/03/imagen_formato_jpg_hex.png" alt="Imagen en formato JPG" width="630" height="63" /><p class="wp-caption-text">Imagen en formato JPG</p></div>
]]></content:encoded>
			<wfw:commentRss>http://www.jdecuyper.com/?feed=rss2&amp;p=155</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Microsoft SQL Server Management Studio 2008: Error 916</title>
		<link>http://www.jdecuyper.com/?p=141</link>
		<comments>http://www.jdecuyper.com/?p=141#comments</comments>
		<pubDate>Fri, 15 Jan 2010 20:01:44 +0000</pubDate>
		<dc:creator>jdecuyper</dc:creator>
				<category><![CDATA[MSSQL]]></category>
		<category><![CDATA[Microsoft SQL Server]]></category>

		<guid isPermaLink="false">http://www.jdecuyper.com/?p=141</guid>
		<description><![CDATA[Encontré un problema al tratar de conectarme con Microsoft SQL Server Management Studio 2008 (SSM) a una instancia de SQL Server 2008. La base de datos se encuentra en un servidor de desarrollo que tenemos en la oficina. Después de conectarme a la base con credenciales de Windows, por alguna razón no lograba acceder a [...]]]></description>
			<content:encoded><![CDATA[<p>Encontré un problema al tratar de conectarme con Microsoft SQL Server Management Studio 2008 (SSM) a una instancia de SQL Server 2008. La base de datos se encuentra en un servidor de desarrollo que tenemos en la oficina. Después de conectarme a la base con credenciales de Windows, por alguna razón no lograba acceder a las bases de datos. Al dar click sobre el nodo &#8220;Database&#8221;, dentro del Explorar del &#8220;Object Explorer&#8221;, me arroja el siguiente mensaje:</p>
<div id="attachment_143" class="wp-caption aligncenter" style="width: 486px"><img class="size-full wp-image-143     " title="Microsoft SQL Server Management Studio 2008 - Error 916" src="http://www.jdecuyper.com/wp-content/uploads/2010/01/ssms_error.png" alt="Microsoft SQL Server Management Studio 2008 - error" width="476" height="168" /><p class="wp-caption-text">Microsoft SQL Server Management Studio 2008 - Error 916</p></div>
<p>Cuando se expande el nodo de bases de datos, SSM recolecta información sobre cada base de datos del servidor. Esa información es la misma que aparece en el &#8220;Object Explorer Details View&#8221; (pueden desplegar esa vista tecleando F7 una vez conectado a la instancia en SSM). El problema es que la información a recolectar no está disponible para las bases de datos apagadas o con la propiedad &#8220;<a href="http://msdn.microsoft.com/en-us/library/ms135094.aspx">AutoClose</a>&#8221; prendida.  Si SSM no logra recibir toda la información que requiere, ejecuta un query hacia las bases de datos para tratar de completar su recolección de datos. Dos problemas pueden suceder:</p>
<li> Si muchas bases de datos tienen la propiedad &#8220;AutoClose&#8221; prendida, la lista puede tardar mucho tiempo en desplegarse ya que SSM tiene que abrir una conexión y consultar todas las bases</li>
<li> Si el usuario no tiene derechos para consultar una base de datos, se arroja un error en SSM</li>
<p>En mi caso, es obviamente la segundo opción que aplica. <strong>Lo único que se puede realizar es apagar la propiedad &#8220;AutoClose&#8221; de todas las bases de datos</strong> y asegurar que el siguiente query nunca contenga ningún registro:</p>
<pre class="brush: sql">SELECT * FROM sys.databases WHERE is_auto_close_on = 1</pre>
<p>El bug ya ha sido <a href="https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=355238" target="_blank">reportado en Microsoft</a>. Si también te encontraste con ese problema, regístrate y agrega un upvote.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jdecuyper.com/?feed=rss2&amp;p=141</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>La tarea no se ejecutó porque no se encontó AL.exe</title>
		<link>http://www.jdecuyper.com/?p=126</link>
		<comments>http://www.jdecuyper.com/?p=126#comments</comments>
		<pubDate>Mon, 26 Oct 2009 20:22:17 +0000</pubDate>
		<dc:creator>jdecuyper</dc:creator>
				<category><![CDATA[Visual Studio 2008]]></category>
		<category><![CDATA[al.exe]]></category>
		<category><![CDATA[Visual Studio Express 2005]]></category>
		<category><![CDATA[Visual Studio Express 2008]]></category>

		<guid isPermaLink="false">http://www.jdecuyper.com/?p=126</guid>
		<description><![CDATA[Recientemente, estuve trabajando en Visual Studio 2008 con varias soluciones hechas con la versión 2005 del IDE. Pude importar y convertirlas  sin mayor problema pero a la hora de compilar, varios proyectos empezaron a quejarse y se me presentó el siguiente error:

Error 42
La tarea no se ejecutó porque no se encontró &#34;AL.exe&#34; o porque [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: justify;">Recientemente, estuve trabajando en Visual Studio 2008 con varias soluciones hechas con la versión 2005 del IDE. Pude importar y convertirlas  sin mayor problema pero a la hora de compilar, varios proyectos empezaron a quejarse y se me presentó el siguiente error:</p>
<pre class="brush: c#">
Error 42
La tarea no se ejecutó porque no se encontró &quot;AL.exe&quot; o porque no está instalado el SDK correcto de Microsoft Windows.
La tarea busca &quot;AL.exe&quot; en el subdirectorio &quot;bin&quot;, bajo la ubicación especificada en el valor InstallationFolder de la clave del Registro HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v6.0A.
Para resolver el problema, realice una de las siguientes acciones:
1) Instale Microsoft Windows SDK para Windows Server 2008 y .NET Framework 3.5.
2) Instale Visual Studio 2008.
3) Establezca manualmente la clave del Registro especificada anteriormente en la ubicación correcta.
3.) Pase esta ubicación al parámetro &quot;ToolPath&quot; de la tarea.
</pre>
<p style="text-align: justify;">El mensaje indica que el proceso de compilación no encuentra el ejecutable &#8220;AL.exe&#8221;. El Assembly linker es una herramienta que ocupa Visual Studio para generar un assembly a partir de módulos escritos en MSIL (lenguaje intermedio de Microsoft) y archivos de recursos. El Assembly Linker viene incluido con el IDE, si no lo tienen instalado, deben descargar el <a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=e6e1c3df-a74f-4207-8586-711ebe331cdc&amp;DisplayLang=en">Microsoft SDK</a> e instalarlo. Si igual que yo, ya lo tienen instalado deben abrir el registro mencionado y verificar que el valor de la clave &#8220;<em>InstallationFolder</em>&#8221; apunte hacia la ubicación del Assembly Linker en su máquina. En caso que se requiera, deberán actualizar el valor de este registro. Después, también será necesario agregar una nueva variable de entorno  como lo pueden ver en la siguiente imagen:</p>
<div id="attachment_125" class="wp-caption aligncenter" style="width: 380px"><img class="size-full wp-image-125" title="Variable de entorno ALTOOLPATH" src="http://www.jdecuyper.com/wp-content/uploads/2009/10/variable_entorno_ALTOOLPATH.png" alt="ALTOOLPATH" width="370" height="182" /><p class="wp-caption-text">ALTOOLPATH</p></div>
<p style="text-align: justify;">El nombre de la variable de entorno es &#8220;ALTOOLPATH&#8221; y su valor en mi máquina es &#8220;C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\al.exe&#8221;. Ahora, abran Visual Studio y compilen la solución. Todo debería funcionar correctamente y si no, espero sus comentarios.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jdecuyper.com/?feed=rss2&amp;p=126</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>¿Cómo habilitar el servicio Full-text Indexing en MSSQL 2008?</title>
		<link>http://www.jdecuyper.com/?p=88</link>
		<comments>http://www.jdecuyper.com/?p=88#comments</comments>
		<pubDate>Wed, 21 Oct 2009 19:28:49 +0000</pubDate>
		<dc:creator>jdecuyper</dc:creator>
				<category><![CDATA[MSSQL]]></category>
		<category><![CDATA[Full-text]]></category>
		<category><![CDATA[indexing]]></category>

		<guid isPermaLink="false">http://www.jdecuyper.com/?p=88</guid>
		<description><![CDATA[La búsqueda de texto completo (full text search) es una técnica para buscar texto en un documento o en una base de datos. El motor de la búsqueda se encarga de examinar todas las palabras de un documento y las compara con las palabras proporcionadas por un usuario. La búsqueda es completa porque todas las palabras [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: justify;">La búsqueda de texto completo (full text search) es una técnica para buscar texto en un documento o en una base de datos. El motor de la búsqueda se encarga de examinar todas las palabras de un documento y las compara con las palabras proporcionadas por un usuario. La búsqueda es completa porque todas las palabras de los documentos o de la base de datos se ocupan. No todas las búsquedas son completas. En el caso de los sitios web, existe por ejemplo la búsqueda por tags que examina solamente el catálogo de tags y no el contenido tageado. Es una táctica menos costosa y que puede ser bastante potente si el contenido está correctamente tageado. Tambien vale la pena mencionar que el mismo sistema gestor de base de datos dispone de varios técnicas para consultar una tabla: <a href="http://grimpi.blogspot.com/2007/05/entender-plan-de-ejecucion-en-sql.html">Table Scan, Index Seek, etc.</a></p>
<p>¿Cuando se requiere ocupar la búsqueda de texto completo? Como todos lo saben, en SQL Server igual que en cualquier otro sistema gestor de base de datos ya existen comandos que permiten realizar búsquedas en tablas. Por ejemplo, si queremos buscar el país &#8216;México&#8217; en un catálogo de paises, basta con ejecutar el siguiente comando SQL:</p>
<pre class="brush: sql">
SELECT * FROM Pais WHERE nombre = &#039;México&#039;
</pre>
<p>Esto es más que suficiente para ese tipo de búsquedas. Sin embargo, existen casos dónde se requiere recurrir a consultas más complejas donde por ejemplo:</p>
<p>- se ocupan documentos o tablas con un volumen importante.<br />
- se realiza una búsqueda con más de una palabra o una frase.<br />
- la cercanía de las palabras es relevante: se busca la palabra &#8216;México&#8217; y &#8216;playa&#8217; con 5 palabras o menos entre cada uno.<br />
- se asignan pesos/importancias diferentes en cada palabra de una frase.<br />
- se necesita considerar las derivaciones de una palabra o de un verbo (ejemplo: correr, corrimos, corre).<br />
- se necesita realizar un búsqueda en campo binario (varbinary o image).<br />
- se toman en cuenta sinonimos (tesaurus)</p>
<p style="text-align: justify;">El Full-Text Engine que dispone SQL Server 2008 permite contestar de forma eficaz a estas consultas, sus táreas más importantes son la indexación y la consulta. La indexación consiste en la creación de una tabla donde se almacenan todas las palabras encontradas en los textos analizados. Por cada palabra se conservan datos como su posición en los documentos, su frecuencia,&#8230; No todas las palabras se indexan. El motor de indexación utiliza stoplists (compuestas de <a href="http://en.wikipedia.org/wiki/Stop_words">stop words</a>) para ignorar palabras como &#8220;de&#8221;, &#8220;la&#8221; o &#8220;en&#8221; con poca relevancia.</p>
<p style="text-align: justify;">Antes, con SQL Server 2000, la indexación se realizaba mediante el servicio MSSEARCH Service (<a href="http://msdn.microsoft.com/en-us/library/aa174506(SQL.80).aspx">Microsoft Search</a>). Este servicio presente en cada sistema operativo tenía la desventaja de ser compartido con otros procesos como SharePoint o Exchange. Se mejoró esa situación con SQL Server 2005 ya que se agregó funcionalidad para poder crear instancias exclusivas del servicio de búsqueda (<a href="http://msdn.microsoft.com/en-us/library/ms142541(SQL.90).aspx">Microsoft Full-Text Engine for SQL Server</a>). Sin embargo, una de las consecuencias negativas de esta arquitectura divida entre varios procesos es que los datos indexados no se están respaldando en la base de datos si no en el servicio de búsqueda (externo a la base de datos), lo cual podía complicar la tarea del DBA a la hora de realizar réplicas de una máquina a otra. Al contrario de la versión 2000 &amp; 2005, el Full-Text Engine de SQL Server 2008 está ahora totalmente integrado al mismo proceso de SQL Server como cualquier otro tipo de servicio de la base de datos. Este cambio estructural es muy importante y trae consigo beneficios tanto de rendimiento como de seguridad (<a href="http://msdn.microsoft.com/en-us/library/ms142541.aspx">leer más en MSDN</a>).</p>
<p style="text-align: justify;">En el siguiente ejemplo, vamos a crear un catálogo de indexación para una tabla de comentarios de un blog ficticio. El Full-Text Engine no está incluido en todas las versiones de SQL Server 2008. Si igual que yo trabajan con la versión Express, deben descargar la versión Express with <a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=B5D1B8C3-FDA5-4508-B0D0-1311D670E336&amp;displaylang=en">Advanced Services</a>. Pueden verificar que tengan el servicio instalado mediante la siguiente instrucción:</p>
<pre class="brush: sql">SELECT fulltextserviceproperty(&#039;isfulltextinstalled&#039;)</pre>
<p>Luego, vamos a crear una base de datos para hacer nuestras pruebas:</p>
<pre class="brush: sql">
USE master
IF DB_ID(&#039;full_text_db&#039;) IS NOT NULL
DROP DATABASE full_text_db
CREATE DATABASE full_text_db
GO
</pre>
<p style="text-align: justify;">Se tiene que verificar que nuestra base de datos tenga la búsqueda de texto completo activado:</p>
<pre class="brush: sql">SELECT DATABASEPROPERTY(&#039;full_text_db&#039;, &#039;IsFullTextEnabled&#039;);</pre>
<p>Si el servicio no está habilitado sobre la base de datos que creamos, lo pueden activar de la siguiente manera:</p>
<pre class="brush: sql">
USE full_text_db
GO
EXEC sp_fulltext_database &#039;enable&#039;
</pre>
<p>Ya con nuestra base de datos lista, podemos crear nuestro catálogo de indexación:</p>
<pre class="brush: sql">CREATE FULLTEXT CATALOG comentariosFullTextSearch</pre>
<p style="text-align: justify;">Creamos la tabla de comentarios cuyo contenido vamos a indexar. Es obligatorio crear un índice sobre esa tabla ya que el servicio de indexación lo requiere:</p>
<pre class="brush: sql">
CREATE TABLE Comentarios (
ID [int] IDENTITY(1,1) NOT NULL,
contenido VARCHAR(8000) NOT NULL,
CONSTRAINT [PK_Comentarios] PRIMARY KEY CLUSTERED
(
ID ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
</pre>
<p style="text-align: justify;">Nada más se tienen que indexar la columnas que se requieren. En nuestro caso vamos a avisar al Full-Text Engine que queremos indexar a la columna &#8216;contenido&#8217;. Mediante la instrucción &#8216;Language&#8217; indicamos en que idioma está el texto que contiene la tabla:</p>
<pre class="brush: sql">
CREATE FULLTEXT INDEX ON Articulo
(
contenido
Language 0XC0A -- Local ID para español - 3082/0XC0A
)
KEY INDEX PK_Articulo ON articulosFullTextSearch
WITH CHANGE_TRACKING AUTO
</pre>
<p style="text-align: justify;">Nada más nos queda llenar nuestra tabla con datos de prueba y lanzar la indexación. Por lo general, la indexación se dispara de forma automática y se actualiza regularmente cada vez que se cambian datos en una columna indexada por ejemplo:</p>
<pre class="brush: sql">
INSERT INTO Comentarios VALUES (&#039;México es muy lindo.&#039;)
INSERT INTO Comentarios VALUES (&#039;Me gusta Méchico.&#039;)
INSERT INTO Comentarios VALUES (&#039;¡Viva Méjico!&#039;)
GO
ALTER FULLTEXT INDEX ON Comentarios
START FULL POPULATION
</pre>
<p style="text-align: justify;">Si todo salió correctamente, ya deben de poder ejecutar la siguientes consultas y empezar a hacer uso del catálogo de indexación que creó el Full-Text Engine para nosotros:</p>
<pre class="brush: sql">
SELECT * FROM Comentarios WHERE CONTAINS(contenido, &#039;&quot;*México*&quot;&#039;)
SELECT * FROM Comentarios WHERE CONTAINS(contenido, &#039;&quot;México*&quot; OR &quot;Méjico&quot;&#039;)
</pre>
<p style="text-align: justify;">En mi siguiente post, les daré más explicación sobre los predicados CONTAINS y FREETEXT y sobre cómo armar consultas con sinónimos usando un archivo XML (tesaurus).</p>
<p><span style="text-decoration: underline;">FUENTES:</span><br />
<a href="http://en.wikipedia.org/wiki/Full_text_search">http://en.wikipedia.org/wiki/Full_text_search</a><br />
<a href="http://en.wikipedia.org/wiki/Full_text_search"></a><a href="http://msdn.microsoft.com/es-es/library/ms142571.aspx">http://msdn.microsoft.com/es-es/library/ms142571.aspx</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.jdecuyper.com/?feed=rss2&amp;p=88</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>¿Por qué desparece la opción de proyectos en visual Studio Express 2008?</title>
		<link>http://www.jdecuyper.com/?p=74</link>
		<comments>http://www.jdecuyper.com/?p=74#comments</comments>
		<pubDate>Fri, 02 Oct 2009 18:31:56 +0000</pubDate>
		<dc:creator>jdecuyper</dc:creator>
				<category><![CDATA[Visual Studio 2008]]></category>
		<category><![CDATA[Visual Studio Express 2008]]></category>
		<category><![CDATA[VSExpress2008]]></category>

		<guid isPermaLink="false">http://www.jdecuyper.com/?p=74</guid>
		<description><![CDATA[Ya van varias veces que por alguna misteriosa razón desaparece la opción de abrir/cerrar proyectos en mi versión Express de Visual Studio 2008. Hace poco desintalé SQL Server Express 2008 para luego instalar la versión avanzada (with Advanced Services) con el fin de aprovechar el motor de búsqueda Full-Text Search. Después de la instalación, había desaparecido la opción de [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: justify; ">Ya van varias veces que por alguna misteriosa razón desaparece la opción de abrir/cerrar proyectos en mi versión Express de Visual Studio 2008. Hace poco desintalé SQL Server Express 2008 para luego instalar la versión avanzada (with <a title="Sql Server Express 2008 with Advanced Services" href="http://www.microsoft.com/downloads/details.aspx?FamilyID=B5D1B8C3-FDA5-4508-B0D0-1311D670E336&amp;displaylang=en" target="_blank">Advanced Services</a>) con el fin de aprovechar el motor de búsqueda Full-Text Search. Después de la instalación, había desaparecido la opción de proyecto en el menú:</p>
<p><img class="size-full wp-image-75" title="vs2008_sinproyecto" src="http://www.jdecuyper.com/wp-content/uploads/2009/10/vs2008_sinproyecto.png" alt="VSExpress 2008 (sin opción de proyectos)" width="368" height="137" /></p>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">Para regresar esa opción:</div>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">1. Abre tu IDE</div>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">2. Herramientas -&gt; Importar y exportar configuraciones</div>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">3. Seleciona &#8220;Restablecer todas las configuraciones&#8221;</div>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">4. Puedes conservar tu configuración pero no es obligatorio</div>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">5. Selecciona &#8216;Finalizar&#8217; y listo!</div>
<p>Para regresar esa opción:</p>
<p>1. Abre tu IDE.</p>
<p>2. Herramientas -&gt; Importar y exportar configuraciones.</p>
<p>3. Seleciona &#8220;Restablecer todas las configuraciones&#8221;.</p>
<p>4. Puedes conservar tu configuración pero no es obligatorio.</p>
<p>5. Selecciona &#8216;Finalizar&#8217; y listo!</p>
<p><img class="size-full wp-image-76" title="vs2008_conproyecto" src="http://www.jdecuyper.com/wp-content/uploads/2009/10/vs2008_conproyecto.png" alt="VSExpress 2008 (con opción de proyectos)" width="365" height="179" /></p>
]]></content:encoded>
			<wfw:commentRss>http://www.jdecuyper.com/?feed=rss2&amp;p=74</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
