5. LadybugDB depuis C#
Sections
Réglages de lecture
Aucune voix pour cette langue dans ce navigateur.
Plus de voix
Code et termes techniques en anglais
Désactivé : la voix de la page lit tout, sans pause entre deux voix.
Aucune voix pour cette langue dans ce navigateur.
Plus de voix
D’autres voix : Microsoft Edge propose des voix naturelles en ligne, et Windows (Paramètres › Heure et langue › Voix), macOS et Android en installent d’autres.
Depuis C#, LadybugDB s’exécute dans ton processus, comme une bibliothèque native appelée par le paquet NuGet LadybugDB. Le paquet n’est pas un fournisseur ADO.NET : pas de DbConnection, pas de DbDataReader, et donc pas de Dapper. Ses classes en sont pourtant proches :
| ADO.NET | LadybugDB 0.19.1 |
|---|---|
| une chaîne de connexion | new Database(path, config) : la base de données elle-même, dans ton processus |
DbConnection |
new Connection(database) : plusieurs connexions peuvent partager une même Database |
DbCommand.ExecuteReader() |
connection.Query(cypher) renvoie un QueryResult |
DbDataReader.Read() et GetInt64(i) |
result.Rows() : un IEnumerable<object?[]> |
DbParameter |
connection.Execute(cypher, dictionary) ou Prepare(cypher) puis Bind(name, value) |
DbException |
LadybugException et LadybugQueryException |
Le programme de cette leçon est csharp/ProjectGraph/Program.cs, exécuté depuis code/ladybugdb et comparé à csharp/expected.txt sur les trois systèmes d’exploitation. Le paquet n’a pas de page dans la documentation de LadybugDB : sa référence est le README de ladybug-dotnet et son code source.
Un nouveau graphe : les projets de GuitarAlchemist/ga
Section intitulée « Un nouveau graphe : les projets de GuitarAlchemist/ga »Les leçons 1 à 4 interrogeaient ce site. Les leçons 5 et 6 interrogent une solution .NET : GuitarAlchemist/ga, une application de théorie musicale de 111 projets C# et F#. data/ga/extract.py lit ses fichiers projet au commit a26a7893 et écrit trois fichiers CSV dans data/ga :
| Table | Type | Source |
|---|---|---|
Project(path, name, language, sdk, frameworks, in_solution) |
nœud | projects.csv : chaque .csproj et .fsproj, et si AllProjects.slnx le liste |
Package(name) |
nœud | les paquets distincts de package_refs.csv |
REFERENCES |
relation, de Project à Project |
project_refs.csv : les éléments <ProjectReference> |
USES(version) |
relation, de Project à Package |
package_refs.csv : les éléments <PackageReference>, avec leur version |
Un graphe de dépendances, c’est ce que parcourt dotnet build, et ce que dessinent les diagrammes de dépendances de Visual Studio ; ici, c’est un graphe que tu peux interroger.
version est la version écrite dans chaque fichier projet. Le Directory.Build.props de ga en change certaines pour tous les projets, avec des éléments <PackageReference Update="…" Version="…"/> : Microsoft.Extensions.Options, System.Numerics.Tensors et douze autres. L’extraction ne les applique pas ; les requêtes de cette leçon utilisent des paquets que ce fichier ne touche pas.
Le projet
Section intitulée « Le projet »<ItemGroup> <PackageReference Include="LadybugDB" Version="0.19.1" /> <PackageReference Include="LadybugDB.Native" Version="0.19.1" /></ItemGroup>LadybugDB est le code managé, qui appelle l’API C du moteur avec P/Invoke. Le moteur lui-même est dans un paquet natif par identificateur de runtime : LadybugDB.Native.win-x64, linux-x64, linux-arm64, osx-x64 et osx-arm64. LadybugDB.Native référence les cinq :
| Taille | |
|---|---|
| les cinq paquets natifs dans le cache NuGet | de 25 Mo (win-x64) à 38 Mo (linux-x64) chacun |
bin/Release/net10.0 après dotnet build |
131 Mo, dont 19 à 28 Mo par runtime |
dotnet publish -r win-x64 |
20 Mo |
Une application pour une seule plateforme peut référencer LadybugDB.Native.win-x64 seul au lieu du méta-paquet.
La version est en retard sur la CLI. 0.19.1 est la dernière version sur NuGet, et le README indique que les trois premiers nombres de la version du paquet sont ceux du moteur : ce programme exécute le moteur 0.19.1, la CLI des leçons 1 à 4 est la 0.20.4. Le programme affiche les deux versions qu’il connaît (lignes 10-14) :
Console.WriteLine($"LadybugDB {LadybugVersion.Version}, storage version {LadybugVersion.StorageVersion}");// Un chemin vide : une base de données en mémoire, comme la CLI lancée sans nom de fichierusing var database = new Database("");using var connection = new Connection(database);LadybugDB 0.19.1, storage version 43La version de stockage est le format des fichiers de base de données. Elle compte quand le programme et la CLI ouvrent le même fichier, plus bas.
Chargement : une référence vers un projet absent
Section intitulée « Chargement : une référence vers un projet absent »Le schéma et les instructions COPY sont ceux de la leçon 2, envoyés un par un avec connection.Query (lignes 16-32) :
Execute("COPY Project FROM 'data/ga/projects.csv' (HEADER = true)");try{ Execute("COPY REFERENCES FROM 'data/ga/project_refs.csv' (HEADER = true)");}catch (LadybugQueryException e){ Console.WriteLine($"{e.GetType().Name}: {e.Message}"); Execute("COPY REFERENCES FROM 'data/ga/project_refs.csv' (HEADER = true, IGNORE_ERRORS = true)");}111 tuples have been copied to the Project table.LadybugQueryException: Copy exception: Unable to find primary key value Experiments/React/reactapp1.client/reactapp1.client.esproj.265 tuples have been copied to the REFERENCES table.1 warnings encountered during copy. Use 'CALL show_warnings() RETURN *' to view the actual warnings. Query ID: 6Execute est une fonction utilitaire du programme qui affiche la première colonne de chaque ligne. ReactApp1.Server.csproj référence reactapp1.client.esproj, un projet JavaScript que l’extraction n’a pas gardé : l’erreur de la leçon 2, devenue une exception .NET. La CLI affichait l’erreur et continuait ; en C#, le COPY en échec lève une exception et rien n’est chargé avant la nouvelle tentative avec IGNORE_ERRORS.
using (var result = connection.Query(""" MATCH (p:Project)-[:REFERENCES]->(core:Project) RETURN core.name, count(*) AS referenced_by ORDER BY referenced_by DESC, core.name LIMIT 3 """)){ Console.WriteLine($"{string.Join(", ", result.ColumnNames)}: {result.RowCount} rows"); foreach (var row in result.Rows()) { Console.WriteLine($"{row[0],-20} {row[1],2} ({row[1]?.GetType()})"); } Console.WriteLine($"a second foreach: {result.Rows().Count()} rows");}core.name, referenced_by: 3 rowsGA.Domain.Core 57 (System.Int64)GA.Domain.Services 48 (System.Int64)GA.Core 32 (System.Int64)a second foreach: 0 rowsRowCountest connu avant la première ligne : le moteur a calculé tout le résultat.- Chaque ligne est un
object?[], et unINT64arrive boxé enlong. Rows()ne peut être énuméré qu’une fois, comme unDbDataReader, mais sans le dire : le secondforeachne trouve aucune ligne, et aucune exception.Rows()est un itérateur sur un curseur du résultat natif (QueryResult.cs, lignes 117-133). Appelle.ToList()quand tu as besoin des lignes deux fois.QueryResultestIDisposable: il garde de la mémoire native jusqu’àDispose.
57 des 111 projets référencent GA.Domain.Core.
Paramètres
Section intitulée « Paramètres »Deux façons, avec un dictionnaire ou avec une instruction préparée (lignes 51-67) :
using (var result = connection.Execute( "MATCH (p:Project {name: $name})-[:REFERENCES*1..10]->(d:Project) RETURN count(DISTINCT d) AS dependencies", new Dictionary<string, object?> { ["name"] = "GaApi" })){ Console.WriteLine($"GaApi depends on {result.Rows().Single()[0]} projects");}using (var statement = connection.Prepare(""" MATCH (p:Project {name: $name})-[:REFERENCES*1..10]->(d:Project) RETURN count(DISTINCT d) AS dependencies """)){ foreach (var name in new[] { "GaCli", "GaMcpServer", "GA.Domain.Core" }) { using var result = statement.Bind("name", name).Execute(); Console.WriteLine($"{name} depends on {result.Rows().Single()[0]} projects"); }}GaApi depends on 20 projectsGaCli depends on 7 projectsGaMcpServer depends on 13 projectsGA.Domain.Core depends on 2 projects$name en Cypher, "name" sans le $ en C#. Execute avec un dictionnaire prépare l’instruction à chaque appel ; Prepare une fois, puis Bind et Execute pour chaque valeur, évite ce travail, comme DbCommand.Prepare(). Bind renvoie l’instruction, donc les appels s’enchaînent. L’API web, GaApi, dépend de 20 projets, directement ou non : le motif de longueur variable de la leçon 3 les trouve tous.
Ce que le binding accepte, et ce qu’il signale (lignes 68-83) :
int 2: 42 projects reference more than 2 projectsstring "2": 42 projects reference more than 2 projectsdecimal 2m: NotSupportedException: Cannot bind a parameter of type System.Decimal.LadybugQueryException: Parameter name not found.LadybugQueryException: Binder exception: Cannot find parameter file. This should not happen.- La requête compare
COUNT { … } > $n. Unintfonctionne, et la chaîne"2"aussi : le moteur la convertit en nombre. SQL Server fait de même avec un paramètrenvarcharcomparé à une colonneint. - Un
decimaléchoue dans le binding, avant que le moteur le voie (PreparedStatement.cs, ligne 198) : passe undouble, ou une chaîne convertie en Cypher. - Un dictionnaire avec
nomau lieu denameéchoue avecParameter name not found., sans le nom. - Un paramètre ne peut pas être un nom de fichier :
COPY Package FROM $fileéchoue à la liaison, avec un message qui dit que cela ne devrait pas arriver. Construis le nom de fichier dans l’instruction, à partir d’une valeur sûre.
Nœuds, relations et chemins
Section intitulée « Nœuds, relations et chemins »Une requête peut renvoyer un nœud entier ou un chemin entier (lignes 85-104) :
using (var result = connection.Query(""" MATCH path = (app:Project {name: 'GaApi'})-[:REFERENCES* ALL SHORTEST 1..10]->(assets:Project {name: 'GA.Business.Assets'}) RETURN app, path """)){ var rows = result.Rows().ToList(); var app = (Node)rows[0][0]!; // L'ID interne (table:position) dépend du chargement, qui diffère selon les exécutions et les machines : n'afficher que son type Console.WriteLine($"{app.Label} ({app.Id.GetType().Name}): {string.Join(", ", app.Properties.Select(p => $"{p.Key}={p.Value}"))}"); // Plusieurs plus courts chemins, dans un ordre quelconque : les trier avant de les afficher foreach (var path in rows.Select(row => (RecursiveRel)row[1]!) .Select(path => $"{path.Rels.Count} relationships: {string.Join(" -> ", path.Nodes.Select(n => n.Properties["name"]))}") .Order()) { Console.WriteLine(path); } var first = ((RecursiveRel)rows[0][1]!).Rels[0]; Console.WriteLine($"first relationship: {first.Label}, starts at GaApi: {first.Source == app.Id}");}Project (InternalId): path=Apps/ga-server/GaApi/GaApi.csproj, name=GaApi, language=C#, sdk=Microsoft.NET.Sdk.Web, frameworks=net10.0, in_solution=True3 relationships: GaApi -> GA.Business.AI -> GA.Data.MongoDB -> GA.Business.Assets3 relationships: GaApi -> GA.Business.ML -> GA.Data.MongoDB -> GA.Business.Assetsfirst relationship: REFERENCES, starts at GaApi: TrueLe binding convertit les valeurs de graphe en records (GraphTypes.cs) : Node(Id, Label, Properties), Rel(Id, Source, Destination, Label, Properties), et RecursiveRel(Nodes, Rels) pour un chemin. Properties est un dictionnaire ; Source et Destination sont les InternalId des nœuds, pas les nœuds. Les Nodes d’un chemin nommé comprennent les deux extrémités.
La première version de cette section affichait app.Id et utilisait SHORTEST, et la CI a échoué sur les trois runners :
- l’ID de
GaApiétait0:11sur ma machine et sur le runner Windows,0:47sous Linux et macOS, avec le même fichier CSV. Un ID interne est la table et la position où le nœud a été stocké : utilise-le au sein d’un résultat, pour relier unRelà ses nœuds, jamais comme une clé que tu conserves ; - il y a deux plus courts chemins, par
GA.Business.AIou parGA.Business.ML, etSHORTESTa renvoyé l’un sur ma machine et l’autre sur les runners.ALL SHORTESTrenvoie les deux, que le programme trie.
Listes et structs
Section intitulée « Listes et structs »Quels paquets sont utilisés en plusieurs versions ? (lignes 106-121)
using (var result = connection.Query(""" MATCH (p:Project)-[u:USES]->(k:Package) WITH k, count(*) AS projects, collect(DISTINCT u.version) AS versions WHERE size(versions) > 1 RETURN k.name, versions, {projects: projects, versions: size(versions)} AS usage ORDER BY size(versions) DESC, k.name LIMIT 3 """)){ foreach (var row in result.Rows()) { var versions = (object?[])row[1]!; var usage = (Dictionary<string, object?>)row[2]!; Console.WriteLine($"{row[0]}: {string.Join(", ", versions.Order())} ({usage["projects"]} projects)"); }}Microsoft.Extensions.Hosting: 10.0.0, 10.0.5, 9.0.0, 9.0.10, 9.0.4 (12 projects)MongoDB.Driver: 2.29.0, 2.30.0, 3.2.0, 3.2.1, 3.5.0 (14 projects)Microsoft.Extensions.DependencyInjection: 10.0.0, 10.0.2, 9.0.0, 9.0.10 (12 projects)Une LIST arrive en object?[], pas en string[] : convertis chaque élément. Un STRUCT arrive en Dictionary<string, object?>. collect ne garantit aucun ordre, donc le programme trie ; Order() sur des chaînes place 10.0.0 avant 9.0.0.
MongoDB.Driver est utilisé en cinq versions, sur deux versions majeures. L’exercice 2 regarde ce que cela signifie pour les projets qui se référencent entre eux.
Le bug d’agrégat, en 0.19.1 aussi
Section intitulée « Le bug d’agrégat, en 0.19.1 aussi »count(*) vient avant collect(DISTINCT …) dans la requête ci-dessus, et pas par hasard (lignes 123-130) :
MATCH (p:Project)-[u:USES]->(k:Package {name: 'MongoDB.Driver'})RETURN k.name, collect(DISTINCT u.version) AS versions, count(*) AS projectsDISTINCT first: MongoDB.Driver, 5 versions, 0 projects0 projet au lieu de 14. C’est le bug du journal, sous une forme plus générale : avec une clé de regroupement, un agrégat qui suit un agrégat DISTINCT dans la même projection est faux, NULL pour sum et 0 pour count. Il est dans le moteur 0.19.1 du paquet comme dans la CLI 0.20.4. Le contournement est le même : mets les agrégats DISTINCT en dernier.
Des lignes en records
Section intitulée « Des lignes en records »Pas de Dapper, mais une fonction utilitaire de dix lignes convertit les lignes en record (lignes 138-145 et 249-256) :
IEnumerable<T> Query<T>(string cypher, Func<object?[], T> map){ using var result = connection.Query(cypher); foreach (var row in result.Rows()) { yield return map(row); }}foreach (var project in Query( "MATCH (p:Project) WHERE NOT p.in_solution RETURN p.name, p.path, p.language ORDER BY p.path LIMIT 3", row => new Project((string)row[0]!, (string)row[1]!, (string)row[2]!))){ Console.WriteLine(project);}Project { Name = FloorManager, Path = Apps/FloorManager/FloorManager.csproj, Language = C# }Project { Name = GaChatbot, Path = Apps/GaChatbot/GaChatbot.csproj, Language = C# }Project { Name = InteractiveTutorial, Path = Apps/InteractiveTutorial/InteractiveTutorial.csproj, Language = C# }38 projects are not in AllProjects.slnxLe using à l’intérieur de l’itérateur libère le résultat quand le foreach se termine, même quand il se termine plus tôt avec break ou Single(). 38 des 111 projets ne sont pas dans AllProjects.slnx : une compilation de la solution ne les compile pas.
Dates et heures
Section intitulée « Dates et heures »Les lignes 147-167 envoient un DateTimeOffset et un DateTime pour 9 h 30 à New York, et comparent avec un littéral :
using (var result = connection.Execute( "RETURN $offset AS offset, $local AS local, timestamp('2026-09-14T09:30:00-04:00') AS literal", new Dictionary<string, object?> { ["offset"] = new DateTimeOffset(2026, 9, 14, 9, 30, 0, TimeSpan.FromHours(-4)), ["local"] = new DateTime(2026, 9, 14, 9, 30, 0), }))offset DateTimeOffset 2026-09-14 13:30:00 +00:00local DateTime 2026-09-14 09:30:00, Kind = Utcliteral DateTime 2026-09-14 09:30:00, Kind = Utc- Un
DateTimeOffsetdevient unTIMESTAMP_TZ, stocké en UTC : il revient en 13:30 à+00:00, le même instant. - Un
DateTimedevient unTIMESTAMP. SonKindest iciUnspecified, et le binding le prend pour de l’UTC (PreparedStatement.cs, lignes 168-171) ; unDateTimeLocalest converti avecToUniversalTime(). Il revient avecKind = Utc. - Le littéral est faux.
timestamp('…-04:00')renvoie 9:30 : le décalage est ignoré sans conversion, alors que leCOPYde la leçon 4 convertissait le même texte en UTC. La CLI 0.20.4 fait de même, etCAST(… AS TIMESTAMP)aussi ;CAST(… AS TIMESTAMP_TZ)donne 13:30. Un sixième bug pour le journal.
LadybugQueryException: Binder exception: Table Projet does not exist.LadybugQueryException: Binder exception: Cannot find property nam for p.LadybugQueryException: Runtime exception: Found duplicated primary key value Common/GA.Core/GA.Core.csproj, which violates the uniqueness constraint of the primary key column.two statements, one result: COUNT_STAR() = 111packages created by two statements: Test.A, Test.BChaque erreur de requête est une LadybugQueryException, avec le type au début du message : Binder exception pour ce que le moteur rejette avant l’exécution, Runtime exception pour ce qui échoue pendant l’exécution. Il n’y a pas de code d’erreur à tester, contrairement à SqlException.Number.
Plusieurs instructions dans un même Query ne renvoient que le premier résultat, et elles s’exécutent toutes : MATCH (p:Project) RETURN count(*); MATCH (k:Package) RETURN count(*) donne le nombre de projets, et le nombre de paquets est perdu ; les deux instructions CREATE ont chacune créé leur nœud. L’API C a lbug_query_result_has_next_query_result pour les résultats suivants (lbug.h, ligne 771), et lbug_connection_interrupt et lbug_connection_set_query_timeout pour arrêter une longue requête (lignes 465 et 472) ; le binding 0.19.1 n’en expose aucune. Envoie une instruction par appel.
Partager un fichier avec la CLI
Section intitulée « Partager un fichier avec la CLI »new Database("out/net/ga.lbdb") crée ou ouvre un fichier de base de données. check.sh, lignes 14-24, en crée un depuis C#, l’interroge avec la CLI, puis le rouvre depuis C#, deux fois :
created out/net/ga.lbdb with LadybugDB 0.19.1-- the CLI, read-onlyp.nameGA.Coreopened out/net/ga.lbdb: GA.Core-- the CLI, read-writep.nameGA.CoreLadybugException: Failed to open Ladybug database at 'out/net/ga.lbdb'.La CLI 0.20.4 lit le fichier écrit par la 0.19.1 dans les deux modes. Mais ouvert en lecture-écriture, elle convertit le fichier vers sa propre version de stockage, 47 au lieu de 43 (storage_version_info.h, lignes 46-49), sans message : ensuite, le programme C# ne peut plus l’ouvrir, et l’exception ne dit pas pourquoi. Avec --read_only, le fichier reste tel quel. Depuis C#, new SystemConfig { ReadOnly = true } fait de même.
Tant que le paquet est en retard sur la CLI, ouvre les fichiers de l’application avec lbug --read_only. Plusieurs processus peuvent ouvrir un fichier en lecture seule en même temps ; un seul peut l’ouvrir en lecture-écriture (concurrence).
Threads et connexions
Section intitulée « Threads et connexions »Le mode timings exécute la même requête, les 7 793 marches du graphe des projets jusqu’à 10 relations, de trois façons (lignes 221-236). Temps en millisecondes, tirés de l’exécution de CI de cette leçon et de ma machine ; ils varient d’une exécution à l’autre, donc la CI les affiche sans les comparer :
| une requête | 4 requêtes, 1 connexion, 4 threads | 4 requêtes, 4 connexions, 4 threads | |
|---|---|---|---|
| ma machine (Windows, 24 threads) | 12 | 49 | 25 |
| runner Linux | 9 | 49 | 20 |
| runner macOS | 10 | 53 | 27 |
| runner Windows | 17 | 76 | 42 |
Une Connection peut être utilisée depuis plusieurs threads, mais elle prend un verrou à chaque appel (Connection.cs, ligne 40) : quatre requêtes sur une connexion prennent quatre fois plus longtemps qu’une seule. Une connexion par thread, sur la même Database, les exécute côte à côte. Dans une application ASP.NET Core, cela signifie une Database pour le processus, un singleton, et une Connection par requête HTTP ou par unité de travail.
À retenir
Section intitulée « À retenir »- Le paquet
LadybugDBn’est pas ADO.NET :Database,Connection,QueryouExecute, etRows()enobject?[]. Pas de Dapper ; un petit itérateur convertit les lignes en records. LadybugDB.Nativeembarque le moteur pour cinq runtimes ; un seulLadybugDB.Native.<rid>oudotnet publish -rn’en garde qu’un. Le paquet est sur le moteur 0.19.1, en retard sur la CLI 0.20.4.Rows()s’énumère une fois : la deuxième fois, il est vide, en silence.- Paramètres : un dictionnaire, ou
PrepareetBindpour réutiliser l’instruction. Pas dedecimal, pas de noms de fichiers. - Les valeurs de graphe reviennent en records
Node,ReletRecursiveRel. Les ID internes et le chemin choisi parSHORTESTpeuvent différer d’une machine à l’autre. - Un agrégat
DISTINCTavant un autre agrégat casse ce dernier en 0.19.1 aussi ;timestamp()ignore le décalage de son argument. Queryavec plusieurs instructions renvoie le premier résultat et les exécute toutes.- Une ouverture en lecture-écriture par une CLI plus récente met le fichier à niveau au-delà de ce que le paquet sait lire : utilise
--read_only. - Une
Databasepar processus, uneConnectionpar thread.
Exercices
Section intitulée « Exercices »Les solutions sont à la fin de Program.cs, et leur sortie est dans expected.txt : la CI les vérifie avec le reste.
- Écris
List<string> Dependents(PreparedStatement statement, string name), qui renvoie les noms des projets qui dépendent d’un projet, directement ou non. Prépare l’instruction une fois et appelle la méthode pourGA.Business.Assets,GA.Data.MongoDBetGaApi. Combien de projets dépendent deGA.Business.Assets?
Solution
Lignes 180-192 et 258-262 :
using (var dependents = connection.Prepare(""" MATCH (d:Project)-[:REFERENCES*1..10]->(:Project {name: $name}) RETURN DISTINCT d.name ORDER BY d.name """)){ foreach (var name in new[] { "GA.Business.Assets", "GA.Data.MongoDB", "GaApi" }) { var names = Dependents(dependents, name); Console.WriteLine($"{name}: {names.Count} dependents{(names.Count > 0 ? $", from {names[0]} to {names[^1]}" : "")}"); }}static List<string> Dependents(PreparedStatement statement, string name){ using var result = statement.Bind("name", name).Execute(); return result.Rows().Select(row => (string)row[0]!).ToList();}GA.Business.Assets: 31 dependents, from AllProjects.AppHost to VectorSearchBenchmarkGA.Data.MongoDB: 29 dependents, from AllProjects.AppHost to VectorSearchBenchmarkGaApi: 4 dependents, from AllProjects.AppHost to VectorSearchBenchmarkLa flèche est inversée par rapport à la section 4 : (d)-[…]->(:Project {name: $name}). ToList() lit les lignes avant que le résultat soit libéré, et renvoie une liste que l’appelant peut énumérer autant de fois qu’il veut.
31 noms, mais 32 projets : count(DISTINCT d) donne 32. Deux projets s’appellent GaApi.Tests, dans Tests/Apps/GaApi.Tests et dans Tests/GaApi.Tests, et DISTINCT d.name les fusionne. name n’est pas la clé : {name: $name} peut correspondre à plusieurs nœuds, ce que path, la clé primaire, ne peut pas.
- Un projet qui utilise une version majeure d’un paquet et référence, directement ou non, un projet qui en utilise une autre version majeure n’obtient qu’une seule version à la compilation : NuGet en choisit une pour tout le graphe de dépendances du projet. Trouve ces conflits pour
MongoDB.DriveretMicrosoft.ML.Tokenizers, avec une instruction préparée qui prend le nom du paquet.
Solution
using (var conflicts = connection.Prepare(""" MATCH (a:Project)-[ua:USES]->(k:Package {name: $package}), (a)-[:REFERENCES*1..10]->(b:Project)-[ub:USES]->(k) WHERE split_part(ua.version, '.', 1) <> split_part(ub.version, '.', 1) RETURN DISTINCT a.name, ua.version, b.name, ub.version ORDER BY a.name, b.name """)){ foreach (var package in new[] { "MongoDB.Driver", "Microsoft.ML.Tokenizers" }) { using var result = conflicts.Bind("package", package).Execute(); foreach (var row in result.Rows()) { Console.WriteLine($"{package}: {row[0]} {row[1]}, but {row[2]} {row[3]}"); } }}MongoDB.Driver: GA.Analytics.Service 3.2.0, but GA.Data.MongoDB 2.30.0MongoDB.Driver: GA.BSP.Service 3.2.0, but GA.Data.MongoDB 2.30.0MongoDB.Driver: GA.DocumentProcessing.Service 3.2.0, but GA.Data.MongoDB 2.30.0MongoDB.Driver: GaApi 3.5.0, but GA.Data.MongoDB 2.30.0Microsoft.ML.Tokenizers: GaApi 1.0.2, but GA.Business.ML 2.0.0Microsoft.ML.Tokenizers: GaApi 1.0.2, but GA.Domain.Services 2.0.0Le motif nomme k deux fois, donc les deux relations USES vont vers le même paquet. split_part(…, 1) extrait la version majeure.
La règle de NuGet est qu’une référence directe l’emporte. GaApi obtient MongoDB.Driver 3.5.0, et GA.Data.MongoDB, compilé avec la 2.30.0, s’exécute avec la 3.5.0 : une nouvelle version majeure peut avoir supprimé ce qu’appelle l’ancien code. Pour Microsoft.ML.Tokenizers, la référence directe est la plus basse : GaApi obtient la 1.0.2 alors que deux de ses dépendances demandent au moins la 2.0.0. NuGet signale cette rétrogradation avec NU1605, une erreur par défaut dans les projets SDK, mais le Directory.Build.props de ga supprime NU1605 pour toute la solution. Si ces projets échouent à l’exécution, sur une méthode manquante par exemple, est à vérifier : je n’ai ni compilé ni exécuté ga pour cette leçon.
(long)row[0]!lit un décompte. Pourquoi(int)row[0]!lève-t-il une exception, et quelles sont deux façons d’obtenir unint?
Solution
using (var result = connection.Query("MATCH (p:Project) RETURN count(*)")){ var count = result.Rows().Single()[0]; Try(() => Console.WriteLine((int)count!)); Console.WriteLine((int)(long)count!); Console.WriteLine(Convert.ToInt32(count));}InvalidCastException: Unable to cast object of type 'System.Int64' to type 'System.Int32'.111111count(*) est un INT64, et la ligne contient un long boxé. L’unboxing doit nommer le type exact : (int) sur un object est un unboxing, pas une conversion numérique. (int)(long)count fait l’unboxing, puis convertit ; Convert.ToInt32 fait les deux, et lève OverflowException si la valeur ne tient pas. DbDataReader.GetInt32 a le même piège sur une colonne bigint, avec une autre exception.
LadybugDBsur NuGet,LadybugDB.Native, et le dépôtladybug-dotnetau commit0f58f1a- En-tête de l’API C de LadybugDB,
lbug.h - Concurrence : bases de données en lecture seule et en lecture-écriture, connexions
- Types de données :
TIMESTAMP,TIMESTAMP_TZ,LIST,STRUCT MATCH: plus courts chemins- Vue d’ensemble d’ADO.NET, identificateurs de runtime, résolution des dépendances NuGet