J'ai une fonction qui crée dynamiquement des instructions SQL basées sur un modèle existant, puis ajoute des champs communs qui existent dans chaque table.
Le problème est que je ressens un comportement étrange lorsque j'essaie d'ajouter les champs communs.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.SqlClient;
using Dapper;
public class Program
{
private static readonly dynamic[] Data = new dynamic[] { new {Foo = 1, Bar = 2}, new {Foo = 3, Bar = 4} };
public static void Main()
{
Console.WriteLine("Hello World");
}
public static void Broken()
{
var parameters = ((IEnumerable<dynamic>)Data).Select(x => new DynamicParameters(x));
parameters.ToList().ForEach(x => x.AddDynamicParams(new {Bax = 7}));
using (var connection = new SqlConnection(""))
{
// This fails with a "Must declare scalar variable @Bax".
connection.Execute("INSERT INTO DataTable(Foo, Bar, Bax) VALUES(@Foo, @Bar, @Bax)", parameters);
}
}
public static void Working()
{
var parameters = ((IEnumerable<dynamic>)Data).Select(x => new DynamicParameters(x)).ToList();
parameters.ForEach(x => x.AddDynamicParams(new {Bax = 7}));
using (var connection = new SqlConnection(""))
{
// This works fine.
connection.Execute("INSERT INTO DataTable(Foo, Bar, Bax) VALUES(@Foo, @Bar, @Bax)", parameters);
}
}
}
Si vous remarquez, la seule différence est la position de mon appel à .ToList()
. Dans la fonction Broken
, je l'appelle dans le cadre d'une chaîne avant .ForEach
et dans la fonction Working
, je l'appelle sur la même ligne que l'affectation.
J'ai essayé de remplacer l'appel à AddDynamicParams
avec chaque Add
des appels pour chaque paramètre, mais il ne semble pas à la matière. J'ai aussi essayé de faire for(var parameter in parameters) { parameter.AddDynamicParams(new {Bax = 7}) }
sans succès.
Malheureusement, il n'y a pas de bon moyen pour afficher les templates
sans définir de point d'arrêt et examiner l'attribut .templates
de l'objet dans Visual Studio. Si quelqu'un peut fournir un meilleur moyen de démontrer que le modèle a disparu, je suis tout à fait prêt à vous entendre.
À l’intérieur de votre méthode Broken
, vous enregistrez deux fois votre collection. Cela est dû à l'exécution différée LINQ .
Cette ligne:
parameters.ToList().ForEach(x => x.AddDynamicParams(new {Bax = 7}));
Énumérer votre IEnumerable<dynamic>
en créant une List<dynamic>
que vous modifiez à l'aide de ForEach
, mais vous n'utilisez pas cette liste du tout, et passez à nouveau votre objet parameters
à la méthode connection.Execute
:
connection.Execute("**your query**", parameters);
Chaque fois que Dapper énumère l'objet parameter
, il projette à nouveau chaque objet (qui ne sont pas les mêmes objets trouvés dans la liste ci-dessus) et crée ainsi une nouvelle instance qui n'est pas modifiée avec AddDynamicParams
.
Pour être plus clair, votre méthode cassée est équivalente à la suivante:
public static void Broken()
{
var parameters = ((IEnumerable<dynamic>)Data).Select(x => new DynamicParameters(x));
var parametersList = parameters.ToList(); // you create a list
parametersList.ForEach(x => x.AddDynamicParams(new {Bax = 7})); // then you modify that list
// parametersList != parameters
using (var connection = new SqlConnection(""))
{
// This fails with a "Must declare scalar variable @Bax".
connection.Execute("INSERT INTO DataTable(Foo, Bar, Bax) VALUES(@Foo, @Bar, @Bax)", parameters); // but here you are passing the old parameters collection
}
}
Pour résoudre votre problème, utilisez votre version de Working
ou utilisez à nouveau LINQ pour créer une version énumérable fonctionnelle:
var parameters = ((IEnumerable<dynamic>)Data).Select(x =>
{
var p = new DynamicParameters(x);
p.AddDynamicParams(new {Bax = 7});
return p;
});