Depuis toujours, Mercator utilise System.Data.SqlClient pour communiquer avec le serveur SQL. Cette librairie est remplacée par Microsoft.Data.SqlClient. L'ancienne librairie est considérée par Microsoft comme étant en "mode maintenance" et ne recevra donc plus de nouvelles fonctionnalités.
Microsoft recommande de migrer vers la nouvelle librairie
- pour des raisons de performances
- parce que la sécurité de cette librairie est améliorée
- parce qu'elle va permettre des développements orientés vers l'intelligence artificielle
- ...
Plus d'informations à ce propos sur cette page.
Mercator Core compilé avec .net 10 utilise Microsoft.Data.SqlClient. Pour un Mercator sans code sur mesure, ce changement est totalement transparent. En revanche, tout code personnalisé qui utilise System.Data.SqlClient doit être adapté puisque ce Mercator ne connaîtra pas cette librairie. Cette migration doit être effectuée avec soin et en respectant la possibilité de fonctionner en mode hybride, càd dans une même configuration avec des Mercator en version classique ou .net Core 8.0.
Afin de fluidifier cette transition, Mercator apporte une série de nouvelles classes, méthodes et propriétés, qui permettent de rendre le code "agnostique" par rapport à la librairie utilisée. Autrement dit, ces classes, méthodes et propriétés existent de façon identique dans toutes les versions de Mercator (.net 4.8, .net 8.0 et .net 10.0). C'est leur implémentation interne qui détermine si System.Data.SqlClient ou Microsoft.Data.SqlClient est utilisé.
✅ Si le code sur mesure est destiné à être utilisé uniquement dans un Mercator Core .net 10.0, alors il suffit de remplacer partout dans ce code :
System.Data.SqlClient par Microsoft.Data.SqlClient
et de recompiler. La suite de cette page ne s'applique pas.
➡️ Sinon, de façon générale, la modification d'un code sur mesure commence par le retrait de la clause
using System.Data.SqlClient;
qui se trouve dans le haut du code.
Si cette clause n'existe pas et si la chaîne "System.Data.SqlClient" ne se trouve pas dans ce code personnalisé, alors aucune modification ne doit être apportée.
Dans le cas contraire, le retrait de cette clause va provoquer quelques erreurs de compilations. Ce sont ces lignes qui devront être modifiées. Nous donnons ici quelques exemples de modifications à effectuer.
using (SqlCommand cmd = new SqlCommand("insert into ..."))
{
Api.SqlExec(Globals.RepData, cmd);
}
devient
using (MercatorSqlCommand cmd = new MercatorSqlCommand("insert into ..."))
{
Api.SqlExec(Globals.RepData, cmd);
}
👉 La classe SqlCommand appartient à l'espace de nom System.Data.SqlClient. Elle n'est donc plus utilisable. La classe MercatorSqlCommand se trouve dans MercatorApi. Ses paramètres sont identiques à ceux de SqlCommand mais elle est "agnostique" par rapport aux deux librairies. La méthode Api.SqlExec accepte indifféremment les deux types de commande.
using (SqlCommand cmd = new SqlCommand(reqSql))
{
cmd.Parameters.AddWithValue("@id", currentUserId).SqlDbType = SqlDbType.Char;
Api.SqlExec(MercatorUi.Globals.RepData, cmd);
}
devient
Api.SqlExec(MercatorUi.Globals.RepData, reqSql, new MercatorSqlParam("@id", currentUserId, SqlDbType.Char));
👉 Ici, on procède à une simplification du code qui ne nécessite plus d'utiliser SqlCommand.
using (MercatorSqlConnection conn = new MercatorSqlConnection(Globals.RepData, true))
{
if (conn.Connection == null)
return;
using (SqlTransaction transac = conn.Connection.BeginTransaction())
{
using (SqlCommand cmd = new SqlCommand("update ... delete ...", conn.Connection, transac))
{
if (!Api.SqlExec(cmd))
Api.SafeRollback(transac);
else
Api.SafeCommit(transac);
}
}
}
devient
using (MercatorSqlConnection conn = new MercatorSqlConnection(Globals.RepData, true))
{
if (!conn.IsConnected)
return;
using (MercatorSqlCommand cmd = new MercatorSqlCommand("update ... delete ..."))
{
Api.SqlExec(conn, cmd, underTransaction: true);
}
}
👉 Ici, on procède à une simplification du code qui ne nécessite plus d'utiliser SqlTransaction.
MercatorSqlParam mySqlParam = Api.SqlParamsGlobal.FindByParamName("@myParam");
if (mySqlParam == null)
Api.SqlParamsGlobal.Add(new MercatorSqlParam("@myParam", "xx", SqlDbType.Char));
else
mySqlParam.pSql.Value = "xx";
devient
MercatorSqlParam mySqlParam = Api.SqlParamsGlobal.FindByParamName("@myParam");
if (mySqlParam == null)
Api.SqlParamsGlobal.Add(new MercatorSqlParam("@myParam", "xx", SqlDbType.Char));
else
mySqlParam.Value = "xx";
👉 Ici, on retire simplement .pSql car il dépend de System.Data.SqlClient ou de Microsoft.Data.SqlClient. L'accesseur direct .Value renvoie la même valeur.
billingEngine.CommandForFinalTransaction = new System.Data.SqlClient.SqlCommand("...");
billingEngine.CommandForInitialTransaction = new System.Data.SqlClient.SqlCommand("...");
devient
billingEngine.MercatorSqlCommandForFinalTransaction = new MercatorSqlCommand("...");
billingEngine.MercatorSqlCommandForInitialTransaction = new MercatorSqlCommand("...");
👉 Ici, on utilise des nouvelles propriétés du BillingEngine qui ne dépendent ni de System.Data.SqlClient, ni de Microsoft.Data.SqlClient. Ce même principe s'applique à tous les engines de Mercator.
⚠️ MercatorSqlCommandForFinalTransaction n'est pas une commande supplémentaire, en plus de CommandForFinalTransaction. Il s'agit simplement d'un accesseur permettant de populer CommandForFinalTransaction. Il ne faut donc jamais mettre une valeur dans les deux propriétés en même temps. Le même principe s'applique à InitialTransaction.
private void BillingEngine_DuringSave(object sender, MercatorUi.Engine.Gescom.BillingEngine.DuringSaveEventArgs e)
{
MercatorUi.Engine.Gescom.BillingEngine billingEngine = (MercatorUi.Engine.Gescom.BillingEngine)sender;
e.SqlCommand.CommandText = "update ...";
e.SqlCommand.Parameters.AddWithValue("@param", "...");
}
devient
private void BillingEngine_DuringSave(object sender, MercatorUi.Engine.Gescom.BillingEngine.DuringSaveEventArgs e)
{
MercatorUi.Engine.Gescom.BillingEngine billingEngine = (MercatorUi.Engine.Gescom.BillingEngine)sender;
e.MercatorSqlCommand.CommandText = "update ...";
e.MercatorSqlCommand.Parameters.AddWithValue("@param", "...");
}
👉 Ici, on utilise des nouvelles propriétés de l'eventArgs qui ne dépendent ni de System.Data.SqlClient, ni de Microsoft.Data.SqlClient. Ce même principe s'applique à tous les engines de Mercator.
⚠️ MercatorSqlCommand n'est pas une commande supplémentaire, en plus de SqlCommand. Il s'agit simplement d'un accesseur permettant de populer SqlCommand. Il ne faut donc jamais mettre une valeur dans les deux propriétés en même temps.
public class Customizer : MercatorUi.ICustomizers.ISqlCommandUpdater
{
public void SqlCommandUpdate(SqlCommand sqlCommandToModify, Form form)
{
sqlCommandToModify.CommandText = "... \r\n"
+ sqlCommandToModify.CommandText;
}
}
devient
public class Customizer : MercatorUi.ICustomizers.IMercatorSqlCommandUpdater
{
public void MercatorSqlCommandUpdate(MercatorSqlCommand sqlCommandToModify, Form form)
{
sqlCommandToModify.CommandText = "... \r\n"
+ sqlCommandToModify.CommandText;
}
}
👉 Ici, on utilise, on remplace simplement l'interface ISqlCommandUpdater par IMercatorSqlCommandUpdater.
void billingEngine_BeginningSave(object sender, MercatorUi.Engine.Gescom.BillingEngine.BeginningSaveEventArgs e)
{
MercatorUi.Engine.Gescom.BillingEngine billingEngine = (MercatorUi.Engine.Gescom.BillingEngine)sender;
using (SqlCommand cmd = new SqlCommand("...", e.Connection, e.Transaction))
{
...
}
}
devient
void billingEngine_BeginningSave(object sender, MercatorUi.Engine.Gescom.BillingEngine.BeginningSaveEventArgs e)
{
MercatorUi.Engine.Gescom.BillingEngine billingEngine = (MercatorUi.Engine.Gescom.BillingEngine)sender;
using (MercatorSqlCommand cmd = new MercatorSqlCommand("...", e.Connection, e.DbTransaction))
{
...
}
}
👉 Notez l'utilisation de e.DbTransaction qui est de type System.Data.Common.DbTransaction, qui est une classe parente à la fois de System.Data.SqlClient.Transaction et Micrsosoft.Data.SqlClient.Transaction.
Voir aussi : Accès asynchrone à la base de données SQL avec l'API de Mercator
Si ce qui précède ne suffit pas, le compilateur intégré de Mercator reconnaîtra la clause #if NET10_0_OR_GREATER afin de produire deux assemblies disctinctes. L'une compilée pour System.Data.SqlClient, l'autre pour Microsoft.Data.SqlClient. A l'exécution, en fonction de la version utilisée, Mercator choisir l'assembly à utiliser.
using System.Data.SqlClient;
devient
#if NET10_0_OR_GREATER
using Microsoft.Data.SqlClient;
#else
using System.Data.SqlClient;
#endif
⚠️ Cette méthode souffre de la limite suivante : MercatorTunnel.dll et MercatorUi.dll passées en référence lors de la compilation sont toujours celles de la version en cours d'exécution. Ceci empêchera la compilation de codes qui, par exemple, utilisent des propriétés de classes dans MercatorUi qui font elle-même référence à System.Data.SqlClient ou Microsoft.Data.SqlClient.
Enfin, si on parle de code se trouvant dans un projet externe compilé depuis Visual Studio, l'utilisation des classes, méthodes et propriétés "agnostiques" est aussi possible. Une autre façon de procéder consiste à produire 2 assemblies en activant la compilation multiplateforme. Pour cela, dans le projet (préalablement migré vers le format SDK), remplacez
<TargetFramework>net48</TargetFramework>
par
<TargetFrameworks>net48;net10.0-windows</TargetFrameworks>
Indiquez les références correctement pour chaque version.
<ItemGroup Condition=" '$(TargetFramework)' == 'net48' ">
<Reference Include="MercatorComponents">
<HintPath>C:\DossiersClientsRefAssemblies\fw40\MercatorComponents.dll</HintPath>
</Reference>
<Reference Include="MercatorTunnel">
<HintPath>C:\DossiersClientsRefAssemblies\fw40\MercatorTunnel.dll</HintPath>
</Reference>
<Reference Include="MercatorUi">
<HintPath>C:\DossiersClientsRefAssemblies\fw40\MercatorUi.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net10.0-windows' ">
<Reference Include="MercatorComponents">
<HintPath>C:\DossiersClientsRefAssemblies\fw100\MercatorComponents.dll</HintPath>
</Reference>
<Reference Include="MercatorTunnel">
<HintPath>C:\DossiersClientsRefAssemblies\fw100\MercatorTunnel.dll</HintPath>
</Reference>
<Reference Include="MercatorUi">
<HintPath>C:\DossiersClientsRefAssemblies\fw100\MercatorUi.dll</HintPath>
</Reference>
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
</ItemGroup>
⚠️ Adaptez les chemins vers les DLL et référencez toujours la DLL compilée pour le framework adéquat. Les versions des DLL de Mercator se reconnaissent par le dernier nombre qui indique la version du framework.
Enfin, remplacez partout dans le code
using System.Data.SqlClient;
par
#if NET10_0_OR_GREATER
using Microsoft.Data.SqlClient;
#else
using System.Data.SqlClient;
#endif
La compilation va produire deux assemblies que vous pouvez placer dans "Gestion / Fichiers SQL / Assemblies". Lors de son démarrage, Mercator téléchargera la bonne version en fonction de la version utilisée.
La requête SQL ci-dessous permet de lister les codes C# stockés dans la base de données, contenant System.Data.SqlClient :
select id,code from ASSEMBLIES where code like '%System.Data.SqlClient%'