Генерація класів з БД за допомогою DataGrip


У цій невеликій замітці буде показано, як написати DataGrip розширення для генерації коду (в даному випадку POCO (C#) класів) на основі таблиць з майже будь-якої БД (SQL Server, Oracle, DB2, Sybase, MySQL, PostgreSQL, SQLite, Apache Derby, HyperSQL, H2).
Передмова

DataGrip це відносно нова IDE від JetBrains для роботи з різними СУБД має деякий API для розширення функціоналу. Завдання — використовуючи його написати генератор POCO (C#) класів.
Якщо ви не хочете читати все це, а бажаєте просто почати генерувати класи, ось посилання на репозиторій зі скриптом у висновку.
Написання скрипта
DataGrip дозволяє розширити свою функціональність за допомогою скриптів (Scripted Extensions). Підтримуються мови Groovy, Clojure і JavaScript. Документація на сайті про це досить коротка, але в розпорядженні є приклади і архів з API у вигляді вихідного коду Java. Вихідний код можна знайти у
<DataGrip_installation_dir>/lib/src/src_database-openapi.zip
. Приклади можна подивитися в самій IDE в панелі Files -> Scratches. Так само DataGrip підтримує скрипти для експорту даних в різні формати (extractors, у даній статті розглянуті не будуть), приклади для форматів csv, json і html теж знаходяться в панелі Scratches.
Отже, для написання скрипта ми будемо використовувати Clojure, за основу був взятий приклад генератора POJO з IDE.
Підсвічування синтаксису і доповнення для Clojure в DataGrip звичайно ж немає, тому можна використовувати будь-який інший редактор.
Для початку налаштуємо маппинги типів з БД на C# типи і оголосимо деякі константи.
Код
(def usings "using System;")
(def default-type "string")
(def type-mappings
[
[["bit"] "bool"]
[["tinyint"] "byte"]
[["uniqueidentifier"] "Guid"]
[["int"] "long"]
[["char"] "char"]
[["varbinary" "image"] "byte[]" true] ; cannot be null
[["double" "float" "real"] "double"]
[["decimal" "money" "numeric" "smallmoney"] "decimal"]
[["datetime" "timestamp" "date" "time"] "DateTime"]
[["datetimeoffset"] "DateTimeOffset"]
])
(def new-line "\r\n")

Далі напишемо функцію яка приводить рядок до PascalCase.
Код
(defn - poco-name [name]
(apply str (map clojure.string/capitalize (re-seq #"(?:[A-Z]+)?[a-z\d]*" name))))

Матчинг типу з БД на тип в C# грунтуючись на маппингах які ми визначили раніше.
Код
(defn - poco-type [data type is-null]
(let [spec (.. data type getSpecification toLowerCase)
spec-matches? (fn [pattern] (= (re-find #"^\w+" spec) pattern))
mapping-matches? (fn [[ps t n]] (when (some spec-matches? ps) [t n]))
[type cant-be-null] (some mapping-matches? type-mappings)
nullable-type (if (and type (not cant-be-null) is-null) (str type "?") type)]
(or nullable-type default-type)))

Функція яка отримує всі стовпці таблиці, викликає функцію матчинга і збирає потрібний нам об'єкт для подальшого збереження. Тут ми використовуємо методи з API, наприклад
com.intellij.database.util.DasUtil/getColumns
, всі ці методи можна подивитися в архіві
src_database-openapi.zip
згаданому вище.
Код
(defn - field-infos [table]
(let [columns (com.intellij.database.util.DasUtil/getColumns table)
field-info (fn [column] {:name (poco-name (.getName column))
:type (poco-type (.getDataType column) (not (.isNotNull column)))})]
(field map-info columns)))

Генерація тексту властивостей і класів, нічого особливого, просто конкатенація рядків. А так само функція запису цього тексту у файл.
Код
(defn - property-text [field-info]
(let [type (:type field-info)
name (:name field-info)]
(str " public " type " " name " { get; set; } " new-line)))

(defn - poco-text [class-name fields]
(apply str (flatten
[usings new-new line-line
"public class " class-name " " new-line "{" new-line
(interpose new-line (interleave (map property-text fields)))
"}" new-line])))

(defn - generate-poco [directory table]
(let [class-name (poco-name (.getName table))
fields (field-infos table)
file (java.io.File. directory (str class-name ".cs"))
text (poco-text class-name fields)]
(com.intellij.openapi.util.io.FileUtil/writeToFile text file)))

І нарешті функція відкриття діалогу вибору директорії для збереження файлів і функція яка визначає вибрані таблиці і запускає генерацію.
Код
(defn - generate-pocos [directory]
(let [table? (partial instance? com.intellij.database.model.DasTable)]
(doseq [table (filter table? SELECTION)]
(generate-poco directory table))))

(.chooseDirectoryAndSave FILES
"Choose directory"
"Choose where to generate POCOs to"
(proxy [com.intellij.util.Consumer] []
(consume [directory]
(generate-pocos directory)
(.refresh FILES directory))))

Установка скрипта

Повний код скрипта на GitHub.
Для установки треба просто скопіювати файл
Generate POCO.clj
IDE > Files > Scratches > Extensions > DataGrip > schema.
І в контекстному меню таблиці з'явиться відповідний пункт підменю в розділі Scripted Extensions.
Результат
наступної таблиці
Було
CREATE TABLE Users
(
Id INT PRIMARY KEY NOT NULL IDENTITY,
first_name NVARCHAR(255),
Last_Name NVARCHAR(255),
Email VARCHAR(255) NOT NULL,
UserGuid UNIQUEIDENTIFIER,
Age TINYINT NOT NULL,
Address NVARCHAR(MAX),
photo IMAGE,
Salary MONEY,
ADDITIONAL_INFO NVARCHAR(42)
);

буде згенерований наступний клас:
Стало
using System;

public class Users 
{
public long Id { get; set; } 

public string FirstName { get; set; } 

public string LastName { get; set; } 

public string Email { get; set; } 

public Guid? UserGuid { get; set; } 

public byte Age { get; set; } 

public string Address { get; set; } 

public byte[] Photo { get; set; } 

public decimal? Salary { get; set; } 

public string AdditionalInfo { get; set; } 
}

Висновок
Ось таким досить простим способом можна генерувати C# класи, які описують таблиці в БД і зробити життя трохи менш рутинною. Звичайно ж, ви не обмежені тільки POCO класами C#, можна написати генератор чого завгодно, для іншої мови, фреймворку і т. д.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.