Accéder aux propriétés à l'intérieur du champ Jsonb pour Npgsql

PostgreSQL a un type de données Jsonb qui vous permet d'ajouter des propriétés supplémentaires au modèle relationnel standard avec la possibilité de les parcourir.


EntityFramework Core avec l'extension Npgsql peut extraire les données de champ vers le type System.String


Cependant, pour filtrer par les propriétés Json via EF au niveau de la requête, vous devez utiliser du SQL pur, ce qui n'est pas très pratique, car vous devez entrer dans le mappage (s'il n'est pas automatique), recherchez les noms des champs qui correspondent aux propriétés du modèle, prenez en charge cette dénomination. La flexibilité que l'ORM nous donne est perdue.


Si cela vous déprime, comme moi, bienvenue au chat.


À la fin de l'article, il y a un lien vers la source!


Indique les tâches


En tant que développeur, je souhaite disposer d'un outil pour accéder aux champs Jsonb dans le but de filtrer et trier par eux, qui:


  • Il sera compatible avec EntityFramework Core 2 (nous l'utilisons)
  • Cela ne vous obligera pas à écrire SQL vous-même tout en travaillant avec
  • Fonctionne avec une structure Json plate (à l'intérieur de json, il n'y a que des propriétés json)

J'ajouterai qu'il existe Npgsql.Json.NET , qui peut projeter des valeurs Json et Jsonb dans des types CLR. Pour être honnête, je ne comprends pas à quoi cela pourrait servir, car comme nous avions besoin d'un champ Json dans une base de données relationnelle, nous avons très probablement des entités avec un ensemble dynamique de champs.


L'algorithme pour résoudre le problème


  1. Définissez une méthode (ou des méthodes) qui couvrira nos besoins.
  2. Créez un traducteur qui participera à la génération du code SQL.
  3. Vissez le tout à Npgsql.

Solution


Tout d'abord, nous définissons une méthode. Je veux qu'il soit utilisé quelque chose comme ceci:


 context.Entity.Where(x => JsonbMethods.Value<string>(x.JsonbField, "jsonPropertyName") == "value") 

Voici donc notre méthode:


 public static TSource Value<TSource>(object jsonbProperty, string jsonbPropertyName) { throw new NotSupportedException(); } 

Pendant plusieurs heures, j'ai choisi les sources d'EF Core, de Npgsql et pas seulement à la recherche de moyens d'étendre les fonctionnalités de base de la génération SQL. Je suis arrivé à cet article , mais je n'ai pas aimé l'approche de l'auteur concernant la méthode de connexion du traducteur, car elle redéfinit l'outil standard, ce qui signifie qu'il peut entrer en conflit avec un autre outil similaire.
En conséquence, je suis arrivé à la source de Net Topology Suite. Tout ce dont j'avais besoin était de trouver un moyen de connecter un traducteur de méthode.


Mais la plupart du temps, j'ai passé à générer le fragment sql dont j'avais besoin.


Voici la syntaxe requise


tableAlias."JsonField"->>"insideProperty"


Au début, j'ai essayé dans le traducteur de renvoyer ColumnExpression. Lors de sa création, le premier paramètre est le nom de la colonne (chaîne). Je viens de le faire cuire à partir des paramètres qui me viennent dans la méthode. Lancé, vérifié, erreur. Il s'avère que ce que je passe comme nom est entouré de guillemets. En conséquence, SQL s'est avéré être tableAlias.""JsonField"->>"insideProperty"" .


Dans le code source du générateur, j'ai trouvé la méthode VisitColumn dans laquelle ce comportement était codé en dur et ne dépendait d'aucun paramètre. Autrement dit, je ne pouvais pas l'affecter. Il fallait chercher une autre solution.


J'ai ensuite créé ma propre Expression - JsonbPropertyAccessorExpression: Expression


Il reste à remplacer sa méthode Accept pour ISqlExpressionVisitor .


Mais le problème est que, dans cette interface, il n'y a aucune méthode qu'un opérateur personnalisé pourrait segmenter. Puis j'ai pensé à visiter non pas une méthode, mais plusieurs. VisitColumn abord visité VisitColumn , qui a créé l'accès à la colonne tableAlias. "JsonField", puis VisitSqlFragment , dans lequel j'ai jeté "->>'insideFieldName'" .


Je n'espérais pas, mais ça a marché. Presque.


Lorsque j'ai essayé de filtrer par texte pour une coïncidence exacte, pour une raison quelconque, un tableAlias."JsonField"->>"insideProperty" = JSONB "value" filtre tableAlias."JsonField"->>"insideProperty" = JSONB "value" été tableAlias."JsonField"->>"insideProperty" = JSONB "value" , ce qui a provoqué une erreur, car il est impossible de convertir le texte en type JSONB s'il ne contient pas de Json valide. . Et pourquoi dois-je mener quelque chose à quelque chose quand je veux un texte?


J'ai même pris la décision de supprimer la marque de la colonne Jsonb du modèle de mappage, qu'il s'agit de Jsonb, en ajoutant uniquement cette marque au MigrationContext afin qu'il génère les migrations correctes. Et ça a même décollé, mais l'approche m'a semblé une béquille. Néanmoins, je me suis arrêté là.


Après cela, j'ai défini CAST, car la méthode Value est universelle et il peut y avoir différents types de données dans les propriétés Json, qui doivent également être triées et filtrées.


Par conséquent, j'ai commencé à renvoyer ExplicitCastExpression partir de mon traducteur, dans lequel j'ai passé mon Expression personnalisée et le type qui était contenu dans les arguments universels de la méthode Value .


quand j'ai regardé le SQL résultant lors de la recherche par date, j'ai trouvé que la valeur comparée était castée en type d'horodatage. timestamp 'some date value' . Et puis cela m'est apparu. Le problème précédent, que j'ai résolu avec une béquille, a disparu par lui-même. Lorsque l'accesseur a été converti en texte dans le champ Json, le générateur n'a plus ajouté de conversion explicite en JSONB, car l'opération de comparaison avait déjà du texte à gauche et, par défaut, l'accesseur du champ Jsonb renvoie le type Jsonb.


À la fin


En conclusion, je veux ajouter que je n'ai pas trouvé de documentation sur la façon d'ajouter des traducteurs personnalisés de propriétés et de méthodes. probablement mal. Si quelqu'un a des commentaires sur l'approche, sur le code, etc., écrivez dans les commentaires.


Si quelqu'un veut agrandir la bibliothèque en fourches, écrivez dans une lettre personnelle, je vais essayer de vous aider. Eh bien, ou lancez des demandes de traction.


Voici le lien vers la source

Source: https://habr.com/ru/post/fr463747/


All Articles