<?xml version="1.0" encoding="utf-8"?><rss version="2.0"><channel><title>c2i.fr - MySQL</title><link>http://www.c2i.fr:80/Tags/MySQL</link><description>c2i.fr - MySQL</description><item><title>Faire du Bulk Insert/Update avec MySQL en .NET</title><link>http://www.c2i.fr:80/articles/faire-du-bulk-insert-update-avec-mysql-en-.net</link><description>&lt;p&gt;&lt;img src="/Media/Default/BlogPost/articles/faire-du-bulk-insert-update-avec-mysql-en-.net/mysql.png" alt="" style="padding: 10px; float: right;" width="400" height="207" /&gt;Si vous &amp;ecirc;tes d&amp;eacute;veloppeur .NET, il y a de fortes chances pour que vous utilisiez une base de donn&amp;eacute;es de type Microsoft SQL Server (ou l&amp;rsquo;une de ses d&amp;eacute;riv&amp;eacute;es). Mais ce n&amp;rsquo;est pas le cas pour tout le monde et l&amp;rsquo;une des alternatives (gratuite) est bien entendu l&amp;rsquo;excellente MySQL.&lt;/p&gt;
&lt;p&gt;Heureusement, &lt;a href="http://www.mysql.fr/downloads/connector/net/" target="_blank"&gt;il existe un connecteur ADO .NET pour MySQL&lt;/a&gt; qui vous permet d&amp;rsquo;acc&amp;eacute;der &amp;agrave; votre base de donn&amp;eacute;es (en version 6.5.4 &amp;agrave; ce jour, 21 Mars 2012)&lt;/p&gt;
&lt;p&gt;Je ne vous ferais pas l&amp;rsquo;affront de vous expliquer comment cela marche avec ce connecteur, puisque c&amp;rsquo;est exactement le m&amp;ecirc;me principe que l&amp;rsquo;acc&amp;egrave;s &amp;agrave; une base SQL Server (avec les ExecuteReader, ExecuteNonQuery &amp;amp; co).&lt;/p&gt;
&lt;p&gt;Mais il y a bien entendu des sp&amp;eacute;cificit&amp;eacute;s, et l&amp;rsquo;une d&amp;rsquo;entre elle est la possibilit&amp;eacute; de faire du Bulk Insert/Update.&lt;/p&gt;
&lt;h2&gt;Bulk Insert/Update : K&amp;eacute;zako ?&lt;/h2&gt;
&lt;p&gt;Imaginons que vous ayez beaucoup de ligne &amp;agrave; ins&amp;eacute;rer dans votre base de donn&amp;eacute;es. La premi&amp;egrave;re solution (la plus catastrophique), est de faire une s&amp;eacute;rie d&amp;rsquo;insert. On peut voir cela de plusieurs fa&amp;ccedil;ons :&lt;/p&gt;
&lt;p&gt;Une boucle qui :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;appelle une m&amp;eacute;thode qui ouvre la connexion,&lt;/li&gt;
&lt;li&gt;effectue un ExecuteNonQuery avec comme requ&amp;ecirc;te, un INSERT INTO,&lt;/li&gt;
&lt;li&gt;ferme la connexion,&lt;/li&gt;
&lt;li&gt;passe &amp;agrave; la ligne suivante.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Catastrophe au niveau performance.&lt;/p&gt;
&lt;p&gt;Seconde solution, cr&amp;eacute;er dynamiquement une requ&amp;ecirc;te SQL avec autant de ligne INSERT INTO que de ligne &amp;agrave; ins&amp;eacute;rer :&lt;/p&gt;
&lt;p&gt;INSERT INTO maTable (Nom, Prenom) VALUES (&amp;lsquo;Clark&amp;rsquo;, &amp;lsquo;Richard&amp;rsquo;)&lt;br /&gt;INSERT INTO maTable (Nom, Prenom) VALUES (&amp;lsquo;Clark&amp;rsquo;, &amp;lsquo;Sabine&amp;rsquo;)&lt;br /&gt;INSERT INTO maTable (Nom, Prenom) VALUES (&amp;lsquo;Gates&amp;rsquo;, &amp;lsquo;Bill&amp;rsquo;)&lt;/p&gt;
&lt;p&gt;Vous pouvez optimiser votre script en verrouillant la table comme le montre &lt;a href="http://dev.mysql.com/doc/refman/5.0/fr/insert-speed.html" target="_blank"&gt;l&amp;rsquo;article sur MySQL&lt;/a&gt;. Inconv&amp;eacute;nients de cette m&amp;eacute;thode : les performances ne sont tout de m&amp;ecirc;me pas du feu de Dieu, et l&amp;rsquo;on doit verrouiller la table.&lt;/p&gt;
&lt;p&gt;Donc l&amp;rsquo;id&amp;eacute;al est de faire du Bulk Insert, ou plus exactement d&amp;rsquo;utiliser &lt;a href="http://dev.mysql.com/doc/refman/5.0/fr/load-data.html" target="_blank"&gt;la syntaxe LOAD DATA INFILE&lt;/a&gt; de MySQL.&lt;/p&gt;
&lt;h2&gt;Load Data Infile&lt;/h2&gt;
&lt;p&gt;Cette m&amp;eacute;thode permet d&amp;rsquo;uploader un fichier type csv dans MySQL et le moteur se charge de faire l&amp;rsquo;insertion. Cette m&amp;eacute;thode est jusqu&amp;rsquo;&amp;agrave; 20 fois plus rapide que celles &amp;eacute;voqu&amp;eacute;es plus haut. L&amp;rsquo;inconv&amp;eacute;nient par rapport &amp;agrave; un Bulk Insert pour SQL Server, c&amp;rsquo;est que vous &amp;ecirc;tes oblig&amp;eacute; de passer par un fichier (avec SQL Server, vous pouvez le faire &amp;ldquo;in-memory&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;Donc regardons comment proc&amp;eacute;der.&lt;/p&gt;
&lt;p&gt;NB: nous allons ins&amp;eacute;rer dans une table Persons des donn&amp;eacute;es repr&amp;eacute;sent&amp;eacute;es par la classe Person suivante :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;public class Person
{
    public int Id { get; set; }
    public string Nom { get; set; }
    public string Prenom { get; set; }
    public decimal SalaireMensuel { get; set; }
}
&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;La table Persons d&amp;eacute;finie l&amp;rsquo;Id comme un INT(11) qui est auto incr&amp;eacute;mentiel.&lt;/p&gt;
&lt;p&gt;MySqlBulkLoader est la classe fournis par le connecteur MySql qui va nous permettre de faire ce que l&amp;rsquo;on veut. Il est tr&amp;egrave;s simple d&amp;rsquo;utilisation. Parmi les propri&amp;eacute;t&amp;eacute;s importantes qu&amp;rsquo;il faut lui pr&amp;eacute;ciser, il y a :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le caract&amp;egrave;re s&amp;eacute;parateur de champs,&lt;/li&gt;
&lt;li&gt;Le caract&amp;egrave;re de fin de ligne,&lt;/li&gt;
&lt;li&gt;Le nom de la table MySql,&lt;/li&gt;
&lt;li&gt;Les colonnes &amp;agrave; consid&amp;eacute;rer,&lt;/li&gt;
&lt;li&gt;Le nombre de ligne dans le fichier csv &amp;agrave; ne pas prendre en compte au d&amp;eacute;but du fichier csv&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une fois que vous avez d&amp;eacute;termin&amp;eacute; tout cela, il ne vous restera plus qu&amp;rsquo;&amp;agrave; cr&amp;eacute;er dynamiquement le fichier.&lt;/p&gt;
&lt;p&gt;Donc premi&amp;egrave;re &amp;eacute;tape, cr&amp;eacute;er notre MySqlBulkLoader :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;public int BulkInsertPerson(IEnumerable&amp;lt;Person&amp;gt; persons)
{
    using (var connection = new MySqlConnection(ConnectionString))
    {
        var bl = new MySqlBulkLoader(connection);
        bl.TableName = "Persons";
        bl.FieldTerminator = "\t";
        bl.LineTerminator = "\r\n";
        bl.FileName = CreatePersonsFile(persons);
        bl.Columns.AddRange(new[]
                                {
                                    "Nom",
                                    "Prenom",
                                    "SalaireMensuel"
                                });

        bl.NumberOfLinesToSkip = 0;

        var inserted = bl.Load();
        return inserted;
    }
}
&lt;/pre&gt;
&lt;p&gt;Vraiment rien de bien compliqu&amp;eacute;. Je n&amp;rsquo;ai juste que la m&amp;eacute;thode CreatePersonsFile &amp;agrave; &amp;eacute;crire qui va cr&amp;eacute;er dynamiquement mon fichier csv. Pour ne pas avoir &amp;agrave; g&amp;eacute;rer ce fichier, je vais utiliser le dossier de fichiers temporaires de Windows (c&amp;rsquo;est lui qui fera le m&amp;eacute;nage &lt;img style="border-style: none;" class="wlEmoticon wlEmoticon-winkingsmile" alt="Clignement d'&amp;oelig;il" src="http://www.c2idotnet.com/Media/Default/Windows-Live-Writer/Faire-du-Bulk-InsertUpdate-avec-MySQ.NET_8001/wlEmoticon-winkingsmile_2.png" /&gt;) :&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;private static string CreatePersonsFile(IEnumerable&amp;lt;Person&amp;gt; persons)
{
    var fileName = Path.GetTempFileName();
    using (var writer = new StreamWriter(fileName))
    {
        foreach (var ligne in persons)
        {
            writer.WriteLine(
                string.Join("\t", new[]
                                      {
                                          ligne.Nom,
                                          ligne.Prenom,
                                          ligne.SalaireMensuel.ToString(CultureInfo.InvariantCulture)
                                      }));
        }
    }
    return fileName;
}
&lt;/pre&gt;
&lt;p&gt;Notez l&amp;rsquo;utilisation du InvariantCulture pour le decimal.&lt;/p&gt;
&lt;h2&gt;Bulk Insert/Update&lt;/h2&gt;
&lt;p&gt;Vous allez me dire : et si je veux faire de l&amp;rsquo;UPDATE et de l&amp;rsquo;INSERT en m&amp;ecirc;me temps ? C&amp;rsquo;est tout &amp;agrave; fait possible.&lt;/p&gt;
&lt;p&gt;Tout d&amp;rsquo;abord, vous avez vu que dans les colonnes, je n&amp;rsquo;avais pas sp&amp;eacute;cifi&amp;eacute; la colonne Id de ma table puisque c&amp;rsquo;est une colonne de type auto incr&amp;eacute;mentiel. Et bien si vous voulez faire de l&amp;rsquo;INSERT/UPDATE, il va falloir l&amp;rsquo;ajouter et de plus, il va falloir indiquer &amp;agrave; mySQL comment g&amp;eacute;rer les conflits. Pour cela, MySqlBulkLoader poss&amp;egrave;de une propri&amp;eacute;t&amp;eacute; ConflictOption : si MySql trouve une ligne avec le m&amp;ecirc;me Id, que doit-il faire ? Si conflictOption est &amp;agrave; Ignore, il ne fera rien, en revanche si il est &amp;agrave; Replace, il supprimera la ligne et la remplacera par les nouvelles donn&amp;eacute;es.&lt;/p&gt;
&lt;p&gt;Attention, dans ce cas, le nombre retourn&amp;eacute;e par la m&amp;eacute;thode est diff&amp;eacute;rente du nombre de ligne v&amp;eacute;ritablement affect&amp;eacute;es. Un UPDATE compte pour 2 (un DELETE et un INSERT), alors qu&amp;rsquo;un INSERT compte pour 1. Donc si vous envoyez x lignes et qu&amp;rsquo;il vous retourne y :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nombre UPDATE = Nombre retourn&amp;eacute; &amp;ndash; Nombre de ligne envoy&amp;eacute;es&lt;/li&gt;
&lt;li&gt;Nombre INSERT = 2*Nombre de ligne envoy&amp;eacute;es &amp;ndash; Nombre retourn&amp;eacute;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Pour en revenir &amp;agrave; notre code :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;public int BulkInsertUpdatePerson(IEnumerable&amp;lt;Person&amp;gt; persons)
{
    using (var connection = new MySqlConnection(ConnectionString))
    {
        var bl = new MySqlBulkLoader(connection);
        bl.TableName = "Persons";
        bl.FieldTerminator = "\t";
        bl.LineTerminator = "\r\n";
        bl.FileName = CreatePersonsFile(persons);
        bl.Columns.AddRange(new[]
                                {
                                    "id",
                                    "Nom",
                                    "Prenom",
                                    "SalaireMensuel"
                                });

        bl.NumberOfLinesToSkip = 0;

        bl.ConflictOption = MySqlBulkLoaderConflictOption.Replace;
        var inserted = bl.Load();
        return inserted;
    }
}
&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Et pour la cr&amp;eacute;ation du fichier, on prend comme convention que si notre Id est &amp;eacute;gal &amp;agrave; 0, c&amp;rsquo;est un INSERT qu&amp;rsquo;il faut faire. Dans on &amp;eacute;crit un null dans la colonne correspondante dans le fichier :&lt;/p&gt;
&lt;pre class="brush: csharp;"&gt;private static string CreatePersonsFile(IEnumerable&amp;lt;Person&amp;gt; persons)
{
    var fileName = Path.GetTempFileName();
    using (var writer = new StreamWriter(fileName))
    {
        foreach (var ligne in persons)
        {
            writer.WriteLine(
                string.Join("\t", new[]
                                      {
                                          ligne.Id == 0 ? null : ligne.Id.ToString(CultureInfo.InvariantCulture),
                                          ligne.Nom,
                                          ligne.Prenom,
                                          ligne.SalaireMensuel.ToString(CultureInfo.InvariantCulture)
                                      }));
        }
    }
    return fileName;
}
&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Et voil&amp;agrave;, en une seule m&amp;eacute;thode, vous pouvez faire des INSERT/UPDATE de masse dans votre base de donn&amp;eacute;es MySQL.&lt;/p&gt;</description><pubDate>Wed, 21 Mar 2012 08:50:26 GMT</pubDate><guid isPermaLink="true">http://www.c2i.fr:80/articles/faire-du-bulk-insert-update-avec-mysql-en-.net</guid></item></channel></rss>