diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..b432968
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+* text=auto
+
+*.sh eol=lf
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 438a7f4..7bc7fc3 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -13,7 +13,7 @@
"args": [
"sync"
],
- "cwd": "${workspaceFolder}/src/DbBroker.Cli",
+ "cwd": "${workspaceFolder}/src/DbBroker.Showcase.Cli",
"stopAtEntry": false,
"console": "internalConsole"
},
diff --git a/README.md b/README.md
index ab8a669..86bee36 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,28 @@
-
-

-
- A lightweight and easy to use .NET tool and library for effortless database records manipulation.
-
+# DBBroker
+
+
+
+A lightweight and easy to use .NET tool and library for effortless database records manipulation.
+
+## Benefits
+
+- Automatically generated Data Models
+- Zero-SQL
+- Compile time database compatibility and change check
## NuGet
| Package | Latest | |
|----|----|----|
-| DBBroker |  | [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0#select-net-standard-version) |
-| DBBroker.Cli |  | |
+| [DBBroker](https://www.nuget.org/packages/DBBroker) |  | [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0#select-net-standard-version) |
+| [DBBroker.Cli](https://www.nuget.org/packages/DBBroker.Cli) |  | |
-**IMPORTANT:** This version has no backward compatibility with DBBroker 1.x and 2.x.
+> [!WARNING]
+> Version 3.x has no backward compatibility with DBBroker 1.x and 2.x
-## What It Does?
+## How it works?
-Differently from another popular ORM packages, DBBroker approach associates a [.NET CLI tool](https://nuget.org/dbbroker.cli) to automatically generate **Data Models** and a [library](https://nuget.org/dbbroker) that uses those Data Models at runtime to manipulate database records.
+DBBroker approach is powerful and different from other ORMs because it associates a [.NET CLI tool](https://nuget.org/dbbroker.cli) to automatically generate **Data Models** and a [library](https://nuget.org/dbbroker) that uses those Data Models at runtime to generate SQL and manipulate database records.
## Philosophy
@@ -29,7 +36,7 @@ DBBroker offers features that benefit any kind of application, from the simplest
These are common requirements for many organizations or database-centered solutions, and, arguably, by everyone who likes to keep things simple.
-## Quick Start
+## Quick Start⚡
Open your terminal and navigate to your application's *.csproj directory. Then follow these steps:
@@ -48,7 +55,7 @@ dotnet nuget add DBBroker
**Step 3:** Create a `dbbroker.config.json` file at the root of your project.
```bash
-dbbroker init --namespace="MyApp.DataModels" --connection-string "[database-connection-string]" --provider Oracle
+dbbroker init --namespace="MyApp.DataModels" --connection-string "" --provider Oracle
```
**Step 4:** Synchronize your project with your database schemas to generate the Data Models.
@@ -67,15 +74,15 @@ var customer = new CustomersDataModel();
customer.Name = "John Three Sixteen";
customer.Birthday = new DateTime(1980, 3, 16);
-var id = await _dbBroker.InsertAsync(customer);
+var id = await dbConnection.InsertAsync(customer);
```
Entity persistence with transactions.
```C#
-using var connection = new SqlConnection();
-connection.Open();
-var transaction = connection.GetTransaction();
+using var dbConnection = new SqlConnection();
+dbConnection.Open();
+var transaction = dbConnection.GetTransaction();
try
{
@@ -84,14 +91,14 @@ try
customer.Name = "John Three Sixteen";
customer.Birthday = new DateTime(1980, 3, 16);
- await connection.InsertAsync(customer, transaction);
+ await dbConnection.InsertAsync(customer, transaction);
- var car = new CarEdm();
+ var car = new CarsDataModel();
car.Model = "Renault Twingo";
car.Year = 2001;
car.CustomerId = customer.Id;
- await connection.InsertAsync(car, transaction);
+ await dbConnection.InsertAsync(car, transaction);
transaction.Commit();
}
catch(Exception ex)
@@ -103,24 +110,34 @@ catch(Exception ex)
Retrieving a record.
```C#
-var customer = await connection.GetByKey("543491fa-788a-474c-9f3b-6ed6566e5d2c");
+var result = await dbConnection
+ .Select()
+ .AddFilter(x => x.Id, SqlEquals.To("1e6fc0e6-1fe2-49c0-ba37-ec14bf8eddc4"))
+ .ExecuteAsync();
+
+var customer = result.FirstOrDefault();
```
Retrieving multiple and filtered records.
```C#
-var inactiveCustomers = await connection.Select()
+var inactiveCustomers = await dbConnection.Select()
.AddFilter(x => x.StatusId, SqlEquals.To(3))
- .FetchFirst(records: 100, skip: 300) // helps implement a 'load more' or pagination strategy
.ExecuteAsync();
```
-```C#
-// another example with columns
-```
+Retrieving multiple records loading only specified columns.
```C#
-// another example with columns and order by
+var inactiveCustomers = await dbConnection
+ .Select([
+ x => x.Id,
+ x => x.Name,
+ x => x.Birthday
+ ])
+ .OrderBy(x => x.Name)
+ .AddFilter(x => x.StatusId, SqlEquals.To(3))
+ .ExecuteAsync();
```
## Supported Databases
@@ -134,7 +151,7 @@ var inactiveCustomers = await connection.Select()
## Contribute
-We appreciate all contributions, whether they're bug reports, feature suggestions, or pull requests. Thank you for your interest and support in improving this project!
+All contributions are appreciated, whether they're bug reports, feature suggestions, or pull requests. Thank you for your interest and support in improving this project!
Financial support is also welcome, whether large or small contributions will help to keep this project moving and always secure.
diff --git a/docs/json-schema/dbbroker.config.schema.json b/docs/json-schema/dbbroker.config.schema.json
index 325569a..321125b 100644
--- a/docs/json-schema/dbbroker.config.schema.json
+++ b/docs/json-schema/dbbroker.config.schema.json
@@ -5,6 +5,21 @@
"description": "Configuration file to describe database contexts",
"type": "object",
"properties": {
+ "database": {
+ "description": "The database information against which DBBroker will run",
+ "type": "object",
+ "properties": {
+ "provider": {
+ "description": "The database provider",
+ "type": "string",
+ "enum": ["SqlServer", "Oracle"]
+ },
+ "connectionString": {
+ "description": "The connection string DBBroker is going to use to prospect database metadata",
+ "type": "string"
+ }
+ }
+ },
"contexts": {
"description": "Database contexts",
"type": "array",
@@ -15,13 +30,21 @@
"description": "The namespace where Data Models should be placed in",
"type": "string"
},
+ "name": {
+ "description": "The context name. Can be used to selectively synchronize a context in a dbbroker.config.json file.",
+ "type": "string"
+ },
+ "outputDirectory": {
+ "description": "The relative directory where Data Models should be placed in. If omitted a folder structure based on the namespace will be created skipping the first part (generaly the application name).",
+ "type": "string"
+ },
"provider": {
- "description": "The database provider",
+ "description": "The database provider. Overrides 'database.provider'.",
"type": "string",
"enum": ["SqlServer", "Oracle"]
},
"connectionString": {
- "description": "The connection string DBBroker is going to use to prospect database metadata",
+ "description": "The connection string DBBroker is going to use to prospect database metadata. Overrides 'database.connectionString'.",
"type": "string"
},
"defaultSqlInsertTemplateTypeFullName": {
@@ -36,8 +59,20 @@
"description": "The name sufix that Entity Data Models classes will receive when generated. The default value is 'DataModel'.",
"type": "string"
},
+ "ignoreTablesNotListed": {
+ "description": "Should DBBroker ignore database tables not described on 'tables' array? If 'true', you need to include manually all table references of tables listed in 'tables' array, unless you set the 'addReferences' property on the table to false. The default value is 'false'.",
+ "type": "boolean"
+ },
+ "ignoreViewsNotListed": {
+ "description": "Should DBBroker ignore database views not described on 'views' array? The default value is 'false'.",
+ "type": "boolean"
+ },
+ "clearOutputDirectory": {
+ "description": "Specifies if DBBroker CLI should Remove all preexisting files from the output directory before syncing. Default value is 'false'.",
+ "type": "boolean"
+ },
"tables": {
- "description": "Array of tables DBBroker will use to generate the Data Models",
+ "description": "Array of database tables DBBroker will use to generate the Data Models. If empty, all tables will be included by default. See 'ignoreTablesNotListed'.",
"type": "array",
"items": {
"type": "object",
@@ -67,6 +102,52 @@
"sqlInsertTemplateArguments": {
"description": "Arguments the ISqlInsertTemplate specified might be expecting",
"type": "object"
+ },
+ "addReferences": {
+ "description": "Specifies if the table references should be included. Composite Primary Keys are not supported. Default value is true.",
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ "views": {
+ "description": "Array of database views DBBroker will use to generate the Data Models. If empty, all tables will be included by default.",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "The view name",
+ "type": "string"
+ },
+ "schema": {
+ "description": "The view schema name",
+ "type": "string"
+ },
+ "type": {
+ "description": "The Data Model type name",
+ "type": "string"
+ },
+ "splitsOn": {
+ "description": "A collection of descriptors to split the database view record set into Data Model entities. When using this feature the first column on the view will be used as the first 'Split value', therefore use an identifier value.",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "column": {
+ "description": "The column name to split the record set and start loading data of an entity",
+ "type": "string"
+ },
+ "type": {
+ "description": "The Data Model type name",
+ "type": "string"
+ },
+ "collection": {
+ "description": "Should the entity on this split be treated as a collection?",
+ "type": "boolean"
+ }
+ }
+ }
}
}
}
diff --git a/src/.gitignore b/src/.gitignore
index 47a6ba7..21c0cc5 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -3,3 +3,6 @@ obj
Edm
DataModels
+
+*.ps1
+.vs
\ No newline at end of file
diff --git a/src/DBBroker.sln b/src/DBBroker.sln
deleted file mode 100644
index 3a3158a..0000000
--- a/src/DBBroker.sln
+++ /dev/null
@@ -1,49 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.5.002.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DbBroker.Cli", "DbBroker.Cli\DbBroker.Cli.csproj", "{638007D9-F403-4D4E-BB91-D366B9F4C265}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbBroker", "DBBroker\DbBroker.csproj", "{7CFB8562-9B08-44F6-AC7A-134DABF87741}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbBroker.Common", "DbBroker.Common\DbBroker.Common.csproj", "{2AB2592F-0851-46CD-8B2B-25D6EA9E6C1A}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbBroker.Showcase.Cli", "DbBroker.Showcase.Cli\DbBroker.Showcase.Cli.csproj", "{C9575870-7543-4BC2-A3A8-2DFCA270ACA7}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbBroker.Unit.Tests", "DBBroker.Unit.Tests\DbBroker.Unit.Tests.csproj", "{476FFE7A-E11E-4515-8814-DDEFBA9361F3}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {638007D9-F403-4D4E-BB91-D366B9F4C265}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {638007D9-F403-4D4E-BB91-D366B9F4C265}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {638007D9-F403-4D4E-BB91-D366B9F4C265}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {638007D9-F403-4D4E-BB91-D366B9F4C265}.Release|Any CPU.Build.0 = Release|Any CPU
- {7CFB8562-9B08-44F6-AC7A-134DABF87741}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7CFB8562-9B08-44F6-AC7A-134DABF87741}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7CFB8562-9B08-44F6-AC7A-134DABF87741}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7CFB8562-9B08-44F6-AC7A-134DABF87741}.Release|Any CPU.Build.0 = Release|Any CPU
- {2AB2592F-0851-46CD-8B2B-25D6EA9E6C1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2AB2592F-0851-46CD-8B2B-25D6EA9E6C1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2AB2592F-0851-46CD-8B2B-25D6EA9E6C1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2AB2592F-0851-46CD-8B2B-25D6EA9E6C1A}.Release|Any CPU.Build.0 = Release|Any CPU
- {C9575870-7543-4BC2-A3A8-2DFCA270ACA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C9575870-7543-4BC2-A3A8-2DFCA270ACA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C9575870-7543-4BC2-A3A8-2DFCA270ACA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C9575870-7543-4BC2-A3A8-2DFCA270ACA7}.Release|Any CPU.Build.0 = Release|Any CPU
- {476FFE7A-E11E-4515-8814-DDEFBA9361F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {476FFE7A-E11E-4515-8814-DDEFBA9361F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {476FFE7A-E11E-4515-8814-DDEFBA9361F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {476FFE7A-E11E-4515-8814-DDEFBA9361F3}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {920DA4FA-1F6D-4774-AD4D-520C4A287E56}
- EndGlobalSection
-EndGlobal
diff --git a/src/DbBroker.Showcase.Cli/Databases/MySQL/Dockerfile b/src/Databases/MySQL/Dockerfile
similarity index 100%
rename from src/DbBroker.Showcase.Cli/Databases/MySQL/Dockerfile
rename to src/Databases/MySQL/Dockerfile
diff --git a/src/DbBroker.Showcase.Cli/Databases/MySQL/init.sql b/src/Databases/MySQL/init.sql
similarity index 100%
rename from src/DbBroker.Showcase.Cli/Databases/MySQL/init.sql
rename to src/Databases/MySQL/init.sql
diff --git a/src/DbBroker.Showcase.Cli/Databases/Oracle/Dockerfile b/src/Databases/Oracle/Dockerfile
similarity index 100%
rename from src/DbBroker.Showcase.Cli/Databases/Oracle/Dockerfile
rename to src/Databases/Oracle/Dockerfile
diff --git a/src/DbBroker.Showcase.Cli/Databases/Oracle/entrypoint.sh b/src/Databases/Oracle/entrypoint.sh
similarity index 100%
rename from src/DbBroker.Showcase.Cli/Databases/Oracle/entrypoint.sh
rename to src/Databases/Oracle/entrypoint.sh
diff --git a/src/Databases/Oracle/init.sql b/src/Databases/Oracle/init.sql
new file mode 100644
index 0000000..07e1642
--- /dev/null
+++ b/src/Databases/Oracle/init.sql
@@ -0,0 +1,318 @@
+ALTER SESSION SET "_ORACLE_SCRIPT"=true;
+
+-- Create user and grant privileges
+CREATE USER DBBROKER IDENTIFIED BY DBBroker_1 DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp;
+GRANT CONNECT, RESOURCE, CREATE VIEW TO DBBROKER;
+
+ALTER USER DBBROKER quota 200M on USERS;
+
+-- Connect to the new schema
+ALTER SESSION SET CURRENT_SCHEMA = DBBROKER;
+
+-- Create the sequences and triggers for auto-increment
+CREATE SEQUENCE CUSTOMERS_NOTES_STATUS_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
+CREATE SEQUENCE ORDERS_NOTES_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
+CREATE SEQUENCE ORDERS_STATUS_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
+CREATE SEQUENCE PROMOTIONS_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
+CREATE SEQUENCE PROMOTIONS_ENROLLMENTS_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
+
+-- #TABLES
+CREATE TABLE SUPPORTED_TYPES (
+ VARCHAR2_TYPE_MAX VARCHAR2(4000) NOT NULL,
+ VARCHAR2_TYPE_MIN VARCHAR2(1) NOT NULL,
+ DATE_TYPE DATE NOT NULL,
+ TIMESTAMP_TYPE TIMESTAMP NOT NULL,
+ NUMBER_TYPE NUMBER NOT NULL,
+ BOOLEAN_TYPE NUMBER(1) NOT NULL,
+ BLOB_TYPE BLOB,
+ CLOB_TYPE CLOB,
+ NCLOB_TYPE NCLOB,
+ UUID_TYPE RAW(16) NOT NULL,
+ RAW_TYPE RAW(16) NOT NULL,
+ FLOAT_TYPE FLOAT(126) NOT NULL,
+ DOUBLE_TYPE BINARY_DOUBLE NOT NULL,
+ DECIMAL_TYPE NUMBER(10, 2) NOT NULL,
+ DECIMAL_TYPE_SCALE_0 NUMBER(10, 0) NOT NULL,
+ DECIMAL_TYPE_SCALE_5 NUMBER(10, 5) NOT NULL,
+ DECIMAL_TYPE_SCALE_10 NUMBER(10, 10) NOT NULL,
+ DECIMAL_TYPE_SCALE_15 NUMBER(20, 15) NOT NULL,
+ DECIMAL_TYPE_SCALE_20 NUMBER(30, 20) NOT NULL,
+ INTERVAL_TYPE INTERVAL DAY TO SECOND NOT NULL,
+ INTERVAL_YEAR_TYPE INTERVAL YEAR TO MONTH NOT NULL,
+ XMLTYPE_TYPE XMLTYPE NOT NULL,
+ JSON_TYPE CLOB CHECK (JSON_TYPE IS JSON) NOT NULL
+);
+
+CREATE TABLE CUSTOMERS (
+ ID RAW(16) PRIMARY KEY,
+ ADDRESS_ID RAW(16),
+ NAME VARCHAR2(250) NOT NULL,
+ EMAIL VARCHAR2(250),
+ BIRTHDAY DATE,
+ ORDERS_TOTAL NUMBER,
+ CREATED_AT TIMESTAMP NOT NULL,
+ CREATED_BY VARCHAR2(50) NOT NULL,
+ MODIFIED_AT TIMESTAMP,
+ MODIFIED_BY VARCHAR2(50)
+);
+
+CREATE TABLE ADDRESSES (
+ ID RAW(16) PRIMARY KEY,
+ STREET VARCHAR2(100),
+ CITY VARCHAR2(50),
+ STATE VARCHAR2(50),
+ POSTAL_CODE VARCHAR2(10),
+ COUNTRY VARCHAR2(50)
+);
+
+CREATE TABLE CUSTOMERS_NOTES_STATUS (
+ ID NUMBER PRIMARY KEY,
+ STATUS VARCHAR2(50)
+);
+
+CREATE TABLE CUSTOMERS_NOTES (
+ ID RAW(16) PRIMARY KEY,
+ CUSTOMER_ID RAW(16),
+ STATUS_ID NUMBER,
+ NOTE_CONTENT VARCHAR2(50) NOT NULL
+);
+
+CREATE TABLE ORDERS (
+ ID RAW(16) PRIMARY KEY,
+ CUSTOMER_ID RAW(16) NOT NULL,
+ BILLING_ADDRESS_ID RAW(16) NOT NULL,
+ SHIPPING_ADDRESS_ID RAW(16) NOT NULL,
+ STATUS_ID NUMBER,
+ CREATED_AT TIMESTAMP NOT NULL,
+ CREATED_BY VARCHAR2(50) NOT NULL,
+ MODIFIED_AT TIMESTAMP,
+ MODIFIED_BY VARCHAR2(50)
+);
+
+CREATE TABLE ORDERS_STATUS (
+ ID NUMBER PRIMARY KEY,
+ STATUS VARCHAR2(50) NOT NULL
+);
+
+CREATE TABLE ORDERS_NOTES (
+ ID NUMBER PRIMARY KEY,
+ ORDER_ID RAW(16) NOT NULL,
+ NOTE_CONTENT VARCHAR2(1024) NOT NULL
+);
+
+CREATE TABLE PRODUCTS (
+ ID RAW(16) PRIMARY KEY,
+ PRODUCT_NAME VARCHAR2(50) NOT NULL,
+ PRICE NUMBER(10, 2) DEFAULT 0
+);
+
+CREATE TABLE ORDERS_PRODUCTS (
+ ID RAW(16) PRIMARY KEY,
+ ORDER_ID RAW(16) NOT NULL,
+ PRODUCT_ID RAW(16) NOT NULL
+);
+
+CREATE TABLE PROMOTIONS (
+ ID NUMBER PRIMARY KEY,
+ TITLE VARCHAR2(50) NOT NULL,
+ EXPIRATION TIMESTAMP NOT NULL
+);
+
+CREATE TABLE PROMOTIONS_ENROLLMENTS (
+ ID NUMBER PRIMARY KEY,
+ CUSTOMER_ID RAW(16) NOT NULL,
+ PROMOTION_ID NUMBER NOT NULL
+);
+
+-- #CONSTRAINTS
+ALTER TABLE CUSTOMERS
+ADD CONSTRAINT FK_CUSTOMERS_ADDRESS_ID FOREIGN KEY (ADDRESS_ID) REFERENCES ADDRESSES(ID);
+--
+ALTER TABLE ORDERS
+ADD CONSTRAINT FK_ORDERS_CUSTOMER_ID FOREIGN KEY (CUSTOMER_ID) REFERENCES CUSTOMERS(ID);
+ALTER TABLE ORDERS
+ADD CONSTRAINT FK_ORDERS_STATUS_ID FOREIGN KEY (STATUS_ID) REFERENCES ORDERS_STATUS(ID);
+ALTER TABLE ORDERS
+ADD CONSTRAINT FK_ORDERS_BILLING_ADDRESS_ID FOREIGN KEY (BILLING_ADDRESS_ID) REFERENCES ADDRESSES(ID);
+ALTER TABLE ORDERS
+ADD CONSTRAINT FK_ORDERS_SHIPPING_ADDRESS_ID FOREIGN KEY (SHIPPING_ADDRESS_ID) REFERENCES ADDRESSES(ID);
+--
+ALTER TABLE CUSTOMERS_NOTES
+ADD CONSTRAINT FK_CUSTOMERS_NOTES_CUSTOMER_ID FOREIGN KEY (CUSTOMER_ID) REFERENCES CUSTOMERS(ID);
+ALTER TABLE CUSTOMERS_NOTES
+ADD CONSTRAINT FK_CUSTOMERS_NOTES_STATUS_ID FOREIGN KEY (STATUS_ID) REFERENCES CUSTOMERS_NOTES_STATUS(ID);
+--
+ALTER TABLE ORDERS_NOTES
+ADD CONSTRAINT FK_ORDERS_NOTES_ORDER_ID FOREIGN KEY (ORDER_ID) REFERENCES ORDERS(ID);
+--
+ALTER TABLE ORDERS_PRODUCTS
+ADD CONSTRAINT FK_ORDERS_PRODUCTS_ORDER_ID FOREIGN KEY (ORDER_ID) REFERENCES ORDERS(ID);
+ALTER TABLE ORDERS_PRODUCTS
+ADD CONSTRAINT FK_ORDERS_PRODUCTS_PRODUCT_ID FOREIGN KEY (PRODUCT_ID) REFERENCES PRODUCTS(ID);
+--
+ALTER TABLE PROMOTIONS_ENROLLMENTS
+ADD CONSTRAINT FK_PROMO_ENRO_CUSTOMER_ID FOREIGN KEY (CUSTOMER_ID) REFERENCES CUSTOMERS(ID);
+ALTER TABLE PROMOTIONS_ENROLLMENTS
+ADD CONSTRAINT FK_PROMO_ENRO_PROMOTION_ID FOREIGN KEY (PROMOTION_ID) REFERENCES PROMOTIONS(ID);
+
+
+-- #VIEWS
+CREATE OR REPLACE VIEW VW_ORDER_SUMMARY
+AS
+SELECT c.NAME AS CUSTOMER_NAME,
+ o.STATUS_ID AS ORDER_STATUS_ID,
+ s.STATUS AS ORDER_STATUS,
+ (SELECT COUNT(*) FROM ORDERS_PRODUCTS op WHERE op.ORDER_ID = o.ID ) AS NUMBER_OF_ITEMS
+FROM ORDERS o
+JOIN CUSTOMERS c ON c.ID = o.CUSTOMER_ID
+JOIN ORDERS_STATUS s ON s.ID = o.STATUS_ID
+ORDER BY o.CREATED_AT DESC;
+
+CREATE OR REPLACE VIEW VW_ORDERS
+AS
+SELECT
+ o.ID AS ORDER_ID,
+ o.CREATED_AT AS ORDER_CREATION,
+ o.CUSTOMER_ID,
+ c.NAME AS CUSTOMER_NAME,
+ p.ID AS PRODUCT_ID,
+ p.PRODUCT_NAME,
+ p.PRICE
+FROM ORDERS o
+JOIN CUSTOMERS c ON c.ID = o.CUSTOMER_ID
+JOIN ORDERS_STATUS s ON s.ID = o.STATUS_ID
+JOIN ORDERS_PRODUCTS op ON op.ORDER_ID = o.ID
+JOIN PRODUCTS p ON p.ID = op.PRODUCT_ID;
+
+
+-- #SEEDING DATA
+INSERT INTO ADDRESSES(ID, STREET, CITY, STATE, POSTAL_CODE, COUNTRY)
+VALUES(HEXTORAW('A1B2C3D4E5F60718293A4B5C6D7E8F90'), '123 Main St', 'Springfield', 'IL', '62701', 'USA');
+
+INSERT INTO ADDRESSES(ID, STREET, CITY, STATE, POSTAL_CODE, COUNTRY)
+VALUES(HEXTORAW('1234567890ABCDEF1234567890ABCDEF'), '456 Elm St', 'Springfield', 'IL', '62702', 'USA');
+
+INSERT INTO ADDRESSES(ID, STREET, CITY, STATE, POSTAL_CODE, COUNTRY)
+VALUES(HEXTORAW('FEDCBA0987654321FEDCBA0987654321'), '789 Oak St', 'Springfield', 'IL', '62703', 'USA');
+
+INSERT INTO CUSTOMERS(ID, ADDRESS_ID, NAME, BIRTHDAY, ORDERS_TOTAL, CREATED_AT, CREATED_BY)
+VALUES(HEXTORAW('00112233445566778899AABBCCDDEEFF'), HEXTORAW('A1B2C3D4E5F60718293A4B5C6D7E8F90'), 'John Doe', TO_DATE('1980-05-15', 'YYYY-MM-DD'), 0, SYSTIMESTAMP, 'system');
+
+INSERT INTO CUSTOMERS(ID, ADDRESS_ID, NAME, BIRTHDAY, ORDERS_TOTAL, CREATED_AT, CREATED_BY)
+VALUES(HEXTORAW('FFEEDDCCBBAA99887766554433221100'), HEXTORAW('1234567890ABCDEF1234567890ABCDEF'), 'Jane Smith', TO_DATE('1990-08-22', 'YYYY-MM-DD'), 0, SYSTIMESTAMP, 'system');
+
+INSERT INTO CUSTOMERS(ID, ADDRESS_ID, NAME, BIRTHDAY, ORDERS_TOTAL, CREATED_AT, CREATED_BY)
+VALUES(HEXTORAW('89ABCDEF0123456789ABCDEF01234567'), HEXTORAW('FEDCBA0987654321FEDCBA0987654321'), 'Alice Johnson', TO_DATE('1985-12-05', 'YYYY-MM-DD'), 0, SYSTIMESTAMP, 'system');
+
+INSERT INTO CUSTOMERS_NOTES_STATUS(ID, STATUS)
+VALUES(CUSTOMERS_NOTES_STATUS_SEQ.nextval, 'Active');
+
+INSERT INTO CUSTOMERS_NOTES_STATUS(ID, STATUS)
+VALUES(CUSTOMERS_NOTES_STATUS_SEQ.nextval, 'Inactive');
+
+INSERT INTO CUSTOMERS_NOTES(ID, CUSTOMER_ID, STATUS_ID, NOTE_CONTENT)
+VALUES(HEXTORAW('ABCDEF1234567890ABCDEF1234567890'), HEXTORAW('00112233445566778899AABBCCDDEEFF'), 1, 'First note for John Doe.');
+
+INSERT INTO CUSTOMERS_NOTES(ID, CUSTOMER_ID, STATUS_ID, NOTE_CONTENT)
+VALUES(HEXTORAW('1234567890ABCDEF1234567890ABCDEF'), HEXTORAW('00112233445566778899AABBCCDDEEFF'), 1, 'Second note for John Doe.');
+
+INSERT INTO PRODUCTS(ID, PRODUCT_NAME, PRICE)
+VALUES(HEXTORAW('2468ACE13579BDF02468ACE13579BDF0'), 'Product A', 5.99);
+
+INSERT INTO PRODUCTS(ID, PRODUCT_NAME, PRICE)
+VALUES(HEXTORAW('13579BDF2468ACE013579BDF2468ACE0'), 'Product B', 15.49);
+
+INSERT INTO PRODUCTS(ID, PRODUCT_NAME, PRICE)
+VALUES(HEXTORAW('0F1E2D3C4B5A69780F1E2D3C4B5A6978'), 'Product C', 25.00);
+
+INSERT INTO PRODUCTS(ID, PRODUCT_NAME, PRICE)
+VALUES(HEXTORAW('9F8E7D6C5B4A3B29F8E7D6C5B4A3B29'), 'Product D', 45.75);
+
+INSERT INTO ORDERS_STATUS(ID, STATUS)
+VALUES(ORDERS_STATUS_SEQ.nextval, 'New');
+
+INSERT INTO ORDERS_STATUS(ID, STATUS)
+VALUES(ORDERS_STATUS_SEQ.nextval, 'Pending');
+
+INSERT INTO ORDERS_STATUS(ID, STATUS)
+VALUES(ORDERS_STATUS_SEQ.nextval, 'Dispatched');
+
+INSERT INTO ORDERS_STATUS(ID, STATUS)
+VALUES(ORDERS_STATUS_SEQ.nextval, 'Delivered');
+
+INSERT INTO ORDERS_STATUS(ID, STATUS)
+VALUES(ORDERS_STATUS_SEQ.nextval, 'Canceled');
+
+INSERT INTO ORDERS(ID, CUSTOMER_ID, BILLING_ADDRESS_ID, SHIPPING_ADDRESS_ID, STATUS_ID, CREATED_AT, CREATED_BY)
+VALUES(HEXTORAW('ABCDEF1234567890ABCDEF1234567890'), HEXTORAW('00112233445566778899AABBCCDDEEFF'), HEXTORAW('1234567890ABCDEF1234567890ABCDEF'), HEXTORAW('1234567890ABCDEF1234567890ABCDEF'), 1, SYSTIMESTAMP, 'system');
+
+INSERT INTO ORDERS_PRODUCTS(ID, ORDER_ID, PRODUCT_ID)
+VALUES(HEXTORAW('11223344556677889900AABBCCDDEEFF'), HEXTORAW('ABCDEF1234567890ABCDEF1234567890'), HEXTORAW('2468ACE13579BDF02468ACE13579BDF0'));
+
+INSERT INTO ORDERS_PRODUCTS(ID, ORDER_ID, PRODUCT_ID)
+VALUES(HEXTORAW('FFEEDDCCBBAA99887766554433221100'), HEXTORAW('ABCDEF1234567890ABCDEF1234567890'), HEXTORAW('13579BDF2468ACE013579BDF2468ACE0'));
+
+INSERT INTO ORDERS_PRODUCTS(ID, ORDER_ID, PRODUCT_ID)
+VALUES(HEXTORAW('A1B2C3D4E5F60718293A4B5C6D7E8F90'), HEXTORAW('ABCDEF1234567890ABCDEF1234567890'), HEXTORAW('0F1E2D3C4B5A69780F1E2D3C4B5A6978'));
+
+INSERT INTO ORDERS_NOTES(ID, ORDER_ID, NOTE_CONTENT)
+VALUES(ORDERS_NOTES_SEQ.nextval, HEXTORAW('ABCDEF1234567890ABCDEF1234567890'), 'First note for the order.');
+
+INSERT INTO ORDERS_NOTES(ID, ORDER_ID, NOTE_CONTENT)
+VALUES(ORDERS_NOTES_SEQ.nextval, HEXTORAW('ABCDEF1234567890ABCDEF1234567890'), 'Second note for the order.');
+
+INSERT INTO ORDERS_NOTES(ID, ORDER_ID, NOTE_CONTENT)
+VALUES(ORDERS_NOTES_SEQ.nextval, HEXTORAW('ABCDEF1234567890ABCDEF1234567890'), 'Third note for the order.');
+
+INSERT INTO ORDERS_NOTES(ID, ORDER_ID, NOTE_CONTENT)
+VALUES(ORDERS_NOTES_SEQ.nextval, HEXTORAW('ABCDEF1234567890ABCDEF1234567890'), 'Fourth note for the order.');
+
+INSERT INTO ORDERS_NOTES(ID, ORDER_ID, NOTE_CONTENT)
+VALUES(ORDERS_NOTES_SEQ.nextval, HEXTORAW('ABCDEF1234567890ABCDEF1234567890'), 'Fifth note for the order.');
+
+INSERT INTO ORDERS(ID, CUSTOMER_ID, BILLING_ADDRESS_ID, SHIPPING_ADDRESS_ID, STATUS_ID, CREATED_AT, CREATED_BY)
+VALUES(HEXTORAW('1234567890ABCDEF1234567890ABCDEF'), HEXTORAW('FFEEDDCCBBAA99887766554433221100'), HEXTORAW('A1B2C3D4E5F60718293A4B5C6D7E8F90'), HEXTORAW('FEDCBA0987654321FEDCBA0987654321'), 2, SYSTIMESTAMP, 'system');
+
+INSERT INTO ORDERS_PRODUCTS(ID, ORDER_ID, PRODUCT_ID)
+VALUES(HEXTORAW('22334455667788990011AABBCCDDEEFF'), HEXTORAW('1234567890ABCDEF1234567890ABCDEF'), HEXTORAW('9F8E7D6C5B4A3B29F8E7D6C5B4A3B29'));
+
+INSERT INTO ORDERS_PRODUCTS(ID, ORDER_ID, PRODUCT_ID)
+VALUES(HEXTORAW('EEFFDDBBCCAA99887766554433221100'), HEXTORAW('1234567890ABCDEF1234567890ABCDEF'), HEXTORAW('2468ACE13579BDF02468ACE13579BDF0'));
+
+INSERT INTO ORDERS_NOTES(ID, ORDER_ID, NOTE_CONTENT)
+VALUES(ORDERS_NOTES_SEQ.nextval, HEXTORAW('1234567890ABCDEF1234567890ABCDEF'), 'First note for the second order.');
+
+INSERT INTO ORDERS_NOTES(ID, ORDER_ID, NOTE_CONTENT)
+VALUES(ORDERS_NOTES_SEQ.nextval, HEXTORAW('1234567890ABCDEF1234567890ABCDEF'), 'Second note for the second order.');
+
+INSERT INTO ORDERS(ID, CUSTOMER_ID, BILLING_ADDRESS_ID, SHIPPING_ADDRESS_ID, STATUS_ID, CREATED_AT, CREATED_BY)
+VALUES(HEXTORAW('0F1E2D3C4B5A69780F1E2D3C4B5A6978'), HEXTORAW('89ABCDEF0123456789ABCDEF01234567'), HEXTORAW('FEDCBA0987654321FEDCBA0987654321'), HEXTORAW('A1B2C3D4E5F60718293A4B5C6D7E8F90'), 3, SYSTIMESTAMP, 'system');
+
+INSERT INTO ORDERS_PRODUCTS(ID, ORDER_ID, PRODUCT_ID)
+VALUES(HEXTORAW('33445566778899001122AABBCCDDEEFF'), HEXTORAW('0F1E2D3C4B5A69780F1E2D3C4B5A6978'), HEXTORAW('13579BDF2468ACE013579BDF2468ACE0'));
+
+INSERT INTO ORDERS_PRODUCTS(ID, ORDER_ID, PRODUCT_ID)
+VALUES(HEXTORAW('DDEEFF00112233445566778899AABBCC'), HEXTORAW('0F1E2D3C4B5A69780F1E2D3C4B5A6978'), HEXTORAW('0F1E2D3C4B5A69780F1E2D3C4B5A6978'));
+
+INSERT INTO ORDERS_NOTES(ID, ORDER_ID, NOTE_CONTENT)
+VALUES(ORDERS_NOTES_SEQ.nextval, HEXTORAW('0F1E2D3C4B5A69780F1E2D3C4B5A6978'), 'First note for the third order.');
+
+INSERT INTO PROMOTIONS(ID, TITLE, EXPIRATION)
+VALUES(PROMOTIONS_SEQ.nextval, 'Spring Sale', SYSTIMESTAMP + INTERVAL '30' DAY);
+
+INSERT INTO PROMOTIONS(ID, TITLE, EXPIRATION)
+VALUES(PROMOTIONS_SEQ.nextval, 'Summer Sale', SYSTIMESTAMP + INTERVAL '60' DAY);
+
+INSERT INTO PROMOTIONS_ENROLLMENTS(ID, CUSTOMER_ID, PROMOTION_ID)
+VALUES(PROMOTIONS_ENROLLMENTS_SEQ.nextval, HEXTORAW('00112233445566778899AABBCCDDEEFF'), 1);
+
+INSERT INTO PROMOTIONS_ENROLLMENTS(ID, CUSTOMER_ID, PROMOTION_ID)
+VALUES(PROMOTIONS_ENROLLMENTS_SEQ.nextval, HEXTORAW('FFEEDDCCBBAA99887766554433221100'), 1);
+
+INSERT INTO PROMOTIONS_ENROLLMENTS(ID, CUSTOMER_ID, PROMOTION_ID)
+VALUES(PROMOTIONS_ENROLLMENTS_SEQ.nextval, HEXTORAW('89ABCDEF0123456789ABCDEF01234567'), 2);
+
+INSERT INTO PROMOTIONS_ENROLLMENTS(ID, CUSTOMER_ID, PROMOTION_ID)
+VALUES(PROMOTIONS_ENROLLMENTS_SEQ.nextval, HEXTORAW('00112233445566778899AABBCCDDEEFF'), 2);
+
+COMMIT;
\ No newline at end of file
diff --git a/src/DbBroker.Showcase.Cli/Databases/Postgres/Dockerfile b/src/Databases/Postgres/Dockerfile
similarity index 100%
rename from src/DbBroker.Showcase.Cli/Databases/Postgres/Dockerfile
rename to src/Databases/Postgres/Dockerfile
diff --git a/src/DbBroker.Showcase.Cli/Databases/Postgres/init.sql b/src/Databases/Postgres/init.sql
similarity index 58%
rename from src/DbBroker.Showcase.Cli/Databases/Postgres/init.sql
rename to src/Databases/Postgres/init.sql
index 76321f8..af2b955 100644
--- a/src/DbBroker.Showcase.Cli/Databases/Postgres/init.sql
+++ b/src/Databases/Postgres/init.sql
@@ -9,7 +9,7 @@ CREATE TABLE customers (
id UUID PRIMARY KEY,
name VARCHAR(250) NOT NULL,
birthday DATE,
- orders_total INT,
+ orders_count INT,
created_at TIMESTAMPTZ NOT NULL,
created_by VARCHAR(50) NOT NULL,
modified_at TIMESTAMPTZ,
@@ -54,7 +54,8 @@ CREATE TABLE orders_notes (
CREATE TABLE products (
id UUID PRIMARY KEY,
- product_name VARCHAR(50) NOT NULL
+ product_name VARCHAR(50) NOT NULL,
+ price DECIMAL(10, 2) NOT NULL
);
CREATE TABLE orders_products (
@@ -79,11 +80,52 @@ CREATE TABLE promotions_enrollments (
FOREIGN KEY (promotion_id) REFERENCES promotions (id)
);
--- Data
+-- SEEDING DATA
+-- Customers >>>
+INSERT INTO customers (id, name, birthday, orders_count, created_at, created_by)
+VALUES ('123e4567-e89b-12d3-a456-426614174000', 'John Doe', '1980-01-01', 5, NOW(), 'system');
+
+INSERT INTO customers (id, name, birthday, orders_count, created_at, created_by)
+VALUES ('223e4567-e89b-12d3-a456-426614174001', 'Jane Smith', '1990-02-02', 3, NOW(), 'system');
+
+INSERT INTO customers (id, name, birthday, orders_count, created_at, created_by)
+VALUES ('323e4567-e89b-12d3-a456-426614174002', 'Alice Johnson', '1985-03-03', 8, NOW(), 'system');
+-- <<< Customers
+
+-- Customers Notes Status >>>
INSERT INTO customers_notes_status (status)
VALUES ('Enabled');
INSERT INTO customers_notes_status (status)
VALUES ('Disabled');
+-- <<< Customers Notes Status
+
+-- Products >>>
+INSERT INTO products (id, product_name, price)
+VALUES ('11111111-1111-1111-1111-111111111111', 'Product A', 10.00);
+
+INSERT INTO products (id, product_name, price)
+VALUES ('22222222-2222-2222-2222-222222222222', 'Product B', 20.00);
+
+INSERT INTO products (id, product_name, price)
+VALUES ('33333333-3333-3333-3333-333333333333', 'Product C', 30.00);
+-- <<< Products
+
+-- Order Status >>>
+INSERT INTO order_status (status)
+VALUES ('New');
+
+INSERT INTO order_status (status)
+VALUES ('Processing');
+
+INSERT INTO order_status (status)
+VALUES ('Shipped');
+
+INSERT INTO order_status (status)
+VALUES ('Delivered');
+
+INSERT INTO order_status (status)
+VALUES ('Cancelled');
+-- <<< Order Status
diff --git a/src/DbBroker.Showcase.Cli/Databases/SqlServer/Dockerfile b/src/Databases/SqlServer/Dockerfile
similarity index 100%
rename from src/DbBroker.Showcase.Cli/Databases/SqlServer/Dockerfile
rename to src/Databases/SqlServer/Dockerfile
diff --git a/src/DbBroker.Showcase.Cli/Databases/SqlServer/entrypoint.sh b/src/Databases/SqlServer/entrypoint.sh
similarity index 100%
rename from src/DbBroker.Showcase.Cli/Databases/SqlServer/entrypoint.sh
rename to src/Databases/SqlServer/entrypoint.sh
diff --git a/src/DbBroker.Showcase.Cli/Databases/SqlServer/init.sql b/src/Databases/SqlServer/init.sql
similarity index 71%
rename from src/DbBroker.Showcase.Cli/Databases/SqlServer/init.sql
rename to src/Databases/SqlServer/init.sql
index 25ddaa6..5b15f05 100644
--- a/src/DbBroker.Showcase.Cli/Databases/SqlServer/init.sql
+++ b/src/Databases/SqlServer/init.sql
@@ -6,13 +6,16 @@ GO
USE [DBBroker]
GO
+-- #TABLES
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Customers](
[Id] [uniqueidentifier] NOT NULL,
+ [AddressId] [uniqueidentifier],
[Name] [varchar](250) NOT NULL,
+ [Email] [varchar](250) NULL,
[Birthday] [date] NULL,
[OrdersTotal] [int] NULL,
[CreatedAt] [datetime] NOT NULL,
@@ -25,6 +28,21 @@ CREATE TABLE [dbo].[Customers](
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
+
+CREATE TABLE [dbo].[Addresses](
+ [Id] [uniqueidentifier] NOT NULL,
+ [Street] [varchar](250) NOT NULL,
+ [City] [varchar](100) NOT NULL,
+ [State] [varchar](50) NOT NULL,
+ [PostalCode] [varchar](20) NOT NULL,
+ [Country] [varchar](100) NOT NULL
+ CONSTRAINT [PK_Addresses] PRIMARY KEY CLUSTERED
+(
+ [Id] ASC
+)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
+) ON [PRIMARY]
+GO
+
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
@@ -40,6 +58,7 @@ CREATE TABLE [dbo].[CustomersNotes](
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
+
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
@@ -53,6 +72,7 @@ CREATE TABLE [dbo].[CustomersNotesStatus](
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
+
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
@@ -60,6 +80,8 @@ GO
CREATE TABLE [dbo].[Orders](
[Id] [uniqueidentifier] NOT NULL,
[CustomerId] [uniqueidentifier] NOT NULL,
+ [BillingAddressId] [uniqueidentifier] NOT NULL,
+ [ShippingAddressId] [uniqueidentifier] NOT NULL,
[StatusId] [int] NULL,
[CreatedAt] [datetime] NOT NULL,
[CreatedBy] [varchar](50) NOT NULL,
@@ -71,12 +93,14 @@ CREATE TABLE [dbo].[Orders](
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
+
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[OrdersNotes](
[Id] [int] IDENTITY(1,1) NOT NULL,
+ [OrderId] [uniqueidentifier] NOT NULL,
[NoteContent] [varchar](1024) NOT NULL,
CONSTRAINT [PK_OrderNotes] PRIMARY KEY CLUSTERED
(
@@ -84,6 +108,7 @@ CREATE TABLE [dbo].[OrdersNotes](
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
+
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
@@ -98,19 +123,21 @@ CREATE TABLE [dbo].[OrdersProducts](
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
+
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-CREATE TABLE [dbo].[OrderStatus](
+CREATE TABLE [dbo].[OrdersStatus](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Status] [varchar](50) NOT NULL,
- CONSTRAINT [PK_OrderStatus] PRIMARY KEY CLUSTERED
+ CONSTRAINT [PK_OrdersStatus] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
+
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
@@ -118,12 +145,14 @@ GO
CREATE TABLE [dbo].[Products](
[Id] [uniqueidentifier] NOT NULL,
[ProductName] [varchar](50) NOT NULL,
+ [Price] [decimal](18, 2) NOT NULL
CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
+
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
@@ -138,6 +167,7 @@ CREATE TABLE [dbo].[Promotions](
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
+
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
@@ -152,6 +182,33 @@ CREATE TABLE [dbo].[PromotionsEnrollments](
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
+
+-- #CONSTRAINTS
+ALTER TABLE [dbo].[Customers] WITH CHECK ADD CONSTRAINT FK_CustomersAddressId FOREIGN KEY([AddressId])
+REFERENCES [dbo].[Addresses] ([Id])
+GO
+--
+ALTER TABLE [dbo].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([CustomerId])
+REFERENCES [dbo].[Customers] ([Id])
+GO
+ALTER TABLE [dbo].[Orders] CHECK CONSTRAINT [FK_Orders_Customers]
+GO
+ALTER TABLE [dbo].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_OrdersStatus] FOREIGN KEY([StatusId])
+REFERENCES [dbo].[OrdersStatus] ([Id])
+GO
+ALTER TABLE [dbo].[Orders] CHECK CONSTRAINT [FK_Orders_OrdersStatus]
+GO
+ALTER TABLE [dbo].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_BillingAddress] FOREIGN KEY([BillingAddressId])
+REFERENCES [dbo].[Addresses] ([Id])
+GO
+ALTER TABLE [dbo].[Orders] CHECK CONSTRAINT [FK_Orders_BillingAddress]
+GO
+ALTER TABLE [dbo].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_ShippingAddress] FOREIGN KEY([ShippingAddressId])
+REFERENCES [dbo].[Addresses] ([Id])
+GO
+ALTER TABLE [dbo].[Orders] CHECK CONSTRAINT [FK_Orders_ShippingAddress]
+GO
+--
ALTER TABLE [dbo].[CustomersNotes] WITH CHECK ADD CONSTRAINT [FK_CustomerNotes_Customers] FOREIGN KEY([CustomerId])
REFERENCES [dbo].[Customers] ([Id])
GO
@@ -162,16 +219,13 @@ REFERENCES [dbo].[CustomersNotesStatus] ([Id])
GO
ALTER TABLE [dbo].[CustomersNotes] CHECK CONSTRAINT [FK_CustomersNotes_CustomersNotesStatus]
GO
-ALTER TABLE [dbo].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([CustomerId])
-REFERENCES [dbo].[Customers] ([Id])
-GO
-ALTER TABLE [dbo].[Orders] CHECK CONSTRAINT [FK_Orders_Customers]
-GO
-ALTER TABLE [dbo].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_OrderStatus] FOREIGN KEY([StatusId])
-REFERENCES [dbo].[OrderStatus] ([Id])
+--
+ALTER TABLE [dbo].[OrdersNotes] WITH CHECK ADD CONSTRAINT [FK_OrderNotes_Orders] FOREIGN KEY([OrderId])
+REFERENCES [dbo].[Orders] ([Id])
GO
-ALTER TABLE [dbo].[Orders] CHECK CONSTRAINT [FK_Orders_OrderStatus]
+ALTER TABLE [dbo].[OrdersNotes] CHECK CONSTRAINT [FK_OrderNotes_Orders]
GO
+--
ALTER TABLE [dbo].[OrdersProducts] WITH CHECK ADD CONSTRAINT [FK_OrderProducts_Orders] FOREIGN KEY([OrderId])
REFERENCES [dbo].[Orders] ([Id])
GO
@@ -182,6 +236,7 @@ REFERENCES [dbo].[Products] ([Id])
GO
ALTER TABLE [dbo].[OrdersProducts] CHECK CONSTRAINT [FK_OrderProducts_Products]
GO
+--
ALTER TABLE [dbo].[PromotionsEnrollments] WITH CHECK ADD CONSTRAINT [FK_PromotionsEnrollments_Customers] FOREIGN KEY([CustomerId])
REFERENCES [dbo].[Customers] ([Id])
GO
@@ -192,5 +247,33 @@ REFERENCES [dbo].[Promotions] ([Id])
GO
ALTER TABLE [dbo].[PromotionsEnrollments] CHECK CONSTRAINT [FK_PromotionsEnrollments_Promotions]
GO
-EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Primary key' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'CustomersNotes', @level2type=N'COLUMN',@level2name=N'Id'
+
+
+-- #VIEWS
+CREATE OR ALTER VIEW VwOrderSummary
+AS
+SELECT c.Name AS CustomerName,
+ o.StatusId AS OrderStatusId,
+ s.Status AS OrderStatus,
+ (SELECT COUNT(*) FROM OrdersProducts op WHERE op.OrderId = o.Id ) AS NumberOfItems
+FROM Orders o
+JOIN Customers c ON c.ID = o.CustomerId
+JOIN OrdersStatus s ON s.Id = o.StatusId;
+GO
+
+CREATE OR ALTER VIEW VwOrders
+AS
+SELECT
+ o.Id AS OrderId,
+ o.CreatedAt AS OrderCreation,
+ o.CustomerId,
+ c.Name AS CustomerName,
+ p.Id AS ProductId,
+ p.ProductName,
+ p.Price
+FROM Orders o
+JOIN Customers c ON c.Id = o.CustomerId
+JOIN OrdersStatus s ON s.Id = o.StatusId
+JOIN OrdersProducts op ON op.OrderId = o.Id
+JOIN Products p ON p.Id = op.ProductId;
GO
diff --git a/src/DbBroker.Showcase.Cli/docker-compose.yaml b/src/Databases/docker-compose.yaml
similarity index 61%
rename from src/DbBroker.Showcase.Cli/docker-compose.yaml
rename to src/Databases/docker-compose.yaml
index d479e04..f56fd8b 100644
--- a/src/DbBroker.Showcase.Cli/docker-compose.yaml
+++ b/src/Databases/docker-compose.yaml
@@ -1,8 +1,11 @@
-name: dbbroker-showcase
+name: dbbroker-databases
+
services:
sqlserver:
+ container_name: sqlserver
+ image: dbbroker-sqlserver
build:
- context: ./Databases/SqlServer
+ context: ./SqlServer
dockerfile: Dockerfile
environment:
SA_PASSWORD: "DBBroker_1"
@@ -11,19 +14,28 @@ services:
- "1439:1433"
networks:
- database-network
+ profiles:
+ - all
+ - no-oracle
oracle:
+ container_name: oracle
+ image: dbbroker-oracle
build:
- context: ./Databases/Oracle
+ context: ./Oracle
dockerfile: Dockerfile
ports:
- "1529:1521"
networks:
- database-network
+ profiles:
+ - all
postgres:
+ container_name: postgres
+ image: dbbroker-postgres
build:
- context: ./Databases/Postgres
+ context: ./Postgres
dockerfile: Dockerfile
environment:
POSTGRES_USER: "dbbroker"
@@ -33,11 +45,16 @@ services:
- "5439:5432"
networks:
- database-network
+ profiles:
+ - all
+ - no-oracle
- mysql:
+ mysql:
+ container_name: mysql
+ image: dbbroker-mysql
build:
- context: ./Databases/MySQL
- dockerfile: Dockerfile
+ context: ./MySQL
+ dockerfile: Dockerfile
environment:
MYSQL_ROOT_PASSWORD: "DBBroker_1"
MYSQL_DATABASE: "dbbroker"
@@ -47,6 +64,9 @@ services:
- "3306:3306"
networks:
- database-network
+ profiles:
+ - all
+ - no-oracle
networks:
database-network:
diff --git a/src/DbBroker.Cli/Commands/Init/InitCommand.cs b/src/DbBroker.Cli/Commands/Init/InitCommand.cs
new file mode 100644
index 0000000..9cdb54e
--- /dev/null
+++ b/src/DbBroker.Cli/Commands/Init/InitCommand.cs
@@ -0,0 +1,62 @@
+using DbBroker.Cli.Extensions;
+
+namespace DbBroker.Cli.Commands.Init;
+
+public class InitCommand
+{
+
+ private const string DbBrokerConfigFileContents =
+ """
+{
+ "$schema": "https://raw.githubusercontent.com/diegosiao/DBBroker/refs/heads/v3.0.0/docs/json-schema/dbbroker.config.schema.json",
+ "database": {
+ "provider": "$PROVIDER$",
+ "connectionString": "$CONNECTIONSTRING$"
+ },
+ "contexts": [
+ {
+ "namespace": "$NAMESPACE$",
+ "outputDirectory": null,
+ "tables": [],
+ "views": []
+ }
+ ]
+}
+""";
+
+ internal static int Execute(InitOptions opts)
+ {
+ if (opts?.Namespace is null || opts?.ConnectionString is null)
+ {
+ "You must provide at least --namespace and --connectionString parameters.".Error();
+ return ExitCodes.CONFIG_FILE_INIT_ERROR;
+ }
+
+ var dbbrokerconfigFilePath = Path.Join(opts.WorkingDirectory ?? Directory.GetCurrentDirectory(), "dbbroker.config.json");
+
+ try
+ {
+ if (File.Exists(dbbrokerconfigFilePath) && !opts.Force)
+ {
+ "The 'dbbroker.config.json' file already exists. Use --force to replace it.".Error();
+ return ExitCodes.CONFIG_FILE_INIT_ERROR;
+ }
+
+ File.WriteAllText(
+ dbbrokerconfigFilePath,
+ DbBrokerConfigFileContents
+ .Replace("$NAMESPACE$", opts.Namespace)
+ .Replace("$CONNECTIONSTRING$", opts.ConnectionString)
+ .Replace("$PROVIDER$", opts.Provider.ToString()));
+
+ $"Configuration file created successfully at '{dbbrokerconfigFilePath}'.".Success();
+ return ExitCodes.SUCCESS;
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine(ex.Message);
+ Console.Error.WriteLine(ex.StackTrace);
+ return ExitCodes.CONFIG_FILE_INIT_ERROR;
+ }
+ }
+}
diff --git a/src/DbBroker.Cli/Commands/Init/InitOptions.cs b/src/DbBroker.Cli/Commands/Init/InitOptions.cs
index f487312..a1cf788 100644
--- a/src/DbBroker.Cli/Commands/Init/InitOptions.cs
+++ b/src/DbBroker.Cli/Commands/Init/InitOptions.cs
@@ -1,27 +1,39 @@
-using System;
using CommandLine;
-using DbBroker.Cli.Extensions;
using DbBroker.Common;
-using DbBroker.Common.Model;
namespace DbBroker.Cli.Commands.Init;
-[Verb("init", HelpText = "Initializes the 'dbbroker.config.json' file")]
+[Verb("init", HelpText = "Initializes and outputs a 'dbbroker.config.json' with the parameters specified.")]
public class InitOptions
{
[Option(
- 'v',
- "vendor",
- HelpText = "Database vendor ",
+ 'n',
+ "namespace",
+ HelpText = "The namespace the classes generated should be put in. Required.")]
+ public string? Namespace { get; init; }
+
+ [Option(
+ 'p',
+ "provider",
+ HelpText = "The database provider name . Default is 'SqlServer'.",
Default = SupportedDatabaseProviders.SqlServer)]
- public SupportedDatabaseProviders Vendor { get; init; }
+ public SupportedDatabaseProviders Provider { get; init; }
+
+ [Option(
+ 'c',
+ "connectionString",
+ HelpText = "The database provider connection string. Required.")]
+ public string? ConnectionString { get; init; }
- [Option('c', "connectionString", HelpText = "Database vendor connection string", Required = true)]
- public required string ConnectionString { get; init; }
+ [Option(
+ 'f',
+ "force",
+ HelpText = "Overrides the dbbroker.config.json file if it exists. Default is false.")]
+ public bool Force { get; set; }
- internal static int Execute(InitOptions opts)
- {
- $"Initializing '{opts.Vendor}'".Log();
- return 0;
- }
+ [Option(
+ 'd',
+ "working-directory",
+ HelpText = "Overrides the executable working directory.")]
+ public string? WorkingDirectory { get; set; }
}
diff --git a/src/DbBroker.Cli/Commands/Sync/SyncCommand.cs b/src/DbBroker.Cli/Commands/Sync/SyncCommand.cs
index 827c4c7..2137bcf 100644
--- a/src/DbBroker.Cli/Commands/Sync/SyncCommand.cs
+++ b/src/DbBroker.Cli/Commands/Sync/SyncCommand.cs
@@ -9,30 +9,57 @@ public class SyncCommand
{
internal static Dictionary Results = [];
+ internal static SyncOptions Options { get; private set; } = new SyncOptions();
+
public static int Execute(SyncOptions options)
{
- "Synchronizing...".Log();
- var configFilesDirectory = options.ConfigFilesDirectory ?? Directory.GetCurrentDirectory();
- var configFiles = Directory.EnumerateFiles(configFilesDirectory, "dbbroker.config.*");
+ Options = options;
+
+ var startTime = DateTime.Now;
+ $"Synchronizing... Started at {startTime}.".Log();
+ var configFiles =
+ options.ConfigurationFiles.Any() ?
+ options.ConfigurationFiles :
+ Directory.EnumerateFiles(Directory.GetCurrentDirectory(), "dbbroker.config.*");
+ var configFilesDirectory = Directory.GetCurrentDirectory();
if (!configFiles.Any())
{
- $"No configuration file was found at '{configFilesDirectory}'.".Error();
- $"Run 'dbbroker init' at your *.csproj file level to create one.".Error();
+ $"No configuration file was found at '{configFilesDirectory}' and no file was specified using --file argument.".Error();
+ $"Run 'dbbroker init' at the same directory of your *.csproj file to create one.".Error();
return ExitCodes.CONFIG_FILE_NOTFOUND;
}
$"Loading {configFiles.Count()} configuration(s) file(s):".Log();
List contexts = [];
+ bool anyLoadedContext = false;
foreach (var configFile in configFiles)
{
$"- {configFile}".Log();
if (TryParseConfig(configFile, out DbBrokerConfig? config))
{
+ if (config?.Database is not null)
+ {
+ foreach (var context in config.Contexts)
+ {
+ context.Provider ??= config.Database.Provider;
+ context.ConnectionString ??= config.Database.ConnectionString;
+ }
+ }
+
contexts.AddRange(config!.Contexts!);
+ anyLoadedContext = true;
}
}
+ "".Log(); // empty line
+
+ if (!anyLoadedContext)
+ {
+ "No database context loaded correctly. Finished with error.".Error();
+ return ExitCodes.NO_CONTEXT_LOADED_ERROR;
+ }
+
var tasks = contexts
.Select(x => new CSharpClassGenerator().GenerateAsync(x));
@@ -40,15 +67,15 @@ public static int Execute(SyncOptions options)
if (Results.Sum(x => x.Value) == 0)
{
- "Data Models synchronized.".Success();
+ $"Synchronization finished at '{DateTime.Now}' ({(DateTime.Now - startTime).TotalSeconds:N2} seconds).".Success();
return ExitCodes.SUCCESS;
}
"There is an error synchronizing the Data Models.".Error();
- "Namespaces with error(s): ".Error();
+ "Contexts with error(s): ".Error();
foreach (var result in Results)
{
- $"- {result.Key}: Code {result.Value}".Error();
+ $"- {result.Key}: [{result.Value}] {ExitCodes.Messages[result.Value]}".Error();
}
return 1;
}
diff --git a/src/DbBroker.Cli/Commands/Sync/SyncOptions.cs b/src/DbBroker.Cli/Commands/Sync/SyncOptions.cs
index 4851833..ebf912b 100644
--- a/src/DbBroker.Cli/Commands/Sync/SyncOptions.cs
+++ b/src/DbBroker.Cli/Commands/Sync/SyncOptions.cs
@@ -5,9 +5,15 @@ namespace DbBroker.Cli.Commands.Sync;
[Verb("sync", HelpText = "Synchronizes your Entity Data Models with the respective database")]
public class SyncOptions
{
- [Option('d', "database", HelpText="The specific database you want to update")]
+ [Option(
+ 'n',
+ "namespace",
+ HelpText = "The specific context namespace you want to update. You can use the namespace or context name.")]
public string? Context { get; init; }
- [Option('c', "configFilesDirectory", HelpText="Provide a file path directory to one or more configuration file you want to use to synchronize. The file name needs to start with 'dbbroker.config*'.")]
- public string? ConfigFilesDirectory { get; init; }
+ [Option(
+ 'f',
+ "file",
+ HelpText = "Provide a file path to a configuration file you want to use to synchronize. You can use multiple files at once.")]
+ public IEnumerable ConfigurationFiles { get; init; } = [];
}
diff --git a/src/DbBroker.Cli/Constants.cs b/src/DbBroker.Cli/Constants.cs
index 68d29ad..80d3146 100644
--- a/src/DbBroker.Cli/Constants.cs
+++ b/src/DbBroker.Cli/Constants.cs
@@ -8,21 +8,39 @@ public static class Constants
public const string EDM_PROPERTY_TEMPLATE =
@"
- private $TYPE _$NAME;
+ private $TYPE _$NAME;
- [$KEYColumn(name: ""$COLUMN_NAME"")]
- public $TYPE $NAME
- {
- get
- {
- return _$NAME;
- }
- set
- {
- _IsNotPristine[nameof($NAME)] = true;
- _$NAME = value;
- }
- }";
+ [$KEYColumn(""$COLUMN_NAME""), ColumnType($$PROVIDER_DBTYPE$$)]
+ public $TYPE $NAME
+ {
+ get
+ {
+ return _$NAME;
+ }
+ set
+ {
+ _IsNotPristine[nameof($NAME)] = true;
+ _$NAME = value;
+ }
+ }";
+
+ public const string EDM_PROPERTY_INDENTED_TEMPLATE =
+@"
+ private $TYPE _$NAME;
+
+ [$KEYColumn(""$COLUMN_NAME""), ColumnType($$PROVIDER_DBTYPE$$)]
+ public $TYPE $NAME
+ {
+ get
+ {
+ return _$NAME;
+ }
+ set
+ {
+ _IsNotPristine[nameof($NAME)] = true;
+ _$NAME = value;
+ }
+ }";
public const string EDM_REFERENCE_TEMPLATE =
@"
@@ -40,25 +58,91 @@ public static class Constants
@"using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+$$PROVIDER_CLIENT_NAMESPACE$$
using DbBroker.Attributes;
using DbBroker.Common;
using DbBroker.Model;
+using System.Collections.Generic;
-namespace $NAMESPACE;
+namespace $NAMESPACE
+{
+ [Table(""$TABLE"", Schema = ""$SCHEMA"")]
+ public class $CLASSNAME : DataModel<$CLASSNAME>
+ {
+ $PROPERTIES
+ $REFERENCES
+ $COLLECTIONS
+ static $CLASSNAME()
+ {
+ Provider = SupportedDatabaseProviders.$PROVIDER;
+ SqlInsertTemplateTypeFullName = ""$ISQLINSERTTEMPLATETYPEFULLNAME"";
+ SqlInsertTemplateTypeArguments = new object[] {$ISQLINSERTTEMPLATETYPEARGUMENTS};
+ }
+ }
+}
+";
+ public const string EDM_VIEW_CLASS_TEMPLATE =
+@"using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+$$PROVIDER_CLIENT_NAMESPACE$$
+using DbBroker.Attributes;
+using DbBroker.Common;
+using DbBroker.Model;
+using DbBroker.Model.Interfaces;
-[Table(name: ""$TABLE"", Schema = ""$SCHEMA"")]
-public class $CLASSNAME : DataModel<$CLASSNAME>
+namespace $NAMESPACE
{
+ [Table(""$TABLE"", Schema = ""$SCHEMA"")]
+ public class $CLASSNAME : ViewDataModel<$CLASSNAME>, IViewDataModel
+ {
+ $PROPERTIES
+ $REFERENCES
+ $CLASSES
+ $TUPLE
+ static $CLASSNAME()
+ {
+ Provider = SupportedDatabaseProviders.$PROVIDER;
+ }
+ }
+}
+";
+
+ public const string EDM_VIEW_INTERNAL_CLASS_TEMPLATE = @"
+ public class $CLASSNAME
+ {
$PROPERTIES
-$REFERENCES
-$COLLECTIONS
+ }";
- static $CLASSNAME()
+ public const string EDM_VIEW_TUPLE_CLASS_TEMPLATE =
+ @" public class $CLASSNAMETuple : DataModel<$CLASSNAMETuple>, IViewDataModelTuple
{
- Provider = SupportedDatabaseProviders.$PROVIDER;
- SqlInsertTemplateTypeFullName = ""$ISQLINSERTTEMPLATETYPEFULLNAME"";
- SqlInsertTemplateTypeArguments = [$ISQLINSERTTEMPLATETYPEARGUMENTS];
+$PROPERTIES
}
-}
+";
+
+ public const string EDM_VIEW_PROPERTY_TEMPLATE =
+@"
+ [$KEYColumn(""$COLUMN_NAME""), ColumnType($$PROVIDER_DBTYPE$$)]
+ public $TYPE $NAME { get; set; }
+";
+
+ public const string EDM_VIEW_PROPERTY_INDENTED_TEMPLATE =
+@"
+ [$KEYColumn(""$COLUMN_NAME"", ColumnType($$PROVIDER_DBTYPE$$))]
+ public $TYPE $NAME { get; set; }
+";
+
+
+ public const string EDM_REFERENCE_VIEW_TEMPLATE =
+@"
+ [DataModelReference(nameof($$REFTYPENAME$$.$$REFPROPERTYNAME$$))]
+ public $$REFTYPENAME$$? $$REFTYPENAME$$Ref { get; set; }
+";
+
+ public const string EDM_COLLECTION_REFERENCE_VIEW_TEMPLATE =
+@"
+ [DataModelCollectionReference(nameof($$REFTYPENAME$$.$$REFPROPERTYNAME$$), typeof($$REFTYPENAME$$))]
+ public IEnumerable<$$REFTYPENAME$$>? $$REFTYPENAME$$Refs { get; set; }
";
}
diff --git a/src/DbBroker.Cli/DbBroker.Cli.csproj b/src/DbBroker.Cli/DbBroker.Cli.csproj
index d088b25..be01ee1 100644
--- a/src/DbBroker.Cli/DbBroker.Cli.csproj
+++ b/src/DbBroker.Cli/DbBroker.Cli.csproj
@@ -11,11 +11,11 @@
true
DBBroker.Cli
- 1.0.0-beta
+ 1.0.1-beta
Diego De Medeiros (https://github.com/diegosiao/DBBroker)
- CLI to generate Entity Data Models
+ CLI to generate Entity Data Models which DBBroker library uses to interact with database.
orm, data, mapping, sql server, oracle, mysql
MIT
@@ -37,6 +37,7 @@
+
diff --git a/src/DbBroker.Cli/DbBroker.Cli.csproj.user b/src/DbBroker.Cli/DbBroker.Cli.csproj.user
new file mode 100644
index 0000000..c04708c
--- /dev/null
+++ b/src/DbBroker.Cli/DbBroker.Cli.csproj.user
@@ -0,0 +1,9 @@
+
+
+
+ ProjectDebugger
+
+
+ DbBroker.Cli.Sync
+
+
\ No newline at end of file
diff --git a/src/DbBroker.Cli/Exceptions/DbBrokerConfigurationException.cs b/src/DbBroker.Cli/Exceptions/DbBrokerConfigurationException.cs
new file mode 100644
index 0000000..b7aba40
--- /dev/null
+++ b/src/DbBroker.Cli/Exceptions/DbBrokerConfigurationException.cs
@@ -0,0 +1,6 @@
+namespace DbBroker.Cli.Exceptions;
+
+public class DbBrokerConfigurationException : ApplicationException
+{
+ public DbBrokerConfigurationException(string message) : base(message) { }
+}
diff --git a/src/DbBroker.Cli/ExitCodes.cs b/src/DbBroker.Cli/ExitCodes.cs
index 68e40fb..2f4b8e5 100644
--- a/src/DbBroker.Cli/ExitCodes.cs
+++ b/src/DbBroker.Cli/ExitCodes.cs
@@ -5,4 +5,25 @@ public static class ExitCodes
public const int SUCCESS = 0;
public const int CONFIG_FILE_NOTFOUND = 1;
+
+ public const int CONFIG_FILE_INIT_ERROR = 2;
+
+ public const int CONFIG_INVALID = 3;
+
+ public const int NO_CONTEXT_LOADED_ERROR = 4;
+
+ public const int CONTEXT_CONNECTION_ERROR = 5;
+
+ public const int CONTEXT_GENERIC_ERROR = 6;
+
+ public static readonly Dictionary Messages = new()
+ {
+ { SUCCESS, "Success" },
+ { CONFIG_FILE_NOTFOUND, "Configuration file not found" },
+ { CONFIG_FILE_INIT_ERROR, "Error creating configuration file" },
+ { CONFIG_INVALID, "Invalid configuration found" },
+ { NO_CONTEXT_LOADED_ERROR, "No database context loaded" },
+ { CONTEXT_CONNECTION_ERROR, "Error connecting to database context" },
+ { CONTEXT_GENERIC_ERROR, "Generic error processing metadata" },
+ };
}
\ No newline at end of file
diff --git a/src/DbBroker.Cli/Extensions/ConsoleExtensions.cs b/src/DbBroker.Cli/Extensions/ConsoleExtensions.cs
index 21d9cb8..4da99c0 100644
--- a/src/DbBroker.Cli/Extensions/ConsoleExtensions.cs
+++ b/src/DbBroker.Cli/Extensions/ConsoleExtensions.cs
@@ -1,3 +1,5 @@
+using System.Diagnostics;
+
namespace DbBroker.Cli.Extensions;
public static class ConsoleExtensions
@@ -5,7 +7,24 @@ public static class ConsoleExtensions
public static void Success(this string console, string? contextNamespace = null, int linesAbove = 1)
{
Console.ForegroundColor = ConsoleColor.Green;
-
+
+ for (int i = 0; i < linesAbove; i++)
+ {
+ Console.WriteLine();
+ }
+
+ if (!string.IsNullOrEmpty(contextNamespace))
+ {
+ Console.Write($"{contextNamespace} | ");
+ }
+ Console.WriteLine(console);
+ Console.ResetColor();
+ }
+
+ public static void Warning(this string console, string? contextNamespace = null, int linesAbove = 1)
+ {
+ Console.ForegroundColor = ConsoleColor.Yellow;
+
for (int i = 0; i < linesAbove; i++)
{
Console.WriteLine();
@@ -39,4 +58,22 @@ public static void Error(this string message, string? contextNamespace = null)
Console.Error.WriteLine(message);
Console.ResetColor();
}
+
+ public static void Debug(this string message, string? contextNamespace = null)
+ {
+ if (!Debugger.IsAttached)
+ {
+ return;
+ }
+
+ Console.ForegroundColor = ConsoleColor.Yellow;
+
+ if (!string.IsNullOrEmpty(contextNamespace))
+ {
+ Console.Write($"{contextNamespace} | ");
+ }
+ Console.WriteLine(message);
+ Console.ResetColor();
+ }
+
}
diff --git a/src/DbBroker.Cli/Extensions/DbBrokerConfigContextExtensions.cs b/src/DbBroker.Cli/Extensions/DbBrokerConfigContextExtensions.cs
index b3190ad..8a1b83e 100644
--- a/src/DbBroker.Cli/Extensions/DbBrokerConfigContextExtensions.cs
+++ b/src/DbBroker.Cli/Extensions/DbBrokerConfigContextExtensions.cs
@@ -1,11 +1,12 @@
using System.Data.Common;
using System.Data.SqlClient;
using DbBroker.Cli.Services.Interfaces;
-using DbBroker.Cli.Services.Providers;
using DbBroker.Cli.Services.Providers.Oracle;
+using DbBroker.Cli.Services.Providers.Postgres;
using DbBroker.Cli.Services.Providers.SqlServer;
using DbBroker.Common;
using DbBroker.Common.Model;
+using Npgsql;
using Oracle.ManagedDataAccess.Client;
namespace DbBroker.Cli.Extensions;
@@ -16,6 +17,7 @@ public static class DbBrokerConfigContextExtensions
{
{ SupportedDatabaseProviders.SqlServer, new SqlServerDefaultConfiguration() },
{ SupportedDatabaseProviders.Oracle, new OracleDefaultConfiguration() },
+ { SupportedDatabaseProviders.Postgres, new PostgresDefaultConfiguration() },
};
public static DbConnection GetDbConnection(this DbBrokerConfigContext context)
@@ -24,23 +26,26 @@ public static DbConnection GetDbConnection(this DbBrokerConfigContext context)
{
SupportedDatabaseProviders.SqlServer => new SqlConnection(context.ConnectionString),
SupportedDatabaseProviders.Oracle => new OracleConnection(context.ConnectionString),
+ SupportedDatabaseProviders.Postgres => new NpgsqlConnection(context.ConnectionString),
_ => throw new ArgumentException($"Provider not supported: {context.Provider}"),
};
}
- public static IMetadataProvider GetMetadataProvider(this DbBrokerConfigContext context)
+ // TODO Do an extensive revision on access modifiers: CLI and lib
+ internal static IMetadataProvider GetMetadataProvider(this DbBrokerConfigContext context)
{
return context.Provider switch
{
SupportedDatabaseProviders.SqlServer => new SqlServerMetadataProvider(),
SupportedDatabaseProviders.Oracle => new OracleMetadaProvider(),
+ SupportedDatabaseProviders.Postgres => new PostgresMetadataProvider(),
_ => throw new ArgumentException($"Provider not supported: {context.Provider}."),
};
}
public static IProviderDefaultConfiguration GetDefaultProviderConfig(this DbBrokerConfigContext context)
{
- return _defaultProviderConfigs[context.Provider];
+ return _defaultProviderConfigs[context.Provider!.Value];
}
public static ISqlTransformer GetSqlTransformer(this DbBrokerConfigContext context)
@@ -49,6 +54,7 @@ public static ISqlTransformer GetSqlTransformer(this DbBrokerConfigContext conte
{
SupportedDatabaseProviders.SqlServer => new SqlServerSqlTransformer(),
SupportedDatabaseProviders.Oracle => new OracleSqlTransformer(),
+ SupportedDatabaseProviders.Postgres => new PostgresSqlTransformer(),
_ => throw new ArgumentException($"Provider not supported: {context.Provider}"),
};
}
diff --git a/src/DbBroker.Cli/Extensions/ResolversExtensions.cs b/src/DbBroker.Cli/Extensions/ResolversExtensions.cs
new file mode 100644
index 0000000..68f4b5f
--- /dev/null
+++ b/src/DbBroker.Cli/Extensions/ResolversExtensions.cs
@@ -0,0 +1,150 @@
+using DbBroker.Cli.Model;
+using DbBroker.Common;
+
+namespace DbBroker.Cli.Extensions;
+
+public static class ResolversExtensions
+{
+ public static string GetDbTypeString(this SupportedDatabaseProviders? provider, ColumnDescriptorModel columnDescriptorModel)
+ {
+ switch (provider)
+ {
+ case SupportedDatabaseProviders.Oracle:
+ return GetOracleDbTypeString(columnDescriptorModel);
+ case SupportedDatabaseProviders.SqlServer:
+ return GetSqlServerDbTypeString(columnDescriptorModel);
+ case SupportedDatabaseProviders.Postgres:
+ return GetPostgresDbTypeString(columnDescriptorModel);
+ default:
+ throw new NotImplementedException($"Provider DbType resolution not implemented for {provider}");
+ }
+ }
+
+ ///
+ /// https://learn.microsoft.com/en-us/dotnet/api/system.data.sqldbtype?view=net-9.0
+ ///
+ ///
+ ///
+ ///
+ private static string GetSqlServerDbTypeString(ColumnDescriptorModel columnDescriptorModel)
+ {
+ switch (columnDescriptorModel.DataType.ToLower())
+ {
+ case "varchar":
+ return "SqlDbType.VarChar";
+ case "datetime":
+ return "SqlDbType.DateTime";
+ case "datetime2":
+ return "SqlDbType.DateTime2";
+ case "date":
+ return "SqlDbType.Date";
+ case "money":
+ return "SqlDbType.Money";
+ case "uniqueidentifier":
+ return "SqlDbType.UniqueIdentifier";
+ case "int":
+ return "SqlDbType.Int";
+ default:
+ throw new NotImplementedException();
+ }
+ }
+
+ private static string GetOracleDbTypeString(ColumnDescriptorModel columnDescriptorModel)
+ {
+ switch (columnDescriptorModel.DataType.ToLower())
+ {
+ case "varchar":
+ case "varchar2":
+ case "nvarchar2":
+ case "char":
+ case "nchar":
+ case "rowid":
+ case "long":
+ return "OracleDbType.Varchar2";
+ case "nclob":
+ return "OracleDbType.NClob";
+ case "clob":
+ return "OracleDbType.Clob";
+ case "date":
+ case "datetime":
+ case "timestamp(6)":
+ return "OracleDbType.Date";
+ case "raw":
+ return "OracleDbType.Raw";
+ case "bfile":
+ case "blob":
+ return "OracleDbType.Blob";
+ case "float":
+ case "decimal":
+ case "money":
+ case "number":
+ return "OracleDbType.Decimal";
+ case "integer":
+ return "OracleDbType.Int32";
+ default:
+ return "OracleDbType.Object";
+ }
+ }
+
+ private static string GetPostgresDbTypeString(ColumnDescriptorModel columnDescriptorModel)
+ {
+ switch (columnDescriptorModel.DataType.ToLower())
+ {
+ case "character varying":
+ case "varchar":
+ case "text":
+ case "char":
+ case "character":
+ case "name":
+ return "NpgsqlDbType.Varchar";
+ case "date":
+ return "NpgsqlDbType.Date";
+ case "timestamp":
+ case "timestamp without time zone":
+ case "timestamp with time zone":
+ return "NpgsqlDbType.Timestamp";
+ case "bytea":
+ return "NpgsqlDbType.Bytea";
+ case "numeric":
+ case "decimal":
+ case "money":
+ return "NpgsqlDbType.Numeric";
+ case "integer":
+ case "int":
+ case "int4":
+ return "NpgsqlDbType.Integer";
+ case "bigint":
+ case "int8":
+ return "NpgsqlDbType.Bigint";
+ case "smallint":
+ case "int2":
+ return "NpgsqlDbType.Smallint";
+ case "real":
+ case "float4":
+ return "NpgsqlDbType.Real";
+ case "double precision":
+ case "float8":
+ return "NpgsqlDbType.Double";
+ case "boolean":
+ case "bool":
+ return "NpgsqlDbType.Boolean";
+ case "uuid":
+ return "NpgsqlDbType.Uuid";
+ default:
+ return "NpgsqlDbType.Unknown";
+ }
+ }
+
+ public static string GetProviderClientUsingString(this SupportedDatabaseProviders? provider)
+ {
+ return provider switch
+ {
+ SupportedDatabaseProviders.Oracle => "using Oracle.ManagedDataAccess.Client;",
+ SupportedDatabaseProviders.SqlServer => "using System.Data;",
+ SupportedDatabaseProviders.Postgres =>
+@"using Npgsql;
+using NpgsqlTypes;",
+ _ => throw new NotImplementedException($"Client namespace not implemented for {provider}"),
+ };
+ }
+}
diff --git a/src/DbBroker.Cli/Extensions/StringExtensions.cs b/src/DbBroker.Cli/Extensions/StringExtensions.cs
index 06fe1d8..7536cda 100644
--- a/src/DbBroker.Cli/Extensions/StringExtensions.cs
+++ b/src/DbBroker.Cli/Extensions/StringExtensions.cs
@@ -3,7 +3,7 @@ namespace DbBroker.Cli.Extensions;
public static class StringExtensions
{
///
- /// Transforms an object name like 'MY_TABLE' into MyTable
+ /// Transforms an object name like 'LAST_NAME' or 'last_name' into LastName
///
///
public static string ToCamelCase(this string value)
@@ -14,8 +14,6 @@ public static string ToCamelCase(this string value)
return string.Join(string.Empty, words.Select(w => $"{w.ToUpperInvariant()[0]}{w.ToLowerInvariant()[1..]}"));
}
- return value?.All(char.IsUpper) ?? false
- ? $"{value?.ToUpperInvariant()[0]}{value?.ToLowerInvariant()[1..]}"
- : value ?? string.Empty;
+ return $"{(value ?? string.Empty).ToUpperInvariant()[0]}{(value ?? string.Empty).ToLowerInvariant()[1..]}";
}
}
diff --git a/src/DbBroker.Cli/Model/ColumnDescriptorModel.cs b/src/DbBroker.Cli/Model/ColumnDescriptorModel.cs
index 4c670aa..729437d 100644
--- a/src/DbBroker.Cli/Model/ColumnDescriptorModel.cs
+++ b/src/DbBroker.Cli/Model/ColumnDescriptorModel.cs
@@ -4,5 +4,5 @@ namespace DbBroker.Cli.Model;
public class ColumnDescriptorModel : DbBrokerConfigContextColumn
{
-
+ public int Index { get; set; }
}
diff --git a/src/DbBroker.Cli/Model/ViewDescriptorModel.cs b/src/DbBroker.Cli/Model/ViewDescriptorModel.cs
new file mode 100644
index 0000000..737f52a
--- /dev/null
+++ b/src/DbBroker.Cli/Model/ViewDescriptorModel.cs
@@ -0,0 +1,12 @@
+namespace DbBroker.Cli.Model;
+
+public class ViewDescriptorModel
+{
+ public required string SchemaName { get; set; }
+
+ public required string ViewName { get; set; }
+
+ public string ViewFullName => $"{SchemaName}.{ViewName}";
+
+ public IEnumerable Columns { get; set; } = [];
+}
diff --git a/src/DbBroker.Cli/Model/ViewSplittedEntityModel.cs b/src/DbBroker.Cli/Model/ViewSplittedEntityModel.cs
new file mode 100644
index 0000000..d06c4a8
--- /dev/null
+++ b/src/DbBroker.Cli/Model/ViewSplittedEntityModel.cs
@@ -0,0 +1,16 @@
+using DbBroker.Common.Model;
+
+namespace DbBroker.Cli.Model;
+
+internal class ViewSplittedEntityModel
+{
+ public DbBrokerConfigContextViewSplitOnItem SplitOnItem { get; set; }
+
+ public List Columns { get; set; }
+
+ public ViewSplittedEntityModel(DbBrokerConfigContextViewSplitOnItem splitOnItem)
+ {
+ SplitOnItem = splitOnItem;
+ Columns = [];
+ }
+}
diff --git a/src/DbBroker.Cli/Program.cs b/src/DbBroker.Cli/Program.cs
index a7a03bf..ef4a661 100644
--- a/src/DbBroker.Cli/Program.cs
+++ b/src/DbBroker.Cli/Program.cs
@@ -6,18 +6,31 @@
namespace DbBroker.Cli;
///
-/// DBBroker CLI is a .NET tool to create and synchronize Data Models
+/// DBBroker CLI is a .NET tool to create and synchronize Data Models for using DBBroker NuGet library
///
class Program
{
- static void Main(string[] args)
+ static void Main(string[] args)
{
- $"Running DBBroker v1.0.1-alpha...".Log();
+ $"Running DBBroker...".Log();
+ var parser = new Parser(settings =>
+ {
+ settings.AllowMultiInstance = true;
+ });
- Parser.Default.ParseArguments(args)
- .MapResult(
- (InitOptions opts) => InitOptions.Execute(opts),
- (SyncOptions opts) => SyncCommand.Execute(opts),
- errs => 1);
+ parser
+ .ParseArguments(args)
+ .MapResult(
+ (InitOptions opts) => InitCommand.Execute(opts),
+ (SyncOptions opts) => SyncCommand.Execute(opts),
+ errs =>
+ {
+ foreach (var err in errs)
+ {
+ err.ToString()?.Error();
+ }
+
+ return 1;
+ });
}
}
diff --git a/src/DbBroker.Cli/Properties/launchSettings.json b/src/DbBroker.Cli/Properties/launchSettings.json
new file mode 100644
index 0000000..a0a1e7c
--- /dev/null
+++ b/src/DbBroker.Cli/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "DbBroker.Cli.Sync": {
+ "commandName": "Project",
+ "commandLineArgs": "sync --file C:\\Users\\USER\\git\\finbook\\src\\back-end\\Finbook.Infrastructure.Persistence\\dbbroker.config.json"
+ },
+ "DbBroker.Cli.Init": {
+ "commandName": "Project",
+ "commandLineArgs": "init --namespace Finbook.Persistence.DataModels --connectionString Host=localhost;Port=5432;Username=admin;Password=admin;Database=finbook;Pooling=true;"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DbBroker.Cli/README.md b/src/DbBroker.Cli/README.md
index aa4877f..4a562cc 100644
--- a/src/DbBroker.Cli/README.md
+++ b/src/DbBroker.Cli/README.md
@@ -1,19 +1,22 @@
# DBBroker CLI
-Generate your Data Models easily and work seamelessly with [DBBroker](https://www.nuget.org/packages/DBBroker) Micro ORM Library.
+Generate your Data Models easily and work seamlessly with [DBBroker](https://www.nuget.org/packages/DBBroker) Micro ORM Library.
```bash
dotnet tool install --global DbBroker.Cli
-# Or locally
-dotnet tool install --global --add-source ./bin/Debug/ dbbroker.cli
+
+# Or if you are building from source
+dotnet tool install --global --prerelease --add-source ./bin/Debug/ dbbroker.cli
```
+On your terminal navigate to the `.csproj` file of the DataModels project directory then follow the instructions below.
+
## `dbbroker init`
Initializes the `dbbroker.config.json` file using the values specified.
```bash
-dbbroker init
+dbbroker init --namespace "MyApp.DataModels" --connectionsString "" --provider "SqlServer"
```
## `dbbroker sync`
@@ -23,3 +26,11 @@ Synchronizes the Data Models with the databases specified in one or more `dbbrok
```bash
dbbroker sync
```
+
+## Contribute
+
+We appreciate all contributions, whether they're bug reports, feature suggestions, or pull requests. Thank you for your interest and support in improving this project!
+
+Financial support is also welcome, whether large or small contributions will help to keep this project moving and always secure.
+
+[](https://www.buymeacoffee.com/diegosiao)
diff --git a/src/DbBroker.Cli/Services/CSharpClassGenerator.cs b/src/DbBroker.Cli/Services/CSharpClassGenerator.cs
index 46154f0..3756c60 100644
--- a/src/DbBroker.Cli/Services/CSharpClassGenerator.cs
+++ b/src/DbBroker.Cli/Services/CSharpClassGenerator.cs
@@ -1,6 +1,9 @@
+using System.Diagnostics;
using System.Text;
using DbBroker.Cli.Commands.Sync;
+using DbBroker.Cli.Exceptions;
using DbBroker.Cli.Extensions;
+using DbBroker.Cli.Model;
using DbBroker.Cli.Services.Interfaces;
using DbBroker.Common.Model;
@@ -10,129 +13,391 @@ public class CSharpClassGenerator : ICSharpClassGenerator
{
public async Task GenerateAsync(DbBrokerConfigContext context)
{
+ if (!string.IsNullOrEmpty(SyncCommand.Options.Context) && !SyncCommand.Options.Context.Equals(context.Name, StringComparison.InvariantCultureIgnoreCase))
+ {
+ "Context ignored.".Warning(context.Namespace);
+ return 0;
+ }
+
"Retrieving database metadata...".Log(context.Namespace);
try
{
+ if (context.Provider is null)
+ {
+ throw new DbBrokerConfigurationException("The context does not have a Provider specified. ");
+ }
+
+ if (context.ConnectionString is null)
+ {
+ throw new DbBrokerConfigurationException("The context does not have a Connection String specified. ");
+ }
+
using var connection = context.GetDbConnection();
+ connection.Open();
- var sqlTransformer = context.GetSqlTransformer();
+ var metadataProvider = context.GetMetadataProvider();
- var tableDescriptors = await context
- .GetMetadataProvider()
+ var tablesDescriptors = await metadataProvider
.GetTableDescriptorsAsync(connection, context);
+ var viewsDescriptors = await metadataProvider
+ .GetViewsDescriptorsAsync(connection, context);
+
+ var outputDirectory = (context.Namespace?.Split(".")?.Length > 1 ? string.Join(Path.DirectorySeparatorChar, context.Namespace.Split(".").Skip(1)) : context.Namespace) ?? string.Empty;
+ outputDirectory = Path.Combine(Directory.GetCurrentDirectory(), outputDirectory!);
+
+ var resolvedOutputDirectory = context.OutputDirectory ?? outputDirectory;
+
+ if (context.ClearOutputDirectory)
+ {
+ try
+ {
+ Directory.Delete(resolvedOutputDirectory, true);
+ }
+ catch (Exception ex)
+ {
+ ex.Message.Warning(context.Namespace);
+ }
+ }
+
+ Directory.CreateDirectory(resolvedOutputDirectory);
+
+ $"Output directory: {resolvedOutputDirectory}".Log(context.Namespace);
+
+ var sqlTransformer = context.GetSqlTransformer();
var providerDefaultConfig = context.GetDefaultProviderConfig();
- var allKeys = tableDescriptors
- .Select(tableDescriptor => tableDescriptor.Value.Keys)
- .SelectMany(x => x);
+ await Task.WhenAll(
+ GenerateClassesForTables(tablesDescriptors, context, sqlTransformer, providerDefaultConfig, resolvedOutputDirectory),
+ GenerateClassesForViews(viewsDescriptors, context, sqlTransformer, resolvedOutputDirectory));
+ }
+ catch (DbBrokerConfigurationException configEx)
+ {
+ configEx.Message.Error(context.Namespace);
+
+ if (Debugger.IsAttached)
+ {
+ configEx.StackTrace?.Error(context.Namespace);
+ }
+
+ SyncCommand.Results.Add(context.Namespace, ExitCodes.CONFIG_FILE_INIT_ERROR);
+ return ExitCodes.CONFIG_FILE_INIT_ERROR;
+ }
+ catch (Exception ex)
+ {
+ ex.Message.Error(context.Namespace);
- foreach (var tableDescriptor in tableDescriptors)
+ if (Debugger.IsAttached)
{
- var outputDirectory = (context.Namespace?.Split(".")?.Length > 1 ? string.Join('/', context.Namespace.Split(".").Skip(1)) : context.Namespace) ?? string.Empty;
- outputDirectory = Path.Combine(Directory.GetCurrentDirectory(), outputDirectory!);
+ ex.StackTrace?.Error(context.Namespace);
+ }
+
+ SyncCommand.Results.Add(context.Namespace, ExitCodes.CONTEXT_GENERIC_ERROR);
+ return 1;
+ }
- Directory.CreateDirectory(context.OutputDirectory ?? outputDirectory);
+ return 0;
+ }
- StringBuilder propsString = new();
- StringBuilder refsString = new();
- StringBuilder collectionsString = new();
- // Find out all references to this table
- var referencesTo = allKeys.Where(x => x.ReferencedTable.Equals(tableDescriptor.Value.TableName));
- if (referencesTo.Any())
+ static async Task GenerateClassesForTables(
+ Dictionary tablesDescriptors,
+ DbBrokerConfigContext context,
+ ISqlTransformer sqlTransformer,
+ IProviderDefaultConfiguration providerDefaultConfig,
+ string outputDirectory)
+ {
+ $"{tablesDescriptors.Count} tables found.".Log(context.Namespace);
+
+ if (tablesDescriptors.Count == 0)
+ {
+ "Make sure the user provided has SELECT permission on metadata tables.".Warning(context.Namespace, 0);
+ }
+
+ var allKeys = tablesDescriptors
+ .Select(tableDescriptor => tableDescriptor.Value.Keys)
+ .SelectMany(x => x);
+
+ StringBuilder propsString = new();
+ StringBuilder refsString = new();
+ StringBuilder collectionsString = new();
+ foreach (var tableDescriptor in tablesDescriptors)
+ {
+ propsString.Clear();
+ refsString.Clear();
+ collectionsString.Clear();
+
+ // Find out all references to this table
+ var referencesTo = allKeys.Where(x => x.ReferencedTable.Equals(tableDescriptor.Value.TableName));
+ if (referencesTo.Any())
+ {
+ foreach (var reference in referencesTo)
{
- foreach (var reference in referencesTo)
+ if (!tablesDescriptors.TryGetValue(reference.TableFullName, out TableDescriptorModel? tableDescriptorModel))
{
- var primaryKeyColumnName = tableDescriptors[reference.TableFullName]
- .Keys?
- .FirstOrDefault(x => x.ConstraintType.Equals("PrimaryKey"))?
- .ColumnName;
-
- if (primaryKeyColumnName is not null)
- {
- collectionsString.AppendLine(
- Constants.EDM_COLLECTION_REFERENCE_TEMPLATE
- .Replace("$$PROPERTYNAME$$", $"{reference.TableName.ToCamelCase()}{reference.ColumnName.ToCamelCase()}")
- .Replace("$$SCHEMANAME$$", reference.SchemaName)
- .Replace("$$TABLENAME$$", reference.ReferencedTable)
- .Replace("$$COLUMNNAME$$", reference.ReferencedColumn)
- .Replace("$$REFCOLUMNNAME$$", reference.ColumnName)
- .Replace("$$REFSCHEMANAME$$", reference.SchemaName)
- .Replace("$$REFTABLENAME$$", reference.TableName)
- .Replace("$$PKCOLUMNNAME$$", primaryKeyColumnName)
- .Replace("$$REFTYPENAME$$", $"{context.ModelsPrefix}{reference.TableName.ToCamelCase()}{context.ModelsSufix}")
- );
- }
+ throw new InvalidOperationException($"The '{reference.TableName}' table is not listed in this context. If you are filtering the tables make sure to list all referenced tables in all levels.");
+ }
+
+ var primaryKeyColumnName = tableDescriptorModel
+ .Keys?
+ .FirstOrDefault(x => x.ConstraintType.Equals("PrimaryKey"))?
+ .ColumnName;
+
+ if (primaryKeyColumnName is not null)
+ {
+ collectionsString.AppendLine(
+ Constants.EDM_COLLECTION_REFERENCE_TEMPLATE
+ .Replace("$$PROPERTYNAME$$", $"{reference.TableName.ToCamelCase()}{reference.ColumnName.ToCamelCase()}")
+ .Replace("$$SCHEMANAME$$", reference.SchemaName)
+ .Replace("$$TABLENAME$$", reference.ReferencedTable)
+ .Replace("$$COLUMNNAME$$", reference.ReferencedColumn)
+ .Replace("$$REFCOLUMNNAME$$", reference.ColumnName)
+ .Replace("$$REFSCHEMANAME$$", reference.SchemaName)
+ .Replace("$$REFTABLENAME$$", reference.TableName)
+ .Replace("$$PKCOLUMNNAME$$", primaryKeyColumnName)
+ .Replace("$$REFTYPENAME$$", $"{context.ModelsPrefix}{reference.TableName.ToCamelCase()}{context.ModelsSufix}")
+ );
}
}
+ }
- foreach (var item in tableDescriptor.Value.Columns)
+ foreach (var item in tableDescriptor.Value.Columns)
+ {
+ if (item is null)
{
- var isPrimaryKey = tableDescriptor
- .Value
- .Keys
- .Any(x => x.KeyFullName.Equals(item.ColumnFullName) && x.ConstraintType.Equals("PrimaryKey"));
+ continue;
+ }
+ var isPrimaryKey = tableDescriptor
+ .Value
+ .Keys
+ .Any(x => x.KeyFullName.Equals(item.ColumnFullName) && x.ConstraintType.Equals("PrimaryKey"));
+
+ propsString.AppendLine(
+ Constants.EDM_PROPERTY_TEMPLATE
+ .Replace("$TYPE", sqlTransformer.GetCSharpType(item.DataType, item.MaxLength ?? "250", item.DataTypePrecision, item.DataTypeScale, item.IsNullable))
+ .Replace("$KEY", isPrimaryKey ? "Key, " : string.Empty)
+ .Replace("$COLUMN_NAME", item?.ColumnName)
+ .Replace("$$PROVIDER_DBTYPE$$", context.Provider!.GetDbTypeString(item!))
+ .Replace("$NAME", item?.ColumnName.ToCamelCase()));
+
+ var foreignKey = tableDescriptor
+ .Value
+ .Keys
+ .FirstOrDefault(x => x.ColumnName.Equals(item?.ColumnName) && x.ConstraintType.Equals("ForeignKey"));
+
+ if (foreignKey is not null)
+ {
+ if (!tablesDescriptors.TryGetValue($"{foreignKey.SchemaName}.{foreignKey.ReferencedTable}", out TableDescriptorModel? foreignKeyTableDescriptorModel))
+ {
+ throw new InvalidOperationException($"The '{foreignKey.ReferencedTable}' table is not listed in this context. If you are filtering the tables make sure to list all referenced tables in all levels.");
+ }
+
+ refsString.AppendLine(
+ Constants.EDM_REFERENCE_TEMPLATE
+ .Replace("$$PROPERTYNAME$$", item?.ColumnName.ToCamelCase())
+ .Replace("$$SCHEMANAME$$", item?.SchemaName)
+ .Replace("$$TABLENAME$$", item?.TableName)
+ .Replace("$$COLUMNNAME$$", item?.ColumnName)
+ .Replace("$$COLUMNALLOWNULLS$$", item?.IsNullable.ToString().ToLowerInvariant())
+ .Replace("$$REFCOLUMNNAME$$", foreignKey.ReferencedColumn)
+ .Replace("$$REFSCHEMANAME$$", foreignKey.SchemaName)
+ .Replace("$$REFTABLENAME$$", foreignKey.ReferencedTable)
+ .Replace("$$PKCOLUMNNAME$$", foreignKey.ReferencedColumn)
+ .Replace("$$REFTYPENAME$$", $"{context.ModelsPrefix}{foreignKeyTableDescriptorModel.TableName.ToCamelCase()}{context.ModelsSufix}")
+ );
+ }
+ }
+
+ var configContextTable = context.Tables.FirstOrDefault(x => x.Name.Equals(tableDescriptor.Value.TableName));
+
+ await File.WriteAllTextAsync(
+ Path.Combine(outputDirectory, $"{context.ModelsPrefix}{tableDescriptor.Value.TableName.ToCamelCase()}{context.ModelsSufix}.cs"),
+ Constants.EDM_CLASS_TEMPLATE
+ .Replace("$$PROVIDER_CLIENT_NAMESPACE$$", context.Provider!.GetProviderClientUsingString())
+ .Replace("$NAMESPACE", context.Namespace ?? "-")
+ .Replace("$CLASSNAME", $"{context.ModelsPrefix}{tableDescriptor.Value.TableName.ToCamelCase()}{context.ModelsSufix}")
+ .Replace("$TABLE", tableDescriptor.Value.TableName)
+ .Replace("$SCHEMA", tableDescriptor.Value.SchemaName)
+ .Replace("$PROPERTIES", propsString.ToString())
+ .Replace("$REFERENCES", refsString.ToString())
+ .Replace("$COLLECTIONS", collectionsString.ToString())
+ .Replace("$PROVIDER", context.Provider.ToString())
+ .Replace("$ISQLINSERTTEMPLATETYPEFULLNAME",
+ configContextTable?.SqlInsertTemplateTypeFullName
+ ?? context.DefaultSqlInsertTemplateTypeFullName
+ ?? providerDefaultConfig.ISqlInsertTemplateTypeFullName)
+ .Replace("$ISQLINSERTTEMPLATETYPEARGUMENTS",
+ string.Join(",", configContextTable?.SqlInsertTemplateArguments?.Values.Select(x => $"\"{x}\"") ?? [])
+ ));
+ }
+ }
+
+ ///
+ /// By default database View Data Models are created as a representation of a database view tuple.
+ /// If the 'SplitsOn' option is configured, then:
+ /// (1) Classes will be created inside of the View Data Model to represent the entities present on the database View,
+ /// (2) The View Data Model will have properties to load the data for these entities: instances or collections,
+ /// (3) A 'tuple' class will be created inside of the View Data Model to help with writing operations.
+ ///
+ ///
+ static async Task GenerateClassesForViews(
+ Dictionary viewsDescriptors,
+ DbBrokerConfigContext context,
+ ISqlTransformer sqlTransformer,
+ string outputDirectory)
+ {
+ $"{viewsDescriptors.Count} views found.".Log(context.Namespace);
+
+ if (viewsDescriptors.Count > 0)
+ {
+ Directory.CreateDirectory(Path.Combine(context.OutputDirectory ?? outputDirectory, "Views"));
+ }
+
+ StringBuilder propsString = new();
+ StringBuilder refsString = new();
+ StringBuilder collectionsString = new();
+ StringBuilder classesString = new();
+ StringBuilder tupleString = new();
+ StringBuilder tuplePropertiesString = new();
+ foreach (var viewDescriptor in viewsDescriptors)
+ {
+ propsString.Clear();
+ refsString.Clear();
+ collectionsString.Clear();
+ classesString.Clear();
+ tupleString.Clear();
+ tuplePropertiesString.Clear();
+
+ var viewConfig = context
+ .Views
+ .FirstOrDefault(x => x.Name == viewDescriptor.Value.ViewFullName || x.Name == viewDescriptor.Value.ViewName);
+
+ // Check if the splitOn column is specified
+ var splitsOn = viewConfig?.SplitsOn;
+
+ if (splitsOn is null)
+ {
+ var index = 0;
+ foreach (var item in viewDescriptor.Value.Columns)
+ {
propsString.AppendLine(
Constants.EDM_PROPERTY_TEMPLATE
- .Replace("$TYPE", sqlTransformer.GetCSharpType(item.DataType, item?.MaxLength ?? "50", item?.IsNullable ?? false))
- .Replace("$KEY", isPrimaryKey ? "Key, " : string.Empty)
- .Replace("$COLUMN_NAME", item?.ColumnName)
+ .Replace("$KEY", index == 0 ? "Key, " : string.Empty)
+ .Replace("$COLUMN_NAME", item.ColumnName)
+ .Replace("$$PROVIDER_DBTYPE$$", context.Provider.GetDbTypeString(item!))
+ .Replace("$TYPE", sqlTransformer.GetCSharpType(item.DataType, item?.MaxLength ?? "250", item?.DataTypePrecision, item?.DataTypeScale, item?.IsNullable ?? false))
.Replace("$NAME", item?.ColumnName.ToCamelCase()));
+ index++;
+ }
+ }
- var foreignKey = tableDescriptor
- .Value
- .Keys
- .FirstOrDefault(x => x.ColumnName.Equals(item?.ColumnName) && x.ConstraintType.Equals("ForeignKey"));
+ if (splitsOn is not null)
+ {
+ // first entity
+ DbBrokerConfigContextViewSplitOnItem currentSplitOn = new()
+ {
+ Column = viewDescriptor.Value.Columns.First().ColumnName,
+ Type = viewConfig?.TypeName ?? $"{context.ModelsPrefix}{viewDescriptor.Value.ViewName.ToCamelCase()}{context.ModelsSufix}",
+ Collection = false
+ };
+
+ var entities = new Dictionary
+ {
+ { currentSplitOn.Type, new ViewSplittedEntityModel(currentSplitOn) }
+ };
- if (foreignKey is not null)
+ var index = 0;
+ foreach (var item in viewDescriptor.Value.Columns)
+ {
+ // check if next the entity
+ var nextEntity = splitsOn.FirstOrDefault(x => x.Column == item.ColumnName);
+ if (nextEntity is not null)
{
- refsString.AppendLine(
- Constants.EDM_REFERENCE_TEMPLATE
- .Replace("$$PROPERTYNAME$$", item?.ColumnName.ToCamelCase())
- .Replace("$$SCHEMANAME$$", item?.SchemaName)
- .Replace("$$TABLENAME$$", item?.TableName)
- .Replace("$$COLUMNNAME$$", item?.ColumnName)
- .Replace("$$COLUMNALLOWNULLS$$", item?.IsNullable.ToString().ToLowerInvariant())
- .Replace("$$REFCOLUMNNAME$$", foreignKey.ReferencedColumn)
- .Replace("$$REFSCHEMANAME$$", foreignKey.SchemaName)
- .Replace("$$REFTABLENAME$$", foreignKey.ReferencedTable)
- .Replace("$$PKCOLUMNNAME$$", foreignKey.ReferencedColumn)
- .Replace("$$REFTYPENAME$$", $"{context.ModelsPrefix}{tableDescriptors[$"{foreignKey.SchemaName}.{foreignKey.ReferencedTable}"].TableName.ToCamelCase()}{context.ModelsSufix}")
- );
+ currentSplitOn = nextEntity;
+ entities[currentSplitOn.Type] = new ViewSplittedEntityModel(currentSplitOn);
+ index = 0;
}
+
+ item.Index = index;
+ entities[currentSplitOn.Type].Columns.Add(item);
+ index++;
+ }
+
+ // properties
+ foreach (var item in entities.Values.First().Columns)
+ {
+ propsString.AppendLine(
+ Constants.EDM_VIEW_PROPERTY_TEMPLATE
+ .Replace("$KEY", item.Index == 0 ? "Key, " : string.Empty)
+ .Replace("$COLUMN_NAME", item.ColumnName)
+ .Replace("$$PROVIDER_DBTYPE$$", context.Provider.GetDbTypeString(item!))
+ .Replace("$TYPE", sqlTransformer.GetCSharpType(item.DataType, item?.MaxLength ?? "250", item?.DataTypePrecision, item?.DataTypeScale, item?.IsNullable ?? false))
+ .Replace("$NAME", item?.ColumnName.ToCamelCase()));
+
+ tuplePropertiesString.AppendLine(
+ Constants.EDM_PROPERTY_INDENTED_TEMPLATE
+ .Replace("$KEY", item!.Index == 0 ? "Key, " : string.Empty)
+ .Replace("$COLUMN_NAME", item.ColumnName)
+ .Replace("$$PROVIDER_DBTYPE$$", context.Provider.GetDbTypeString(item!))
+ .Replace("$TYPE", sqlTransformer.GetCSharpType(item.DataType, item?.MaxLength ?? "250", item!.DataTypePrecision, item!.DataTypeScale, item!.IsNullable))
+ .Replace("$NAME", item!.ColumnName.ToCamelCase()));
}
- var configContextTable = context.Tables.FirstOrDefault(x => x.Name.Equals(tableDescriptor.Value.TableName));
-
- File.WriteAllText(
- Path.Combine(outputDirectory, $"{context.ModelsPrefix}{tableDescriptor.Value.TableName.ToCamelCase()}{context.ModelsSufix}.cs"),
- Constants.EDM_CLASS_TEMPLATE
- .Replace("$NAMESPACE", context.Namespace ?? "-")
- .Replace("$CLASSNAME", $"{context.ModelsPrefix}{tableDescriptor.Value.TableName.ToCamelCase()}{context.ModelsSufix}")
- .Replace("$TABLE", tableDescriptor.Value.TableName)
- .Replace("$SCHEMA", tableDescriptor.Value.SchemaName)
- .Replace("$PROPERTIES", propsString.ToString())
- .Replace("$REFERENCES", refsString.ToString())
- .Replace("$COLLECTIONS", collectionsString.ToString())
- .Replace("$PROVIDER", context.Provider.ToString())
- .Replace("$ISQLINSERTTEMPLATETYPEFULLNAME",
- configContextTable?.SqlInsertTemplateTypeFullName
- ?? context.DefaultSqlInsertTemplateTypeFullName
- ?? providerDefaultConfig.ISqlInsertTemplateTypeFullName)
- .Replace("$ISQLINSERTTEMPLATETYPEARGUMENTS",
- string.Join(",", configContextTable?.SqlInsertTemplateArguments?.Values.Select(x => $"\"{x}\"") ?? [])
- ));
+ // references and collections
+ foreach (var entity in entities.Values.Skip(1))
+ {
+ refsString.AppendLine(
+ (entity.SplitOnItem.Collection ? Constants.EDM_COLLECTION_REFERENCE_VIEW_TEMPLATE : Constants.EDM_REFERENCE_VIEW_TEMPLATE)
+ .Replace("$$REFTYPENAME$$", entity.SplitOnItem.Type)
+ .Replace("$$REFPROPERTYNAME$$", entity.SplitOnItem.Column.ToCamelCase()));
+
+ // classes properties/class
+ var classProperties = string.Join(
+ Environment.NewLine,
+ entity.Columns.Select(c => Constants.EDM_VIEW_PROPERTY_INDENTED_TEMPLATE
+ .Replace("$KEY", c.Index == 0 ? "Key, " : string.Empty)
+ .Replace("$COLUMN_NAME", c.ColumnName)
+ .Replace("$TYPE", sqlTransformer.GetCSharpType(c.DataType, c?.MaxLength ?? "250", c?.DataTypePrecision, c?.DataTypeScale, c?.IsNullable ?? false))
+ .Replace("$$PROVIDER_DBTYPE$$", context.Provider.GetDbTypeString(c!))
+ .Replace("$NAME", c!.ColumnName.ToCamelCase())));
+
+ classesString.AppendLine(
+ Constants.EDM_VIEW_INTERNAL_CLASS_TEMPLATE
+ .Replace("$CLASSNAME", entity.SplitOnItem.Type)
+ .Replace("$PROPERTIES", classProperties));
+
+ tuplePropertiesString.AppendLine(string.Join(
+ Environment.NewLine,
+ entity.Columns.Select(c => Constants.EDM_PROPERTY_INDENTED_TEMPLATE
+ .Replace("$KEY", c.Index == 0 ? "Key, " : string.Empty)
+ .Replace("$COLUMN_NAME", c.ColumnName)
+ .Replace("$$PROVIDER_DBTYPE$$", context.Provider.GetDbTypeString(c!))
+ .Replace("$TYPE", sqlTransformer.GetCSharpType(c.DataType, c?.MaxLength ?? "250", c?.DataTypePrecision, c?.DataTypeScale, c?.IsNullable ?? false))
+ .Replace("$NAME", c!.ColumnName.ToCamelCase()))));
+ }
+
+ // tuple properties/class
+ tupleString.AppendLine(
+ Constants.EDM_VIEW_TUPLE_CLASS_TEMPLATE
+ .Replace("$CLASSNAME", entities.First().Value.SplitOnItem.Type)
+ .Replace("$PROPERTIES", tuplePropertiesString.ToString()));
}
- }
- catch (Exception ex)
- {
- $"{context.Namespace} | {ex.Message}".Error();
- SyncCommand.Results.Add(context.Namespace, 1);
- return 1;
- }
- return 0;
+ await File.WriteAllTextAsync(
+ Path.Combine(outputDirectory, "Views", $"{context.ModelsPrefix}{viewDescriptor.Value.ViewName.ToCamelCase()}{context.ModelsSufix}.cs"),
+ Constants.EDM_VIEW_CLASS_TEMPLATE
+ .Replace("$$PROVIDER_CLIENT_NAMESPACE$$", context.Provider.GetProviderClientUsingString())
+ .Replace("$NAMESPACE", context.Namespace ?? "-")
+ .Replace("$CLASSNAME", $"{context.ModelsPrefix}{viewDescriptor.Value.ViewName.ToCamelCase()}{context.ModelsSufix}")
+ .Replace("$TABLE", viewDescriptor.Value.ViewName)
+ .Replace("$SCHEMA", viewDescriptor.Value.SchemaName)
+ .Replace("$PROPERTIES", propsString.ToString())
+ .Replace("$REFERENCES", refsString.ToString())
+ .Replace("$CLASSES", classesString.ToString())
+ .Replace("$TUPLE", tupleString.ToString())
+ .Replace("$PROVIDER", context.Provider.ToString())
+ );
+ }
}
}
diff --git a/src/DbBroker.Cli/Services/Interfaces/ICSharpTransformer.cs b/src/DbBroker.Cli/Services/Interfaces/ICSharpTransformer.cs
deleted file mode 100644
index ea97f8e..0000000
--- a/src/DbBroker.Cli/Services/Interfaces/ICSharpTransformer.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace DbBroker.Cli.Services.Interfaces;
-
-///
-/// Methods to transform C# in SQL
-///
-public interface ICSharpTransformer
-{
-
-}
\ No newline at end of file
diff --git a/src/DbBroker.Cli/Services/Interfaces/IMetadataProvider.cs b/src/DbBroker.Cli/Services/Interfaces/IMetadataProvider.cs
index 9d830d5..4c4baec 100644
--- a/src/DbBroker.Cli/Services/Interfaces/IMetadataProvider.cs
+++ b/src/DbBroker.Cli/Services/Interfaces/IMetadataProvider.cs
@@ -4,13 +4,21 @@
namespace DbBroker.Cli.Services.Interfaces;
-public interface IMetadataProvider
+internal interface IMetadataProvider
{
///
- ///
+ /// Gets the tables descriptors for the context specified
///
/// The Database connection
/// The configuration context
/// A dictionary where the Key is 'Schema.TableName' of the tables and the value contains a
Task> GetTableDescriptorsAsync(DbConnection connection, DbBrokerConfigContext context);
+
+ ///
+ /// Gets the tables descriptors for the context specified
+ ///
+ /// The Database connection
+ /// The configuration context
+ /// A dictionary where the Key is 'Schema.ViewName' of the database views and the value contains a
+ Task> GetViewsDescriptorsAsync(DbConnection connection, DbBrokerConfigContext context);
}
diff --git a/src/DbBroker.Cli/Services/Interfaces/ISqlTransformer.cs b/src/DbBroker.Cli/Services/Interfaces/ISqlTransformer.cs
index 71f2c37..5352924 100644
--- a/src/DbBroker.Cli/Services/Interfaces/ISqlTransformer.cs
+++ b/src/DbBroker.Cli/Services/Interfaces/ISqlTransformer.cs
@@ -1,7 +1,7 @@
namespace DbBroker.Cli.Services.Interfaces;
///
-/// Methods to transform SQL in C#
+/// Methods to translate Database types into C# types
///
public interface ISqlTransformer
{
@@ -10,7 +10,9 @@ public interface ISqlTransformer
///
/// The database type
/// The database type length or option
+ /// The database number type precision
+ /// The database number type scale
/// True if the column accepts null, false otherwise
/// The C# type name or null if the database type is unsupported
- string? GetCSharpType(string databaseType, string? databaseTypeLength, bool isNullable);
+ string? GetCSharpType(string databaseType, string? databaseTypeLength, string? dataPrecision, string? dataScale, bool isNullable);
}
\ No newline at end of file
diff --git a/src/DbBroker.Cli/Services/Providers/MetadataProvider.cs b/src/DbBroker.Cli/Services/Providers/MetadataProvider.cs
index 6d38172..bd9799e 100644
--- a/src/DbBroker.Cli/Services/Providers/MetadataProvider.cs
+++ b/src/DbBroker.Cli/Services/Providers/MetadataProvider.cs
@@ -1,22 +1,35 @@
using System.Data.Common;
+using System.Diagnostics;
using Dapper;
+using DbBroker.Cli.Extensions;
using DbBroker.Cli.Model;
using DbBroker.Cli.Services.Interfaces;
using DbBroker.Common.Model;
namespace DbBroker.Cli.Services.Providers;
-public abstract class MetadataProvider(string sqlSelectColumns, string sqlSelectKeys) : IMetadataProvider
+internal abstract class MetadataProvider(string sqlSelectTablesColumns, string sqlSelectViewsColumns, string sqlSelectKeys) : IMetadataProvider
{
- private readonly string _sqlSelectColumns = sqlSelectColumns;
- private readonly string _sqlSelectKeys = sqlSelectKeys;
+ public abstract string GetTableFilterExpression(DbBrokerConfigContext context, bool checkAddReferencesConfig = false);
+
+ public abstract string GetViewsFilterExpression(DbBrokerConfigContext context);
public virtual async Task> GetTableDescriptorsAsync(DbConnection connection, DbBrokerConfigContext context)
{
+ var stopwatch = Stopwatch.StartNew();
Dictionary tableDescriptors = [];
- var columns = await connection.QueryAsync(_sqlSelectColumns);
- var keys = await connection.QueryAsync(_sqlSelectKeys);
+ var tablesFilterExpression = GetTableFilterExpression(context);
+
+ var sqlColumns = sqlSelectTablesColumns.Replace("$$TABLESFILTER$$", tablesFilterExpression);
+ sqlColumns.Debug(context.Namespace);
+ var columns = await connection.QueryAsync(sqlColumns);
+
+ var sqlKeys = sqlSelectKeys
+ .Replace("$$PRIMARYKEYS$$", tablesFilterExpression)
+ .Replace("$$FOREIGNKEYS$$", GetTableFilterExpression(context, true));
+ sqlKeys.Debug(context.Namespace);
+ var keys = await connection.QueryAsync(sqlKeys);
foreach (var tableColumns in columns.GroupBy(x => $"{x.SchemaName}.{x.TableName}"))
{
@@ -26,13 +39,50 @@ public virtual async Task> GetTableDesc
TableName = tableColumns.FirstOrDefault()?.TableName ?? string.Empty,
Columns = [.. tableColumns]
};
- tableDescriptor.Keys = keys
- .Where(x => x.SchemaName.Equals(tableDescriptor.SchemaName) && x.TableName.Equals(tableDescriptor.TableName))
- .ToArray();
+
+ var contextTable = context
+ .Tables
+ .FirstOrDefault(t => t.Name.ToLower().Equals(tableDescriptor.TableName.ToLower()));
+
+ tableDescriptor.Keys = [..
+ keys.Where(x =>
+ x.SchemaName.Equals(tableDescriptor.SchemaName)
+ && x.TableName.Equals(tableDescriptor.TableName)
+ && (x.ConstraintType != "PrimaryKey" || contextTable?.PrimaryKeyColumn is null || x.ColumnName.ToLower().Equals(contextTable?.PrimaryKeyColumn.ToLower())))];
tableDescriptors.Add(tableDescriptor.TableFullName, tableDescriptor);
}
+ $"{stopwatch.Elapsed.TotalSeconds:N2} seconds to retrieve tables metadata".Log(context.Namespace);
return tableDescriptors;
}
+
+ public async Task> GetViewsDescriptorsAsync(DbConnection connection, DbBrokerConfigContext context)
+ {
+ if (!context.Views.Any())
+ {
+ return [];
+ }
+
+ var stopwatch = Stopwatch.StartNew();
+ Dictionary viewsDescriptors = [];
+
+ var columns = await connection.QueryAsync(
+ sqlSelectViewsColumns.Replace("$$VIEWSFILTER$$", GetViewsFilterExpression(context)));
+
+ foreach (var viewColumns in columns.GroupBy(x => $"{x.SchemaName}.{x.ViewName}"))
+ {
+ var viewDescriptor = new ViewDescriptorModel
+ {
+ SchemaName = viewColumns.FirstOrDefault()?.SchemaName ?? string.Empty,
+ ViewName = viewColumns.FirstOrDefault()?.ViewName ?? string.Empty,
+ Columns = [.. viewColumns]
+ };
+
+ viewsDescriptors.Add(viewDescriptor.ViewFullName, viewDescriptor);
+ }
+
+ $"{stopwatch.Elapsed.TotalSeconds:N2} seconds to retrieve views metadata".Log(context.Namespace);
+ return viewsDescriptors;
+ }
}
diff --git a/src/DbBroker.Cli/Services/Providers/Oracle/OracleConstants.cs b/src/DbBroker.Cli/Services/Providers/Oracle/OracleConstants.cs
index 0a63755..4e2e4f8 100644
--- a/src/DbBroker.Cli/Services/Providers/Oracle/OracleConstants.cs
+++ b/src/DbBroker.Cli/Services/Providers/Oracle/OracleConstants.cs
@@ -2,22 +2,50 @@ namespace DbBroker.Cli.Services.Providers.Oracle;
public class OracleConstants
{
- internal const string SELECT_COLUMNS = @"
+ ///
+ /// Replace $$TABLESFILTER$$
+ ///
+ internal const string SELECT_TABLES_COLUMNS = @"
SELECT
t.owner AS SchemaName,
t.table_name AS TableName,
c.column_name AS ColumnName,
c.data_type AS DataType,
c.data_length AS MaxLength,
+ c.data_precision AS DataTypePrecision,
+ c.data_scale AS DataTypeScale,
CASE c.nullable WHEN 'N' THEN 0 ELSE 1 END AS IsNullable
FROM
all_tables t
INNER JOIN
all_tab_columns c ON t.table_name = c.table_name AND t.owner = c.owner
- WHERE t.owner = user
+ WHERE
+ t.owner = user
+ $$TABLESFILTER$$
ORDER BY
t.owner, t.table_name, c.column_id";
+ ///
+ /// Replace $$VIEWSFILTER$$
+ ///
+ internal const string SELECT_VIEWS_COLUMNS = @"
+ SELECT
+ v.owner AS SchemaName,
+ v.view_name AS ViewName,
+ c.column_name AS ColumnName,
+ c.data_type AS DataType,
+ c.data_length AS MaxLength,
+ CASE c.nullable WHEN 'N' THEN 0 ELSE 1 END AS IsNullable
+ FROM
+ all_views v
+ INNER JOIN
+ all_tab_columns c ON v.view_name = c.table_name AND v.owner = c.owner
+ WHERE
+ v.owner = user
+ $$VIEWSFILTER$$
+ ORDER BY
+ v.owner, v.view_name, c.column_id";
+
internal const string SELECT_KEYS = @"
SELECT
u.username AS SchemaName,
@@ -37,37 +65,31 @@ INNER JOIN
all_constraints con ON cc.constraint_name = con.constraint_name AND cc.owner = con.owner AND con.CONSTRAINT_TYPE = 'P'
INNER JOIN
all_users u ON t.owner = u.username
-
+ WHERE 1=1
+ $$PRIMARYKEYS$$
+
UNION ALL
- SELECT
- u.username AS SchemaName,
- tp.table_name AS TableName,
- cp.column_name AS ColumnName,
- fk.constraint_name AS ConstraintName,
+ SELECT t.owner AS SchemaName,
+ t.table_name AS TableName,
+ fkc.column_name AS ColumnName,
+ fkc.constraint_name AS ConstraintName,
'ForeignKey' AS ConstraintType,
- tr.table_name AS ReferencedTable,
- cr.column_name AS ReferencedColumn
- FROM
- all_constraints fk
- INNER JOIN
+ rfkc.table_name AS ReferencedTable,
+ rfkc.column_name AS ReferencedColumn
+ FROM
+ all_tables t
+ INNER JOIN
+ all_constraints fk ON fk.table_name = t.table_name
+ INNER JOIN
all_cons_columns fkc ON fk.constraint_name = fkc.constraint_name AND fk.owner = fkc.owner
INNER JOIN
- all_tables tp ON fkc.table_name = tp.table_name AND fkc.owner = tp.owner AND tp.owner = user
- INNER JOIN
- all_tab_columns cp ON fkc.table_name = cp.table_name AND fkc.column_name = cp.column_name AND fkc.owner = cp.owner
- INNER JOIN
- all_users u ON tp.owner = u.username
- INNER JOIN
- all_constraints pk ON fk.r_constraint_name = pk.constraint_name AND fk.r_owner = pk.owner
- INNER JOIN
- all_cons_columns pkk ON pk.constraint_name = pkk.constraint_name AND pk.owner = pkk.owner
- INNER JOIN
- all_tables tr ON pkk.table_name = tr.table_name AND pkk.owner = tr.owner
- INNER JOIN
- all_tab_columns cr ON pkk.table_name = cr.table_name AND pkk.column_name = cr.column_name AND pkk.owner = cr.owner
- WHERE
+ all_cons_columns rfkc ON rfkc.constraint_name = fk.r_constraint_name
+ WHERE
+ t.owner = user
+ AND
fk.constraint_type = 'R'
+ $$FOREIGNKEYS$$
ORDER BY
SchemaName, TableName, ColumnName
";
diff --git a/src/DbBroker.Cli/Services/Providers/Oracle/OracleDefaultConfiguration.cs b/src/DbBroker.Cli/Services/Providers/Oracle/OracleDefaultConfiguration.cs
index e0f1ce1..6014955 100644
--- a/src/DbBroker.Cli/Services/Providers/Oracle/OracleDefaultConfiguration.cs
+++ b/src/DbBroker.Cli/Services/Providers/Oracle/OracleDefaultConfiguration.cs
@@ -1,9 +1,8 @@
-using System;
using DbBroker.Cli.Services.Interfaces;
namespace DbBroker.Cli.Services.Providers.Oracle;
public class OracleDefaultConfiguration : IProviderDefaultConfiguration
{
- public string ISqlInsertTemplateTypeFullName => throw new NotImplementedException();
+ public string ISqlInsertTemplateTypeFullName => "DbBroker.Model.Providers.Oracle.OracleExplicitKeySqlInsertTemplate";
}
diff --git a/src/DbBroker.Cli/Services/Providers/Oracle/OracleMetadaProvider.cs b/src/DbBroker.Cli/Services/Providers/Oracle/OracleMetadaProvider.cs
index 18c7191..4bd2803 100644
--- a/src/DbBroker.Cli/Services/Providers/Oracle/OracleMetadaProvider.cs
+++ b/src/DbBroker.Cli/Services/Providers/Oracle/OracleMetadaProvider.cs
@@ -1,8 +1,44 @@
+using DbBroker.Common.Model;
+
namespace DbBroker.Cli.Services.Providers.Oracle;
-public class OracleMetadaProvider : MetadataProvider
+internal class OracleMetadaProvider : MetadataProvider
{
- public OracleMetadaProvider() : base(OracleConstants.SELECT_COLUMNS, OracleConstants.SELECT_KEYS)
+ internal OracleMetadaProvider()
+ : base(
+ OracleConstants.SELECT_TABLES_COLUMNS,
+ OracleConstants.SELECT_VIEWS_COLUMNS,
+ OracleConstants.SELECT_KEYS)
+ {
+ }
+
+ public override string GetTableFilterExpression(DbBrokerConfigContext context, bool checkAddReferencesConfig = false)
{
+ if (!context.IgnoreTablesNotListed || !context.Tables.Any())
+ {
+ return string.Empty;
+ }
+
+ if (checkAddReferencesConfig)
+ {
+ // That will define/restrict the FOREIGN KEYS select
+ var tables = context.Tables.Where(t => context.Tables.Any(x => x.AddReferences && x.Name == t.Name));
+
+ return tables.Any() ?
+ $"AND lower(t.table_name) IN ({string.Join(',', tables.Select(x => $"'{x.Name?.ToLower()}'"))})" :
+ "AND 1 <> 1";
+ }
+
+ return $"AND lower(t.table_name) IN ({string.Join(',', context.Tables.Select(x => $"'{x.Name?.ToLower()}'"))})";
+ }
+
+ public override string GetViewsFilterExpression(DbBrokerConfigContext context)
+ {
+ if (!context.IgnoreViewsNotListed || !context.Tables.Any())
+ {
+ return string.Empty;
+ }
+
+ return $"AND lower(v.view_name) IN ({string.Join(',', context.Views.Select(x => $"'{x.Name?.ToLower()}'"))})";
}
}
diff --git a/src/DbBroker.Cli/Services/Providers/Oracle/OracleSqlTransformer.cs b/src/DbBroker.Cli/Services/Providers/Oracle/OracleSqlTransformer.cs
index 1324e6d..88f0627 100644
--- a/src/DbBroker.Cli/Services/Providers/Oracle/OracleSqlTransformer.cs
+++ b/src/DbBroker.Cli/Services/Providers/Oracle/OracleSqlTransformer.cs
@@ -12,7 +12,7 @@ public class OracleSqlTransformer : ISqlTransformer
/// The database type length
/// Specifies if the column associated with the type accepts null values
/// The .NET type short name or object if no mapping is found.
- public string? GetCSharpType(string databaseType, string? databaseTypeLength, bool isNullable)
+ public string? GetCSharpType(string databaseType, string? databaseTypeLength, string? dataPrecision, string? dataScale, bool isNullable)
{
switch (databaseType.ToLower())
{
@@ -20,8 +20,11 @@ public class OracleSqlTransformer : ISqlTransformer
case "varchar2":
case "nvarchar2":
case "char":
+ case "nchar":
case "clob":
case "rowid":
+ case "nclob":
+ case "long":
return "string?";
case "date":
case "datetime":
diff --git a/src/DbBroker.Cli/Services/Providers/Postgres/PostgresConstants.cs b/src/DbBroker.Cli/Services/Providers/Postgres/PostgresConstants.cs
new file mode 100644
index 0000000..05d6c41
--- /dev/null
+++ b/src/DbBroker.Cli/Services/Providers/Postgres/PostgresConstants.cs
@@ -0,0 +1,100 @@
+using System;
+
+namespace DbBroker.Cli.Services.Providers.Postgres;
+
+public class PostgresConstants
+{
+///
+ /// Replace $$TABLESFILTER$$
+ ///
+ internal const string SELECT_TABLES_COLUMNS = @"
+ SELECT
+ table_schema AS SchemaName,
+ table_name AS TableName,
+ column_name AS ColumnName,
+ data_type AS DataType,
+ character_maximum_length AS MaxLength,
+ numeric_precision AS DataTypePrecision,
+ numeric_scale AS DataTypeScale,
+ CASE is_nullable WHEN 'NO' THEN 0 ELSE 1 END AS IsNullable
+ FROM
+ information_schema.columns
+ WHERE
+ table_schema = current_schema()
+ --$$TABLESFILTER$$
+ ORDER BY
+ table_schema, table_name, ordinal_position;";
+
+ ///
+ /// Replace $$VIEWSFILTER$$
+ ///
+ internal const string SELECT_VIEWS_COLUMNS = @"
+ SELECT
+ c.table_schema AS SchemaName,
+ c.table_name AS ViewName,
+ c.column_name AS ColumnName,
+ c.data_type AS DataType,
+ c.character_maximum_length AS MaxLength,
+ CASE c.is_nullable WHEN 'NO' THEN 0 ELSE 1 END AS IsNullable
+ FROM
+ information_schema.columns c
+ INNER JOIN
+ information_schema.views v
+ ON c.table_schema = v.table_schema AND c.table_name = v.table_name
+ WHERE
+ c.table_schema = current_schema()
+ $$VIEWSFILTER$$
+ ORDER BY
+ c.table_schema, c.table_name, c.ordinal_position;";
+
+ internal const string SELECT_KEYS = @"
+ -- Primary Keys
+ SELECT
+ n.nspname AS SchemaName,
+ t.relname AS TableName,
+ a.attname AS ColumnName,
+ con.conname AS ConstraintName,
+ 'PrimaryKey' AS ConstraintType,
+ NULL AS ReferencedTable,
+ NULL AS ReferencedColumn
+ FROM
+ pg_constraint con
+ JOIN
+ pg_class t ON con.conrelid = t.oid
+ JOIN
+ pg_namespace n ON t.relnamespace = n.oid
+ JOIN
+ pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(con.conkey)
+ WHERE
+ con.contype = 'p'
+
+ UNION ALL
+
+ -- Foreign Keys
+ SELECT
+ n.nspname AS SchemaName,
+ t.relname AS TableName,
+ a.attname AS ColumnName,
+ con.conname AS ConstraintName,
+ 'ForeignKey' AS ConstraintType,
+ rt.relname AS ReferencedTable,
+ ra.attname AS ReferencedColumn
+ FROM
+ pg_constraint con
+ JOIN
+ pg_class t ON con.conrelid = t.oid
+ JOIN
+ pg_namespace n ON t.relnamespace = n.oid
+ JOIN
+ pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(con.conkey)
+ JOIN
+ pg_class rt ON con.confrelid = rt.oid
+ JOIN
+ pg_attribute ra ON ra.attrelid = rt.oid AND ra.attnum = ANY(con.confkey)
+ WHERE
+ con.contype = 'f'
+ ORDER BY
+ SchemaName, TableName, ColumnName;
+ ";
+
+}
diff --git a/src/DbBroker.Cli/Services/Providers/Postgres/PostgresDefaultConfiguration.cs b/src/DbBroker.Cli/Services/Providers/Postgres/PostgresDefaultConfiguration.cs
new file mode 100644
index 0000000..6e2f739
--- /dev/null
+++ b/src/DbBroker.Cli/Services/Providers/Postgres/PostgresDefaultConfiguration.cs
@@ -0,0 +1,8 @@
+using DbBroker.Cli.Services.Interfaces;
+
+namespace DbBroker.Cli.Services.Providers.Postgres;
+
+public class PostgresDefaultConfiguration : IProviderDefaultConfiguration
+{
+ public string ISqlInsertTemplateTypeFullName => "DbBroker.Model.Providers.Postgres.PostgresExplicitKeySqlInsertTemplate";
+}
diff --git a/src/DbBroker.Cli/Services/Providers/Postgres/PostgresMetadataProvider.cs b/src/DbBroker.Cli/Services/Providers/Postgres/PostgresMetadataProvider.cs
new file mode 100644
index 0000000..5bf1f8c
--- /dev/null
+++ b/src/DbBroker.Cli/Services/Providers/Postgres/PostgresMetadataProvider.cs
@@ -0,0 +1,30 @@
+using DbBroker.Common.Model;
+
+namespace DbBroker.Cli.Services.Providers.Postgres;
+
+internal class PostgresMetadataProvider : MetadataProvider
+{
+ internal PostgresMetadataProvider() : base(PostgresConstants.SELECT_TABLES_COLUMNS, PostgresConstants.SELECT_VIEWS_COLUMNS, PostgresConstants.SELECT_KEYS)
+ {
+ }
+
+ public override string GetTableFilterExpression(DbBrokerConfigContext context, bool checkAddReferencesConfig = false)
+ {
+ if (!context.IgnoreTablesNotListed || !context.Tables.Any())
+ {
+ return string.Empty;
+ }
+
+ return $"AND lower(table_name) IN ({string.Join(',', context.Tables.Select(x => $"'{x.Name?.ToLower()}'"))})";
+ }
+
+ public override string GetViewsFilterExpression(DbBrokerConfigContext context)
+ {
+ if (!context.IgnoreViewsNotListed || !context.Tables.Any())
+ {
+ return string.Empty;
+ }
+
+ return $"AND lower(v.table_name) IN ({string.Join(',', context.Views.Select(x => $"'{x.Name?.ToLower()}'"))})";
+ }
+}
diff --git a/src/DbBroker.Cli/Services/Providers/Postgres/PostgresSqlTransformer.cs b/src/DbBroker.Cli/Services/Providers/Postgres/PostgresSqlTransformer.cs
new file mode 100644
index 0000000..75814cc
--- /dev/null
+++ b/src/DbBroker.Cli/Services/Providers/Postgres/PostgresSqlTransformer.cs
@@ -0,0 +1,54 @@
+using DbBroker.Cli.Services.Interfaces;
+
+namespace DbBroker.Cli.Services.Providers.Postgres;
+
+public class PostgresSqlTransformer : ISqlTransformer
+{
+ public string? GetCSharpType(string databaseType, string? databaseTypeLength, string? dataPrecision, string? dataScale, bool isNullable)
+ {
+ switch (databaseType.ToLower())
+ {
+ case "character varying":
+ case "varchar":
+ case "text":
+ case "char":
+ case "character":
+ case "name":
+ return "string?";
+ case "timestamp":
+ case "timestamp without time zone":
+ case "timestamp with time zone":
+ case "date":
+ return isNullable ? "DateTime?" : "DateTime";
+ case "bytea":
+ return "byte[]?";
+ case "numeric":
+ case "decimal":
+ case "money":
+ return isNullable ? "decimal?" : "decimal";
+ case "integer":
+ case "int":
+ case "int4":
+ return isNullable ? "int?" : "int";
+ case "bigint":
+ case "int8":
+ return isNullable ? "long?" : "long";
+ case "smallint":
+ case "int2":
+ return isNullable ? "short?" : "short";
+ case "real":
+ case "float4":
+ return isNullable ? "float?" : "float";
+ case "double precision":
+ case "float8":
+ return isNullable ? "double?" : "double";
+ case "boolean":
+ case "bool":
+ return isNullable ? "bool?" : "bool";
+ case "uuid":
+ return isNullable ? "Guid?" : "Guid";
+ default:
+ return "object";
+ }
+ }
+}
diff --git a/src/DbBroker.Cli/Services/Providers/SqlServer/SqlServerConstants.cs b/src/DbBroker.Cli/Services/Providers/SqlServer/SqlServerConstants.cs
index 4a3e443..79f4d3c 100644
--- a/src/DbBroker.Cli/Services/Providers/SqlServer/SqlServerConstants.cs
+++ b/src/DbBroker.Cli/Services/Providers/SqlServer/SqlServerConstants.cs
@@ -1,10 +1,11 @@
-using System;
-
namespace DbBroker.Cli.Services.Providers.SqlServer;
public class SqlServerConstants
{
- internal const string SELECT_COLUMNS = @"
+ ///
+ /// Replace $$TABLESFILTER$$
+ ///
+ internal const string SELECT_TABLES_COLUMNS = @"
SELECT
s.name AS SchemaName,
t.name AS TableName,
@@ -20,9 +21,30 @@ INNER JOIN
sys.columns AS c ON t.object_id = c.object_id
INNER JOIN
sys.types AS ty ON c.user_type_id = ty.user_type_id
+ WHERE 1=1
+ $$TABLESFILTER$$
ORDER BY
s.name, t.name, c.column_id;";
+ internal const string SELECT_VIEWS_COLUMNS = @"
+ SELECT
+ s.name AS SchemaName,
+ v.name AS ViewName,
+ c.name AS ColumnName,
+ ty.name AS DataType,
+ c.max_length AS MaxLength,
+ c.is_nullable AS IsNullable
+ FROM
+ sys.views AS v
+ INNER JOIN
+ sys.schemas AS s ON v.schema_id = s.schema_id
+ INNER JOIN
+ sys.columns AS c ON v.object_id = c.object_id
+ INNER JOIN
+ sys.types AS ty ON c.user_type_id = ty.user_type_id
+ ORDER BY
+ s.name, v.name, c.column_id;";
+
internal const string SELECT_KEYS = @"
SELECT
s.name AS SchemaName,
diff --git a/src/DbBroker.Cli/Services/Providers/SqlServer/SqlServerMetadataProvider.cs b/src/DbBroker.Cli/Services/Providers/SqlServer/SqlServerMetadataProvider.cs
index d60734a..5a01a0a 100644
--- a/src/DbBroker.Cli/Services/Providers/SqlServer/SqlServerMetadataProvider.cs
+++ b/src/DbBroker.Cli/Services/Providers/SqlServer/SqlServerMetadataProvider.cs
@@ -1,8 +1,25 @@
+using DbBroker.Common.Model;
+
namespace DbBroker.Cli.Services.Providers.SqlServer;
-public class SqlServerMetadataProvider : MetadataProvider
+internal class SqlServerMetadataProvider : MetadataProvider
{
- public SqlServerMetadataProvider() : base(SqlServerConstants.SELECT_COLUMNS, SqlServerConstants.SELECT_KEYS)
+ internal SqlServerMetadataProvider() : base(SqlServerConstants.SELECT_TABLES_COLUMNS, SqlServerConstants.SELECT_VIEWS_COLUMNS, SqlServerConstants.SELECT_KEYS)
+ {
+ }
+
+ public override string GetTableFilterExpression(DbBrokerConfigContext context, bool checkAddReferencesConfig = false)
+ {
+ if (!context.IgnoreTablesNotListed || !context.Tables.Any())
+ {
+ return string.Empty;
+ }
+
+ return $"AND lower(t.name) IN ({string.Join(',', context.Tables.Select(x => $"'{x.Name?.ToLower()}'"))})";
+ }
+
+ public override string GetViewsFilterExpression(DbBrokerConfigContext context)
{
+ throw new NotImplementedException();
}
}
diff --git a/src/DbBroker.Cli/Services/Providers/SqlServer/SqlServerSqlTransformer.cs b/src/DbBroker.Cli/Services/Providers/SqlServer/SqlServerSqlTransformer.cs
index a98a3d5..f0879d2 100644
--- a/src/DbBroker.Cli/Services/Providers/SqlServer/SqlServerSqlTransformer.cs
+++ b/src/DbBroker.Cli/Services/Providers/SqlServer/SqlServerSqlTransformer.cs
@@ -4,7 +4,7 @@ namespace DbBroker.Cli.Services.Providers.SqlServer;
public class SqlServerSqlTransformer : ISqlTransformer
{
- public string? GetCSharpType(string databaseType, string? databaseTypeLength, bool isNullable)
+ public string? GetCSharpType(string databaseType, string? databaseTypeLength, string? dataPrecision, string? dataScale, bool isNullable)
{
return databaseType.ToLower() switch
{
diff --git a/src/DbBroker.Cli/dbbroker.config.json b/src/DbBroker.Cli/dbbroker.config.json
index 4ea2fd7..c980368 100644
--- a/src/DbBroker.Cli/dbbroker.config.json
+++ b/src/DbBroker.Cli/dbbroker.config.json
@@ -1,32 +1,12 @@
{
+ "$schema": "https://raw.githubusercontent.com/diegosiao/DBBroker/refs/heads/v3.0.0/docs/json-schema/dbbroker.config.schema.json",
"contexts": [
{
- "namespace": "EShop.DataModels.SqlServer",
- "connectionString": "Server=localhost,1439;Database=DbBroker;User Id=sa;Password=DBBroker_1;",
+ "namespace": "MyApp.DataModels",
"provider": "SqlServer",
- "modelsPrefix": "Db",
- "modelsSufix": "",
+ "connectionString": "",
"tables": [],
"views": []
- },
- {
- "namespace": "EShop.DataModels.Oracle",
- "provider": "Oracle",
- "connectionString": "user id=dbbroker;password=DBBroker_1;data source=//localhost:1529/xe;",
- "defaultSqlInsertTemplateTypeFullName": "DbBroker.Model.Providers.Oracle.OracleExplicitKeySqlInsertTemplate",
- "modelsPrefix": "Db",
- "modelsSufix": "",
- "tables": [
- {
- "name": "ORDER_STATUS",
- "SqlInsertTemplateTypeFullName": "",
- "SqlInsertTemplate": "OracleSequence",
- "SqlInsertTemplateArguments": {
- "sequence": "ORDER_STATUS_SEQ"
- }
- }
- ],
- "views": []
}
]
-}
+}
\ No newline at end of file
diff --git a/src/DbBroker.Common/Model/DbBrokerConfig.cs b/src/DbBroker.Common/Model/DbBrokerConfig.cs
index 2d93f15..775690f 100644
--- a/src/DbBroker.Common/Model/DbBrokerConfig.cs
+++ b/src/DbBroker.Common/Model/DbBrokerConfig.cs
@@ -5,6 +5,8 @@ namespace DbBroker.Common.Model;
public class DbBrokerConfig
{
+ public DbBrokerConfigDatabase Database { get; set; }
+
public IEnumerable Contexts { get; set; }
public DateTime? LastSynced { get; set; }
diff --git a/src/DbBroker.Common/Model/DbBrokerConfigContext.cs b/src/DbBroker.Common/Model/DbBrokerConfigContext.cs
index 130b561..731394c 100644
--- a/src/DbBroker.Common/Model/DbBrokerConfigContext.cs
+++ b/src/DbBroker.Common/Model/DbBrokerConfigContext.cs
@@ -6,7 +6,9 @@ public class DbBrokerConfigContext
{
public string Namespace { get; set; }
- public SupportedDatabaseProviders Provider { get; set; }
+ public string Name { get; set; }
+
+ public SupportedDatabaseProviders? Provider { get; set; }
public string ConnectionString { get; set; }
@@ -17,7 +19,7 @@ public class DbBrokerConfigContext
public string ConnectionStringKey { get; set; }
///
- /// Optional. Will override the default behavior of using the namespace as folder structure (skipping the first namespace segment when multiple).
+ /// Optional. Will override the default behavior of using the namespace as folder structure.
///
public string OutputDirectory { get; set; }
@@ -37,5 +39,22 @@ public class DbBrokerConfigContext
///
public string ModelsSufix { get; set; } = "DataModel";
+ ///
+ /// Should DBBroker ignore database tables not described on '' collection?. The default value is 'false'.
+ ///
+ public bool IgnoreTablesNotListed { get; set; }
+
+ ///
+ /// Should DBBroker ignore database views not described on '' collection?. The default value is 'false'.
+ ///
+ public bool IgnoreViewsNotListed { get; set; }
+
+ ///
+ /// Remove all preexisting files from the output directory before syncing
+ ///
+ public bool ClearOutputDirectory { get; set; }
+
public IEnumerable Tables { get; set; } = [];
+
+ public IEnumerable Views { get; set; } = [];
}
\ No newline at end of file
diff --git a/src/DbBroker.Common/Model/DbBrokerConfigContextColumn.cs b/src/DbBroker.Common/Model/DbBrokerConfigContextColumn.cs
index 984a648..6acfc0c 100644
--- a/src/DbBroker.Common/Model/DbBrokerConfigContextColumn.cs
+++ b/src/DbBroker.Common/Model/DbBrokerConfigContextColumn.cs
@@ -12,13 +12,22 @@ public class DbBrokerConfigContextColumn
public string TableName { get; set; }
+ public string ViewName { get; set; }
+
public string ColumnName { get; set; }
public string DataType { get; set; }
public string MaxLength { get; set; }
+ public string DataTypePrecision { get; set; }
+
+ public string DataTypeScale { get; set; }
+
public bool IsNullable { get; set; }
- public string ColumnFullName => $"{SchemaName}.{TableName}.{ColumnName}";
+ // TODO the logic to deal with read only column is missing
+ public bool ReadOnly { get; set; }
+
+ public string ColumnFullName => $"{SchemaName}.{TableName}{ViewName}.{ColumnName}";
}
diff --git a/src/DbBroker.Common/Model/DbBrokerConfigTable.cs b/src/DbBroker.Common/Model/DbBrokerConfigContextTable.cs
similarity index 76%
rename from src/DbBroker.Common/Model/DbBrokerConfigTable.cs
rename to src/DbBroker.Common/Model/DbBrokerConfigContextTable.cs
index b9df337..ba49b2c 100644
--- a/src/DbBroker.Common/Model/DbBrokerConfigTable.cs
+++ b/src/DbBroker.Common/Model/DbBrokerConfigContextTable.cs
@@ -17,5 +17,10 @@ public class DbBrokerConfigContextTable
public Dictionary SqlInsertTemplateArguments { get; set; }
+ // TODO the logic to deal with read only tables is missing
+ public bool ReadOnly { get; set; }
+
+ public bool AddReferences { get; set; } = true;
+
public IEnumerable Columns { get; set; }
}
diff --git a/src/DbBroker.Common/Model/DbBrokerConfigContextView.cs b/src/DbBroker.Common/Model/DbBrokerConfigContextView.cs
new file mode 100644
index 0000000..e9a4fe3
--- /dev/null
+++ b/src/DbBroker.Common/Model/DbBrokerConfigContextView.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+
+namespace DbBroker.Common.Model;
+
+public class DbBrokerConfigContextView
+{
+ public string Name { get; set; }
+
+ public string TypeName { get; set; }
+
+ public IEnumerable SplitsOn { get; set; } = [];
+}
diff --git a/src/DbBroker.Common/Model/DbBrokerConfigContextViewSplitOnItem.cs b/src/DbBroker.Common/Model/DbBrokerConfigContextViewSplitOnItem.cs
new file mode 100644
index 0000000..8e58942
--- /dev/null
+++ b/src/DbBroker.Common/Model/DbBrokerConfigContextViewSplitOnItem.cs
@@ -0,0 +1,10 @@
+namespace DbBroker.Common.Model;
+
+public class DbBrokerConfigContextViewSplitOnItem
+{
+ public string Column { get; set; }
+
+ public string Type { get; set; }
+
+ public bool Collection { get; set; }
+}
diff --git a/src/DbBroker.Common/Model/DbBrokerConfigDatabase.cs b/src/DbBroker.Common/Model/DbBrokerConfigDatabase.cs
new file mode 100644
index 0000000..5e9bfa7
--- /dev/null
+++ b/src/DbBroker.Common/Model/DbBrokerConfigDatabase.cs
@@ -0,0 +1,8 @@
+namespace DbBroker.Common.Model;
+
+public class DbBrokerConfigDatabase
+{
+ public SupportedDatabaseProviders? Provider { get; set; }
+
+ public string ConnectionString { get; set; }
+}
diff --git a/src/DbBroker.Common/SupportedDatabaseProviders.cs b/src/DbBroker.Common/SupportedDatabaseProviders.cs
index ed8b01a..ffad5bc 100644
--- a/src/DbBroker.Common/SupportedDatabaseProviders.cs
+++ b/src/DbBroker.Common/SupportedDatabaseProviders.cs
@@ -7,21 +7,26 @@ public enum SupportedDatabaseProviders
{
///
/// Microsoft SQL Server
- /// Required Client Library: System.Data.SqlClient
+ /// Required Client Library: Microsoft.Data.SqlClient
/// Tested against:
+ /// - 2019
/// - 2022
///
SqlServer,
///
/// Oracle
- /// Required Client Library: Oracle.ManagedDataAccess.Core
+ /// Required Client Library: Oracle.ManagedDataAccess.Core
/// Tested against:
/// - 11g
+ /// - 12c
+ /// - 19c
+ /// - 21c
///
Oracle,
+ Postgres,
+
// Roadmap...
- // Postgres,
// MySQl
}
diff --git a/src/DbBroker.Showcase.Cli/Databases/Oracle/init.sql b/src/DbBroker.Showcase.Cli/Databases/Oracle/init.sql
deleted file mode 100644
index 47f1129..0000000
--- a/src/DbBroker.Showcase.Cli/Databases/Oracle/init.sql
+++ /dev/null
@@ -1,161 +0,0 @@
-ALTER SESSION SET "_ORACLE_SCRIPT"=true;
-
--- Create user and grant privileges
-CREATE USER DBBROKER IDENTIFIED BY DBBroker_1 DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp;
-GRANT CONNECT, RESOURCE TO DBBROKER;
-
-ALTER USER DBBROKER quota 200M on USERS;
-
--- Connect to the new schema
-ALTER SESSION SET CURRENT_SCHEMA = DBBROKER;
-
--- Create the sequences and triggers for auto-increment
-CREATE SEQUENCE CUSTOMERS_NOTES_STATUS_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
-CREATE SEQUENCE ORDERS_NOTES_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
-CREATE SEQUENCE ORDER_STATUS_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
-CREATE SEQUENCE PROMOTIONS_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
-CREATE SEQUENCE PROMOTIONS_ENROLLMENTS_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
-
--- Create the tables
-CREATE TABLE ADDRESS (
- ID RAW(16) PRIMARY KEY,
- STREET VARCHAR2(100),
- CITY VARCHAR2(50),
- STATE VARCHAR2(50),
- POSTAL_CODE VARCHAR2(10),
- COUNTRY VARCHAR2(50)
-);
-
-CREATE TABLE CUSTOMERS (
- ID RAW(16) PRIMARY KEY,
- ADDRESS_ID RAW(16),
- NAME VARCHAR2(250) NOT NULL,
- BIRTHDAY DATE,
- ORDERS_TOTAL NUMBER,
- CREATED_AT TIMESTAMP NOT NULL,
- CREATED_BY VARCHAR2(50) NOT NULL,
- MODIFIED_AT TIMESTAMP,
- MODIFIED_BY VARCHAR2(50),
- CONSTRAINT FK_CUSTOMERS_ADDRESS_ID FOREIGN KEY (ADDRESS_ID) REFERENCES ADDRESS(ID)
-);
-
-CREATE TABLE CUSTOMERS_NOTES_STATUS (
- ID NUMBER PRIMARY KEY,
- STATUS VARCHAR2(50)
-);
-
-CREATE TABLE CUSTOMERS_NOTES (
- ID RAW(16) PRIMARY KEY,
- CUSTOMER_ID RAW(16),
- STATUS_ID NUMBER,
- NOTE_CONTENT VARCHAR2(50) NOT NULL,
- CONSTRAINT FK_CUSTOMERS_NOTES_CUSTOMER_ID FOREIGN KEY (CUSTOMER_ID) REFERENCES CUSTOMERS(ID),
- CONSTRAINT FK_CUSTOMERS_NOTES_STATUS_ID FOREIGN KEY (STATUS_ID) REFERENCES CUSTOMERS_NOTES_STATUS(ID)
-);
-
-CREATE TABLE ORDER_STATUS (
- ID NUMBER PRIMARY KEY,
- STATUS VARCHAR2(50) NOT NULL
-);
-
-CREATE TABLE ORDERS (
- ID RAW(16) PRIMARY KEY,
- CUSTOMER_ID RAW(16) NOT NULL,
- STATUS_ID NUMBER,
- CREATED_AT TIMESTAMP NOT NULL,
- CREATED_BY VARCHAR2(50) NOT NULL,
- MODIFIED_AT TIMESTAMP,
- MODIFIED_BY VARCHAR2(50),
- CONSTRAINT FK_ORDERS_CUSTOMER_ID FOREIGN KEY (CUSTOMER_ID) REFERENCES CUSTOMERS(ID),
- CONSTRAINT FK_ORDERS_STATUS_ID FOREIGN KEY (STATUS_ID) REFERENCES ORDER_STATUS(ID)
-);
-
-CREATE TABLE ORDERS_NOTES (
- ID NUMBER PRIMARY KEY,
- ORDER_ID RAW(16) NOT NULL,
- NOTE_CONTENT VARCHAR2(1024) NOT NULL,
- CONSTRAINT FK_ORDERS_NOTES_ORDER_ID FOREIGN KEY (ORDER_ID) REFERENCES ORDERS(ID)
-);
-
-CREATE TABLE PRODUCTS (
- ID RAW(16) PRIMARY KEY,
- PRODUCT_NAME VARCHAR2(50) NOT NULL
-);
-
-CREATE TABLE ORDERS_PRODUCTS (
- ID RAW(16) PRIMARY KEY,
- ORDER_ID RAW(16) NOT NULL,
- PRODUCT_ID RAW(16) NOT NULL,
- CONSTRAINT FK_ORDERS_PRODUCTS_ORDER_ID FOREIGN KEY (ORDER_ID) REFERENCES ORDERS(ID),
- CONSTRAINT FK_ORDERS_PRODUCTS_PRODUCT_ID FOREIGN KEY (PRODUCT_ID) REFERENCES PRODUCTS(ID)
-);
-
-CREATE TABLE PROMOTIONS (
- ID NUMBER PRIMARY KEY,
- TITLE VARCHAR2(50) NOT NULL,
- EXPIRATION TIMESTAMP NOT NULL
-);
-
-CREATE TABLE PROMOTIONS_ENROLLMENTS (
- ID NUMBER PRIMARY KEY,
- CUSTOMER_ID RAW(16) NOT NULL,
- PROMOTION_ID NUMBER NOT NULL,
- CONSTRAINT FK_PROMO_ENRO_CUSTOMER_ID FOREIGN KEY (CUSTOMER_ID) REFERENCES CUSTOMERS(ID),
- CONSTRAINT FK_PROMO_ENRO_PROMOTION_ID FOREIGN KEY (PROMOTION_ID) REFERENCES PROMOTIONS(ID)
-);
-
--- Add a comment to the primary key column
-COMMENT ON COLUMN CUSTOMERS.ID IS 'Primary key';
-
--- INSERT INTO ORDER_STATUS(ID, STATUS)
--- VALUES(ORDER_STATUS_SEQ.nextval, 'New');
-
--- INSERT INTO ORDER_STATUS(ID, STATUS)
--- VALUES(ORDER_STATUS_SEQ.nextval, 'Pending');
-
--- INSERT INTO ORDER_STATUS(ID, STATUS)
--- VALUES(ORDER_STATUS_SEQ.nextval, 'Finished');
-
--- INSERT INTO ORDER_STATUS(ID, STATUS)
--- VALUES(ORDER_STATUS_SEQ.nextval, 'Canceled');
-
--- Create triggers for auto-increment fields
-/* CREATE TRIGGER TRG_CUSTOMERS_NOTES_STATUS_BI
-BEFORE INSERT ON CUSTOMERS_NOTES_STATUS
-FOR EACH ROW
-BEGIN
- :NEW.ID := CUSTOMERS_NOTES_STATUS_SEQ.NEXTVAL;
-END;
-/
-
-CREATE TRIGGER TRG_ORDERS_NOTES_BI
-BEFORE INSERT ON ORDERS_NOTES
-FOR EACH ROW
-BEGIN
- :NEW.ID := ORDERS_NOTES_SEQ.NEXTVAL;
-END;
-/
-
-CREATE TRIGGER TRG_ORDER_STATUS_BI
-BEFORE INSERT ON ORDER_STATUS
-FOR EACH ROW
-BEGIN
- :NEW.ID := ORDER_STATUS_SEQ.NEXTVAL;
-END;
-/
-
-CREATE TRIGGER TRG_PROMOTIONS_BI
-BEFORE INSERT ON PROMOTIONS
-FOR EACH ROW
-BEGIN
- :NEW.ID := PROMOTIONS_SEQ.NEXTVAL;
-END;
-/
-
-CREATE TRIGGER TRG_PROMOTIONS_ENROLLMENTS_BI
-BEFORE INSERT ON PROMOTIONS_ENROLLMENTS
-FOR EACH ROW
-BEGIN
- :NEW.ID := PROMOTIONS_ENROLLMENTS_SEQ.NEXTVAL;
-END;
-/ */
diff --git a/src/DbBroker.Showcase.Cli/DbBroker.Showcase.Cli.csproj b/src/DbBroker.Showcase.Cli/DbBroker.Showcase.Cli.csproj
index 967821c..4446555 100644
--- a/src/DbBroker.Showcase.Cli/DbBroker.Showcase.Cli.csproj
+++ b/src/DbBroker.Showcase.Cli/DbBroker.Showcase.Cli.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/DbBroker.Showcase.Cli/Program.cs b/src/DbBroker.Showcase.Cli/Program.cs
index 60ce6eb..697c5a6 100644
--- a/src/DbBroker.Showcase.Cli/Program.cs
+++ b/src/DbBroker.Showcase.Cli/Program.cs
@@ -1,8 +1,7 @@
-using EShop.DataModels.Oracle;
-using DbBroker;
+using DbBroker;
using System.Diagnostics;
using DbBroker.Extensions;
-using DbBroker.Showcase.Cli.Seeders;
+using DbBroker.Tests.DataModels.Oracle;
Console.Write(
@"
@@ -10,23 +9,32 @@
DBBroker Showtime!
==================
+This project is a showcase of DBBroker capabilities.
+
Start databases:
> docker-compose up --build
-Once the databases are ready, generate the Data Models:
+Once the databases are ready, install DBBroker CLI and generate the Data Models:
- > dotnet install -g DbBroker.Cli
+ > dotnet tool install --global DbBroker.Cli --prerelease
> dbbroker sync
A running container does not mean the database is ready.
-Check the databases containers logs to make sure the databases are ready.
+Check the databases containers logs or healthcheck status to make sure the databases are ready.
+
+> Why should I use DBBroker?
+
+If you choose to use it, you will effortlessly generate and maintain Data Models classes
+that represents your database, besides you will get out-of-the-box CRUD and aggregation operations,
+plus application compatibility reports.
All good? Showtime now?
(y/n): ");
if (!Debugger.IsAttached && (!Console.ReadLine()?.ToLower().Equals("y") ?? true))
{
+ Environment.Exit(0);
return;
}
@@ -36,15 +44,12 @@ Which database you would like to use?
[1] SQL Server
[2] Oracle
-Input the the option number: ");
+: ");
Console.WriteLine(Guid.NewGuid().ToString());
Console.WriteLine(Guid.NewGuid().ToString());
-OracleSeeder.Seed();
-
-// (Too much SQL) Dapper -------> [ DBBroker ⭐ ] <------- EF (Too much management)
-
+// Creating connection from a Data Model type
using var connection = typeof(CustomersDataModel)
.GetConnection("user id=dbbroker;password=DBBroker_1;data source=//localhost:1529/xe;");
@@ -52,6 +57,7 @@ [2] Oracle
var orders = connection
.Select(
load: [
+
// Root properties (depth 0)
(x => x.CreatedAt),
(x => x.CreatedBy),
@@ -60,22 +66,57 @@ [2] Oracle
(x => x.CustomerIdRef!.Name),
// Just 'Country' and 'State' for Address (depth 2)
- (x => x.CustomerIdRef!.AddressIdRef!.Country),
+ (x => x.CustomerIdRef!.AddressIdRef!.Country),
(x => x.CustomerIdRef!.AddressIdRef!.State),
- // Explicitly loading collections
+ // Explicitly loading collections
(x => x.OrdersProductsOrderIdRefs),
(x => x.OrdersNotesOrderIdRefs)
],
- depth: 2,
- skip: 1,
- take: 2)
+ depth: 2)
.OrderBy(x => x.CreatedBy)
.OrderBy(x => x.CustomerIdRef!.Name, ascending: false)
//.AddFilter(x => x.CustomerId, SqlEquals.To(OracleSeeder.customerId_1))
.Execute();
-Console.WriteLine($"Orders retrieved: {orders.Count()}");
-Console.WriteLine($"Customers: {string.Join(",", orders.Select(x => x.CustomerIdRef!.Name))}");
+// var orderSummary = connection
+// .Select()
+// .OrderBy(x => x.StatusId)
+// .OrderBy(x => x.UvwOrderSummaryCustomerRef!.Name)
+// .Execute();
+
+// var o = connection
+// .Insert(new UvwOrderSummaryDataModelTuple());
+
+try
+{
+ var count = connection
+ .Count()
+ .AddFilter(x => x.Id, SqlEquals.To(new Guid("5a0642ca-1710-48b6-9381-93f4072fee9d").ToByteArray()))
+ .Execute();
+
+ var sum = connection
+ .Sum(x => x.StatusId)
+ .Execute();
+
+ var avg = connection
+ .Avg(x => x.StatusId)
+ .Execute();
+
+ var min = connection
+ .Min(x => x.StatusId)
+ .Execute();
+
+ var max = connection
+ .Max(x => x.StatusId)
+ .Execute();
+
+}
+catch (Exception ex)
+{
+ Console.WriteLine(ex.ToString());
+}
+
+// Console.WriteLine($"Customers: {string.Join(",", orders.Select(x => x.CustomerIdRef!.Name))}");
Console.WriteLine($"Press any key to finish...");
Console.Read();
diff --git a/src/DbBroker.Showcase.Cli/README.md b/src/DbBroker.Showcase.Cli/README.md
index 77938ba..fd20d64 100644
--- a/src/DbBroker.Showcase.Cli/README.md
+++ b/src/DbBroker.Showcase.Cli/README.md
@@ -2,11 +2,27 @@
The easyest way of getting started with DBBroker is running the showcase using Docker Compose to run the supported databases.
+The Database vendors versions are:
+
+- Oracle 21.3.0 XE
+- SQL Server 2019 - latest
+
+Start the databases by running:
+
+```bash
+docker compose build
+docker compose up -d
+```
+
+You can also build and run a single database selectively, if you prefer.
+
```bash
-docker-compose build
-docker-compose up -d
+docker-compose build sqlserver
+docker-compose up -d sqlserver
```
+To install DBBroker CLI .NET tool, run:
+
```bash
dotnet tool install --global DBBroker.Cli
```
diff --git a/src/DbBroker.Showcase.Cli/Seeders/OracleSeeder.cs b/src/DbBroker.Showcase.Cli/Seeders/OracleSeeder.cs
deleted file mode 100644
index b74db53..0000000
--- a/src/DbBroker.Showcase.Cli/Seeders/OracleSeeder.cs
+++ /dev/null
@@ -1,285 +0,0 @@
-using System.Data.Common;
-using DbBroker.Extensions;
-using DbBroker.Model;
-using EShop.DataModels.Oracle;
-
-namespace DbBroker.Showcase.Cli.Seeders;
-
-public static class OracleSeeder
-{
- public readonly static byte[] customerId_1 = new Guid("80202d51-cf03-7840-9f81-7a65e97830ec").ToByteArray();
-
- public readonly static byte[] customerId_2 = new Guid("47748fba-db36-4502-b2df-06ef2c7502fd").ToByteArray();
-
- public readonly static byte[] customerId_3 = new Guid("fd1d24b4-6daa-4e95-bb33-9fa57fdc66d9").ToByteArray();
-
- private static List products = [
- new() {
- Id = Guid.NewGuid().ToByteArray(),
- ProductName = "Banana"
- },
- new() {
- Id = Guid.NewGuid().ToByteArray(),
- ProductName = "Orange"
- },
- new() {
- Id = Guid.NewGuid().ToByteArray(),
- ProductName = "Apple"
- },
- new() {
- Id = Guid.NewGuid().ToByteArray(),
- ProductName = "Grape"
- },
- ];
-
- public static void Seed()
- {
- using var connection = typeof(CustomersDataModel).GetConnection("user id=dbbroker;password=DBBroker_1;data source=//localhost:1529/xe;");
-
- // check if seeded already
- if (connection.Select().AddFilter(x => x.Id, SqlEquals.To(customerId_1)).Execute().Any())
- {
- return;
- }
-
- var ordersStatus = new List { "New", "Pending", "Finished", "Canceled" };
- ordersStatus.ForEach(x =>
- {
- var order = new OrderStatusDataModel
- {
- Status = x
- };
- connection.Insert(order);
- });
-
- try
- {
- SeedCustomerAndOrders_1(connection);
- SeedCustomerAndOrders_2(connection);
- SeedCustomerAndOrders_3(connection);
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex.Message);
- }
- }
-
- private static void SeedCustomerAndOrders_1(DbConnection connection)
- {
- AddressDataModel address = new()
- {
- Id = Guid.NewGuid().ToByteArray(),
- Street = "Av. Pres. Café Filho, 400",
- State = "RN",
- PostalCode = "59010000",
- City = "Natal",
- Country = "Brazil"
- };
-
- CustomersDataModel customer = new()
- {
- Id = customerId_1,
- AddressId = address.Id,
- Name = "John Three Sixteen",
- CreatedAt = DateTime.Now,
- CreatedBy = Environment.UserName,
- };
-
- OrdersDataModel order = new()
- {
- Id = Guid.NewGuid().ToByteArray(),
- StatusId = 1,
- CustomerId = customer.Id,
- CreatedAt = DateTime.Now,
- CreatedBy = Environment.UserName,
- };
-
- using var transaction = connection.BeginTransaction();
- try
- {
- connection.Insert(address, transaction);
- connection.Insert(customer, transaction);
- connection.Insert(order, transaction);
- transaction.Commit();
- }
- catch (Exception ex)
- {
- transaction.Rollback();
- Console.WriteLine(ex.Message);
- }
-
- products.ForEach(product =>
- {
- connection.Insert(product);
- connection.Insert(new OrdersProductsDataModel
- {
- Id = Guid.NewGuid().ToByteArray(),
- OrderId = order.Id,
- ProductId = product.Id,
- });
- });
-
- var orderNotes = new List{
- new(){
- OrderId = order.Id,
- NoteContent = "Note content 1",
- },
- new(){
- OrderId = order.Id,
- NoteContent = "Note content 2",
- },
- new(){
- OrderId = order.Id,
- NoteContent = "Note content 3",
- },
- new(){
- OrderId = order.Id,
- NoteContent = "Note content 4",
- },
- new(){
- OrderId = order.Id,
- NoteContent = "Note content 5",
- },
- };
-
- orderNotes.ForEach(note =>
- {
- connection.Insert(note);
- });
- }
-
- private static void SeedCustomerAndOrders_2(DbConnection connection)
- {
- AddressDataModel address = new()
- {
- Id = Guid.NewGuid().ToByteArray(),
- Street = "Av. Prudente de Morais, 5121",
- State = "RN",
- PostalCode = "59064-625",
- City = "Natal",
- Country = "Brazil"
- };
-
- CustomersDataModel customer = new()
- {
- Id = customerId_2,
- AddressId = address.Id,
- Name = "Luke Two",
- CreatedAt = DateTime.Now,
- CreatedBy = Environment.UserName,
- };
-
- OrdersDataModel order = new()
- {
- Id = Guid.NewGuid().ToByteArray(),
- StatusId = 1,
- CustomerId = customer.Id,
- CreatedAt = DateTime.Now,
- CreatedBy = Environment.UserName,
- };
-
- using var transaction = connection.BeginTransaction();
- try
- {
- connection.Insert(address, transaction);
- connection.Insert(customer, transaction);
- connection.Insert(order, transaction);
- transaction.Commit();
- }
- catch (Exception ex)
- {
- transaction.Rollback();
- Console.WriteLine(ex.Message);
- }
-
- products.Skip(3).ToList().ForEach(product =>
- {
- connection.Insert(new OrdersProductsDataModel
- {
- Id = Guid.NewGuid().ToByteArray(),
- OrderId = order.Id,
- ProductId = product.Id,
- });
- });
-
- var orderNotes = new List{
- new(){
- OrderId = order.Id,
- NoteContent = "[Customer 2] Note content 1",
- },
- new(){
- OrderId = order.Id,
- NoteContent = "[Customer 2] Note content 2",
- }
- };
-
- orderNotes.ForEach(note =>
- {
- connection.Insert(note);
- });
- }
-
- private static void SeedCustomerAndOrders_3(DbConnection connection)
- {
- AddressDataModel address = new()
- {
- Id = Guid.NewGuid().ToByteArray(),
- Street = "Av. Pres. Café Filho, 1",
- State = "RN",
- PostalCode = "59010000",
- City = "Natal",
- Country = "Brazil"
- };
-
- CustomersDataModel customer = new()
- {
- Id = customerId_3,
- AddressId = address.Id,
- Name = "Matthew Seven",
- CreatedAt = DateTime.Now,
- CreatedBy = Environment.UserName,
- };
-
- OrdersDataModel order = new()
- {
- Id = Guid.NewGuid().ToByteArray(),
- StatusId = 1,
- CustomerId = customer.Id,
- CreatedAt = DateTime.Now,
- CreatedBy = Environment.UserName,
- };
-
- using var transaction = connection.BeginTransaction();
- try
- {
- connection.Insert(address, transaction);
- connection.Insert(customer, transaction);
- connection.Insert(order, transaction);
- transaction.Commit();
- }
- catch (Exception ex)
- {
- transaction.Rollback();
- Console.WriteLine(ex.Message);
- }
-
- connection.Insert(new OrdersProductsDataModel
- {
- Id = Guid.NewGuid().ToByteArray(),
- OrderId = order.Id,
- ProductId = products.Last().Id,
- });
-
- var orderNotes = new List{
- new(){
- OrderId = order.Id,
- NoteContent = "[Customer 3] Note content 1",
- }
- };
-
- orderNotes.ForEach(note =>
- {
- connection.Insert(note);
- });
- }
-}
diff --git a/src/DbBroker.Tests.DataModels/DbBroker.Tests.DataModels.csproj b/src/DbBroker.Tests.DataModels/DbBroker.Tests.DataModels.csproj
new file mode 100644
index 0000000..0a26fd8
--- /dev/null
+++ b/src/DbBroker.Tests.DataModels/DbBroker.Tests.DataModels.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DbBroker.Showcase.Cli/dbbroker.config.json b/src/DbBroker.Tests.DataModels/dbbroker.config.json
similarity index 93%
rename from src/DbBroker.Showcase.Cli/dbbroker.config.json
rename to src/DbBroker.Tests.DataModels/dbbroker.config.json
index 4d9a141..0aab037 100644
--- a/src/DbBroker.Showcase.Cli/dbbroker.config.json
+++ b/src/DbBroker.Tests.DataModels/dbbroker.config.json
@@ -2,7 +2,7 @@
"$schema": "https://raw.githubusercontent.com/diegosiao/DBBroker/refs/heads/v3.0.0/docs/json-schema/dbbroker.config.schema.json",
"contexts": [
{
- "namespace": "EShop.DataModels.SqlServer",
+ "namespace": "DbBroker.Tests.DataModels.SqlServer",
"provider": "SqlServer",
"connectionString": "Server=127.0.0.1,1439;Database=DbBroker;User Id=sa;Password=DBBroker_1;",
"defaultSqlInsertTemplateTypeFullName": "DbBroker.Model.Providers.SqlServerExplicitKeySqlInsertTemplate",
@@ -16,7 +16,7 @@
"views": []
},
{
- "namespace": "EShop.DataModels.Oracle",
+ "namespace": "DbBroker.Tests.DataModels.Oracle",
"provider": "Oracle",
"connectionString": "user id=dbbroker;password=DBBroker_1;data source=//localhost:1529/xe;",
"defaultSqlInsertTemplateTypeFullName": "DbBroker.Model.Providers.Oracle.OracleExplicitKeySqlInsertTemplate",
diff --git a/src/DBBroker.Unit.Tests/Config/ConnectionStrings.cs b/src/DbBroker.Tests/Config/ConnectionStrings.cs
similarity index 75%
rename from src/DBBroker.Unit.Tests/Config/ConnectionStrings.cs
rename to src/DbBroker.Tests/Config/ConnectionStrings.cs
index 9c2ca27..1ccc9f2 100644
--- a/src/DBBroker.Unit.Tests/Config/ConnectionStrings.cs
+++ b/src/DbBroker.Tests/Config/ConnectionStrings.cs
@@ -1,4 +1,4 @@
-namespace DbBroker.Unit.Tests.Config;
+namespace DbBroker.Tests.Config;
public class ConnectionStrings
{
diff --git a/src/DBBroker.Unit.Tests/DBBroker.Unit.Tests.csproj b/src/DbBroker.Tests/DbBroker.Tests.csproj
similarity index 60%
rename from src/DBBroker.Unit.Tests/DBBroker.Unit.Tests.csproj
rename to src/DbBroker.Tests/DbBroker.Tests.csproj
index ef07565..8e03e28 100644
--- a/src/DBBroker.Unit.Tests/DBBroker.Unit.Tests.csproj
+++ b/src/DbBroker.Tests/DbBroker.Tests.csproj
@@ -1,28 +1,24 @@
-
+
- net8.0
+ net9.0
enable
enable
-
false
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -32,7 +28,12 @@
-
+
+
+
+
+
+
diff --git a/src/DBBroker.Unit.Tests/Providers/Oracle/DeleteTests.cs b/src/DbBroker.Tests/Providers/Oracle/DeleteTests.cs
similarity index 86%
rename from src/DBBroker.Unit.Tests/Providers/Oracle/DeleteTests.cs
rename to src/DbBroker.Tests/Providers/Oracle/DeleteTests.cs
index 000a86a..2c3ef88 100644
--- a/src/DBBroker.Unit.Tests/Providers/Oracle/DeleteTests.cs
+++ b/src/DbBroker.Tests/Providers/Oracle/DeleteTests.cs
@@ -1,10 +1,9 @@
using System.Data.Common;
-using DbBroker.Model;
-using EShop.DataModels.Oracle;
+using DbBroker.Tests.DataModels.Oracle;
using Microsoft.Extensions.DependencyInjection;
using Oracle.ManagedDataAccess.Client;
-namespace DbBroker.Unit.Tests.Providers.Oracle;
+namespace DbBroker.Tests.Providers.Oracle;
public class DeleteTests(ServiceProviderFixture fixture) : IClassFixture
{
@@ -24,7 +23,9 @@ private void DeleteRecord(DbTransaction? transaction)
CreatedBy = Environment.UserName,
};
- _oracleConnection.Insert(customer, transaction);
+ _oracleConnection
+ .Insert(customer, transaction)
+ .Execute();
var rowsAffected = _oracleConnection
.Delete()
@@ -33,7 +34,7 @@ private void DeleteRecord(DbTransaction? transaction)
transaction?.Commit();
- Assert.True(rowsAffected == 1);
+ Assert.Equal(1, rowsAffected);
}
[Fact]
diff --git a/src/DBBroker.Unit.Tests/Providers/Oracle/InsertTests.cs b/src/DbBroker.Tests/Providers/Oracle/InsertTests.cs
similarity index 54%
rename from src/DBBroker.Unit.Tests/Providers/Oracle/InsertTests.cs
rename to src/DbBroker.Tests/Providers/Oracle/InsertTests.cs
index b810c1f..7950696 100644
--- a/src/DBBroker.Unit.Tests/Providers/Oracle/InsertTests.cs
+++ b/src/DbBroker.Tests/Providers/Oracle/InsertTests.cs
@@ -1,15 +1,15 @@
using System.Data;
-using EShop.DataModels.Oracle;
+using DbBroker.Tests.DataModels.Oracle;
using Microsoft.Extensions.DependencyInjection;
using Oracle.ManagedDataAccess.Client;
-namespace DbBroker.Unit.Tests.Providers.Oracle;
+namespace DbBroker.Tests.Providers.Oracle;
public class InsertTests(ServiceProviderFixture fixture) : IClassFixture
{
private readonly OracleConnection _oracleConnection = fixture.ServiceProvider.GetService()!;
- [Fact]
+ [Fact(DisplayName = "Can insert a record?")]
public void CanInsertRecord()
{
CustomersDataModel customer = new()
@@ -22,8 +22,39 @@ public void CanInsertRecord()
CreatedBy = Environment.UserName,
};
- var customerInserted = _oracleConnection.Insert(customer);
- Assert.True(customerInserted);
+ var rowsAffected = _oracleConnection
+ .Insert(customer)
+ .Execute();
+
+ Assert.Equal(1, rowsAffected);
+ }
+
+ [Fact(DisplayName = "Can insert a record using all supported types?")]
+ public void CanInsertAllSupportedTypesRecord()
+ {
+ // TODO Implement this test properly
+ if (_oracleConnection.State != ConnectionState.Open)
+ {
+ _oracleConnection.Open();
+ }
+
+ SupportedTypesDataModel supportedTypes = new()
+ {
+ UuidType = Guid.NewGuid().ToByteArray(),
+ Varchar2TypeMax = "John Three Sixteen",
+ Varchar2TypeMin = "A",
+ NumberType = 20,
+ DateType = DateTime.Now.AddYears(-30),
+ TimestampType = DateTime.UtcNow,
+ BooleanType = 0,
+ };
+
+ // var rowsAffected = _oracleConnection
+ // .Insert(supportedTypes)
+ // .Execute();
+
+ // Assert.Equal(1, rowsAffected);
+ Assert.True(true);
}
[Fact]
@@ -35,7 +66,7 @@ public void CanInsertUsingTransaction()
}
var transaction = _oracleConnection.BeginTransaction();
- AddressDataModel address = new()
+ AddressesDataModel address = new()
{
Id = Guid.NewGuid().ToByteArray(),
Street = "Prudente De Morais, Av.",
@@ -61,15 +92,19 @@ public void CanInsertUsingTransaction()
Id = Guid.NewGuid().ToByteArray(),
CustomerId = customer.Id,
StatusId = 1,
+ BillingAddressId = address.Id,
+ ShippingAddressId = address.Id,
CreatedAt = DateTime.UtcNow,
CreatedBy = Environment.UserName,
};
- Assert.True(
- _oracleConnection.Insert(address, transaction)
- && _oracleConnection.Insert(customer, transaction)
- && _oracleConnection.Insert(order, transaction));
+ var rowsAffected =
+ _oracleConnection.Insert(address, transaction).Execute()
+ + _oracleConnection.Insert(customer, transaction).Execute()
+ + _oracleConnection.Insert(order, transaction).Execute();
+ Assert.Equal(3, rowsAffected);
+
transaction.Commit();
}
}
diff --git a/src/DBBroker.Unit.Tests/Providers/Oracle/SelectTests.cs b/src/DbBroker.Tests/Providers/Oracle/SelectTests.cs
similarity index 73%
rename from src/DBBroker.Unit.Tests/Providers/Oracle/SelectTests.cs
rename to src/DbBroker.Tests/Providers/Oracle/SelectTests.cs
index 40e169e..c427f9d 100644
--- a/src/DBBroker.Unit.Tests/Providers/Oracle/SelectTests.cs
+++ b/src/DbBroker.Tests/Providers/Oracle/SelectTests.cs
@@ -1,9 +1,9 @@
-using DbBroker.Model;
-using EShop.DataModels.Oracle;
+using DbBroker.Tests.DataModels.Oracle;
+using DbBroker.Tests;
using Microsoft.Extensions.DependencyInjection;
using Oracle.ManagedDataAccess.Client;
-namespace DbBroker.Unit.Tests.Providers.Oracle;
+namespace DbBroker.Tests.Providers.Oracle;
public class SelectTests(ServiceProviderFixture fixture) : IClassFixture
{
@@ -24,14 +24,17 @@ public void CanSelectRecord()
CreatedBy = Environment.UserName,
};
- var customerInserted = _oracleConnection.Insert(customer);
- Assert.True(customerInserted);
+ var rowsAffected = _oracleConnection
+ .Insert(customer)
+ .Execute();
+
+ Assert.Equal(1, rowsAffected);
- var customerSelected = _oracleConnection
+ var result = _oracleConnection
.Select()
.AddFilter(x => x.Id, SqlEquals.To(customerId.ToByteArray()))
.Execute();
- Assert.True(customerSelected.Count() == 1);
+ Assert.Single(result);
}
}
diff --git a/src/DBBroker.Unit.Tests/Providers/Oracle/UpdateTests.cs b/src/DbBroker.Tests/Providers/Oracle/UpdateTests.cs
similarity index 92%
rename from src/DBBroker.Unit.Tests/Providers/Oracle/UpdateTests.cs
rename to src/DbBroker.Tests/Providers/Oracle/UpdateTests.cs
index a6936dc..2e70796 100644
--- a/src/DBBroker.Unit.Tests/Providers/Oracle/UpdateTests.cs
+++ b/src/DbBroker.Tests/Providers/Oracle/UpdateTests.cs
@@ -1,10 +1,10 @@
using System.Data.Common;
using DbBroker.Model;
-using EShop.DataModels.Oracle;
+using DbBroker.Tests.DataModels.Oracle;
using Microsoft.Extensions.DependencyInjection;
using Oracle.ManagedDataAccess.Client;
-namespace DbBroker.Unit.Tests.Providers.Oracle;
+namespace DbBroker.Tests.Providers.Oracle;
public class UpdateTests(ServiceProviderFixture fixture) : IClassFixture
{
@@ -24,7 +24,9 @@ private void UpdateRecord(DbTransaction? transaction)
CreatedBy = Environment.UserName,
};
- _oracleConnection.Insert(customer, transaction);
+ _oracleConnection
+ .Insert(customer, transaction)
+ .Execute();
var customerInserted = _oracleConnection
.Select()
diff --git a/src/DBBroker.Unit.Tests/Providers/Oracle/UpsertTests.cs b/src/DbBroker.Tests/Providers/Oracle/UpsertTests.cs
similarity index 84%
rename from src/DBBroker.Unit.Tests/Providers/Oracle/UpsertTests.cs
rename to src/DbBroker.Tests/Providers/Oracle/UpsertTests.cs
index 2634333..aba538b 100644
--- a/src/DBBroker.Unit.Tests/Providers/Oracle/UpsertTests.cs
+++ b/src/DbBroker.Tests/Providers/Oracle/UpsertTests.cs
@@ -1,9 +1,9 @@
using DbBroker.Model;
-using EShop.DataModels.Oracle;
+using DbBroker.Tests.DataModels.Oracle;
using Microsoft.Extensions.DependencyInjection;
using Oracle.ManagedDataAccess.Client;
-namespace DbBroker.Unit.Tests.Providers.Oracle;
+namespace DbBroker.Tests.Providers.Oracle;
public class UpsertTests(ServiceProviderFixture fixture) : IClassFixture
{
@@ -24,11 +24,12 @@ public void CanUpsertRecord()
CreatedBy = Environment.UserName,
};
- _oracleConnection.Upsert(customer);
+ _oracleConnection
+ .Upsert(customer)
+ .Execute();
var customerInserted = _oracleConnection
.Select()
- .AddFilter(x => x.Id, SqlEquals.To(customerId.ToByteArray()))
.Execute()
.FirstOrDefault();
@@ -36,7 +37,9 @@ public void CanUpsertRecord()
customer.Name = "John Fourteen Six";
- _oracleConnection.Upsert(customer);
+ _oracleConnection
+ .Upsert(customer)
+ .Execute();
var customerUpdated = _oracleConnection
.Select()
diff --git a/src/DBBroker.Unit.Tests/Providers/SqlServer/CustomerTests.cs b/src/DbBroker.Tests/Providers/SqlServer/CustomerTests.cs
similarity index 73%
rename from src/DBBroker.Unit.Tests/Providers/SqlServer/CustomerTests.cs
rename to src/DbBroker.Tests/Providers/SqlServer/CustomerTests.cs
index 61a7469..41aed47 100644
--- a/src/DBBroker.Unit.Tests/Providers/SqlServer/CustomerTests.cs
+++ b/src/DbBroker.Tests/Providers/SqlServer/CustomerTests.cs
@@ -1,8 +1,8 @@
-using System.Data.SqlClient;
-using EShop.DataModels.SqlServer;
+using Microsoft.Data.SqlClient;
+using DbBroker.Tests.DataModels.SqlServer;
using Microsoft.Extensions.DependencyInjection;
-namespace DbBroker.Unit.Tests.Providers.SqlServer;
+namespace DbBroker.Tests.Providers.SqlServer;
public class CustomerTests(ServiceProviderFixture fixture) : IClassFixture
{
@@ -21,7 +21,7 @@ public void CanCreateCustomer()
CreatedBy = Environment.UserName,
};
- var row = _sqlConnection.Insert(customer);
- Assert.True(row);
+ var rowsAffected = _sqlConnection.Insert(customer).Execute();
+ Assert.Equal(1, rowsAffected);
}
}
diff --git a/src/DBBroker.Unit.Tests/ServiceProviderFixture.cs b/src/DbBroker.Tests/ServiceProviderFixture.cs
similarity index 94%
rename from src/DBBroker.Unit.Tests/ServiceProviderFixture.cs
rename to src/DbBroker.Tests/ServiceProviderFixture.cs
index 2a6214f..0b4b182 100644
--- a/src/DBBroker.Unit.Tests/ServiceProviderFixture.cs
+++ b/src/DbBroker.Tests/ServiceProviderFixture.cs
@@ -1,6 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
-namespace DbBroker.Unit.Tests;
+namespace DbBroker.Tests;
public class ServiceProviderFixture : IDisposable
{
diff --git a/src/DBBroker.Unit.Tests/Startup.cs b/src/DbBroker.Tests/Startup.cs
similarity index 92%
rename from src/DBBroker.Unit.Tests/Startup.cs
rename to src/DbBroker.Tests/Startup.cs
index 784f3be..140d72b 100644
--- a/src/DBBroker.Unit.Tests/Startup.cs
+++ b/src/DbBroker.Tests/Startup.cs
@@ -1,11 +1,11 @@
-using System.Data.SqlClient;
-using DbBroker.Unit.Tests.Config;
+using Microsoft.Data.SqlClient;
+using DbBroker.Tests.Config;
using Oracle.ManagedDataAccess.Client;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-namespace DbBroker.Unit.Tests;
+namespace DbBroker.Tests;
public class Startup
{
diff --git a/src/DBBroker.Unit.Tests/appsettings.json b/src/DbBroker.Tests/appsettings.json
similarity index 80%
rename from src/DBBroker.Unit.Tests/appsettings.json
rename to src/DbBroker.Tests/appsettings.json
index a09b3ee..28cf531 100644
--- a/src/DBBroker.Unit.Tests/appsettings.json
+++ b/src/DbBroker.Tests/appsettings.json
@@ -1,6 +1,6 @@
{
"ConnectionStrings": {
- "SqlServer": "Server=localhost,1439;Database=DbBroker;User Id=sa;Password=DBBroker_1;",
+ "SqlServer": "Server=localhost,1439;Database=DbBroker;User Id=sa;Password=DBBroker_1;Encrypt=false;",
"Oracle": "user id=dbbroker;password=DBBroker_1;data source=//localhost:1529/xe;"
}
-}
+}
\ No newline at end of file
diff --git a/src/DbBroker.sln b/src/DbBroker.sln
new file mode 100644
index 0000000..bac49e0
--- /dev/null
+++ b/src/DbBroker.sln
@@ -0,0 +1,49 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.36429.23 d17.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbBroker", "DbBroker\DbBroker.csproj", "{C0325966-63C9-AA56-26CC-21BCFD0A2762}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbBroker.Cli", "DbBroker.Cli\DbBroker.Cli.csproj", "{7852F5CC-F277-E73C-01C2-872A43C7F479}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbBroker.Tests", "DbBroker.Tests\DbBroker.Tests.csproj", "{47DE68C9-FE3C-FDB5-EAEA-7E58230F645D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbBroker.Common", "DbBroker.Common\DbBroker.Common.csproj", "{D78FD2D9-A07B-F0EF-AC6D-02D4706F64D3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbBroker.Tests.DataModels", "DbBroker.Tests.DataModels\DbBroker.Tests.DataModels.csproj", "{D306FB71-8BAF-5A46-20F2-358725E9EA9C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C0325966-63C9-AA56-26CC-21BCFD0A2762}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C0325966-63C9-AA56-26CC-21BCFD0A2762}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C0325966-63C9-AA56-26CC-21BCFD0A2762}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C0325966-63C9-AA56-26CC-21BCFD0A2762}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7852F5CC-F277-E73C-01C2-872A43C7F479}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7852F5CC-F277-E73C-01C2-872A43C7F479}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7852F5CC-F277-E73C-01C2-872A43C7F479}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7852F5CC-F277-E73C-01C2-872A43C7F479}.Release|Any CPU.Build.0 = Release|Any CPU
+ {47DE68C9-FE3C-FDB5-EAEA-7E58230F645D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {47DE68C9-FE3C-FDB5-EAEA-7E58230F645D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {47DE68C9-FE3C-FDB5-EAEA-7E58230F645D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {47DE68C9-FE3C-FDB5-EAEA-7E58230F645D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D78FD2D9-A07B-F0EF-AC6D-02D4706F64D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D78FD2D9-A07B-F0EF-AC6D-02D4706F64D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D78FD2D9-A07B-F0EF-AC6D-02D4706F64D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D78FD2D9-A07B-F0EF-AC6D-02D4706F64D3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D306FB71-8BAF-5A46-20F2-358725E9EA9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D306FB71-8BAF-5A46-20F2-358725E9EA9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D306FB71-8BAF-5A46-20F2-358725E9EA9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D306FB71-8BAF-5A46-20F2-358725E9EA9C}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {02488F59-3017-4161-BE11-4E47500B14A8}
+ EndGlobalSection
+EndGlobal
diff --git a/src/DbBroker/Attributes/ColumnType.cs b/src/DbBroker/Attributes/ColumnType.cs
new file mode 100644
index 0000000..e299f54
--- /dev/null
+++ b/src/DbBroker/Attributes/ColumnType.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace DbBroker.Attributes;
+
+///
+/// Specifies the database type of a column.
+///
+public class ColumnType : Attribute
+{
+ ///
+ /// Gets the database type of the column.
+ ///
+ public object ProviderDbType { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public ColumnType(object providerDbType)
+ {
+ ProviderDbType = providerDbType;
+ }
+}
diff --git a/src/DbBroker/Attributes/DataModelCollectionReferenceAttribute.cs b/src/DbBroker/Attributes/DataModelCollectionReferenceAttribute.cs
index 0e20f32..c745732 100644
--- a/src/DbBroker/Attributes/DataModelCollectionReferenceAttribute.cs
+++ b/src/DbBroker/Attributes/DataModelCollectionReferenceAttribute.cs
@@ -2,12 +2,43 @@
namespace DbBroker.Attributes;
+///
+/// Specifies that a property is a reference to a collection of data models.
+///
public class DataModelCollectionReferenceAttribute : DataModelReferenceBaseAttribute
{
+ ///
+ /// Gets or sets the type of the data model in the collection.
+ ///
public Type DataModelType { get; set; }
+ ///
+ /// Gets or sets the name of the primary key column in the referenced table.
+ ///
public string RefTablePrimaryKeyColumnName { get; set; }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ ///
+ public DataModelCollectionReferenceAttribute(string splitOnColumnName, Type dataModelType)
+ {
+ DataModelType = dataModelType;
+ ColumnName = splitOnColumnName;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public DataModelCollectionReferenceAttribute(
string schemaName,
string tableName,
diff --git a/src/DbBroker/Attributes/DataModelMetaColumnAttribute.cs b/src/DbBroker/Attributes/DataModelMetaColumnAttribute.cs
index 94cd2ff..3148107 100644
--- a/src/DbBroker/Attributes/DataModelMetaColumnAttribute.cs
+++ b/src/DbBroker/Attributes/DataModelMetaColumnAttribute.cs
@@ -2,6 +2,9 @@
namespace DbBroker.Attributes;
+///
+/// Specifies that a property is a meta column in a data model.
+///
public class DataModelMetaColumnAttribute : Attribute
{
diff --git a/src/DbBroker/Attributes/DataModelReferenceAttribute.cs b/src/DbBroker/Attributes/DataModelReferenceAttribute.cs
index 040a433..c198f81 100644
--- a/src/DbBroker/Attributes/DataModelReferenceAttribute.cs
+++ b/src/DbBroker/Attributes/DataModelReferenceAttribute.cs
@@ -1,21 +1,39 @@
-using System;
-
namespace DbBroker.Attributes;
+///
+/// Indicates that a property is a reference to another data model.
+///
public class DataModelReferenceAttribute : DataModelReferenceBaseAttribute
{
+ ///
+ /// The property that holds the reference.
+ ///
public string Property { get; private set; }
-
+
+ ///
+ /// The name of the schema that contains the table with the foreign key column.
+ ///
public bool ColumnAllowNulls { get; set; }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public DataModelReferenceAttribute(
- string property,
- string schemaName,
- string tableName,
- string columnName,
+ string property,
+ string schemaName,
+ string tableName,
+ string columnName,
bool columnAllowNulls,
- string refColumnName,
- string refSchemaName,
+ string refColumnName,
+ string refSchemaName,
string refTableName)
{
Property = property;
@@ -27,4 +45,13 @@ public DataModelReferenceAttribute(
RefSchemaName = refSchemaName;
RefTableName = refTableName;
}
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public DataModelReferenceAttribute(string splitOnColumnName)
+ {
+ ColumnName = splitOnColumnName;
+ }
}
diff --git a/src/DbBroker/Attributes/DataModelReferenceBaseAttribute.cs b/src/DbBroker/Attributes/DataModelReferenceBaseAttribute.cs
index f682f60..538f150 100644
--- a/src/DbBroker/Attributes/DataModelReferenceBaseAttribute.cs
+++ b/src/DbBroker/Attributes/DataModelReferenceBaseAttribute.cs
@@ -2,19 +2,43 @@
namespace DbBroker.Attributes;
+///
+/// Base attribute for data model references (foreign keys).
+///
public abstract class DataModelReferenceBaseAttribute : Attribute
{
+ ///
+ /// Schema name of the table containing the foreign key.
+ ///
public string SchemaName { get; set; }
+ ///
+ /// Table name containing the foreign key.
+ ///
public string TableName { get; set; }
+ ///
+ /// Column name of the foreign key.
+ ///
public string ColumnName { get; protected set; }
+ ///
+ /// Column name of the referenced primary key.
+ ///
public string RefColumnName { get; protected set; }
+ ///
+ /// Schema name of the referenced table.
+ ///
public string RefSchemaName { get; protected set; }
+ ///
+ /// Table name of the referenced table.
+ ///
public string RefTableName { get; protected set; }
+ ///
+ /// Full name of the table containing the foreign key.
+ ///
public string RefTableFullName => $"{RefSchemaName}.{RefTableName}";
}
diff --git a/src/DbBroker/Compatibility/DescriptorComparer.cs b/src/DbBroker/Compatibility/DescriptorComparer.cs
new file mode 100644
index 0000000..cf83fb1
--- /dev/null
+++ b/src/DbBroker/Compatibility/DescriptorComparer.cs
@@ -0,0 +1,9 @@
+namespace DbBroker.Compatibility;
+
+///
+/// Compares two descriptors for compatibility.
+///
+public class DescriptorComparer
+{
+ // TODO: Implement comparison logic for descriptors: One from the classes generated by the DbBroker tool and another from the database schema.
+}
diff --git a/src/DbBroker/Compatibility/DescriptorGenerator.cs b/src/DbBroker/Compatibility/DescriptorGenerator.cs
new file mode 100644
index 0000000..9962b64
--- /dev/null
+++ b/src/DbBroker/Compatibility/DescriptorGenerator.cs
@@ -0,0 +1,9 @@
+namespace DbBroker.Compatibility;
+
+///
+/// This class generates database descriptors and compatibility reports
+///
+public class DescriptorGenerator
+{
+ // TODO: Implement logic to generate descriptors from DBBroker Data Models on an Assembly and from Database.
+}
diff --git a/src/DbBroker/Constants.cs b/src/DbBroker/Constants.cs
index 4ead06c..08c7431 100644
--- a/src/DbBroker/Constants.cs
+++ b/src/DbBroker/Constants.cs
@@ -1,6 +1,6 @@
namespace DbBroker;
-public class Constants
+internal class Constants
{
public const string SqlSelectTemplate = @"
SELECT $$COLUMNS$$
@@ -9,6 +9,36 @@ public class Constants
$$ORDERBYCOLUMNS$$
$$OFFSETFETCH$$";
+ public const string SqlSelectCountTemplate = @"
+SELECT COUNT(*)
+FROM $$TABLEFULLNAME$$
+WHERE 1=1
+$$FILTERS$$";
+
+ public const string SqlSelectSumTemplate = @"
+SELECT SUM($$COLUMNS$$)
+FROM $$TABLEFULLNAME$$
+WHERE 1=1
+$$FILTERS$$";
+
+ public const string SqlSelectAvgTemplate = @"
+SELECT AVG($$COLUMNS$$)
+FROM $$TABLEFULLNAME$$
+WHERE 1=1
+$$FILTERS$$";
+
+ public const string SqlSelectMaxTemplate = @"
+SELECT MAX($$COLUMNS$$)
+FROM $$TABLEFULLNAME$$
+WHERE 1=1
+$$FILTERS$$";
+
+ public const string SqlSelectMinTemplate = @"
+SELECT MIN($$COLUMNS$$)
+FROM $$TABLEFULLNAME$$
+WHERE 1=1
+$$FILTERS$$";
+
public const string SqlUpdateTemplate = @"
UPDATE $$TABLEFULLNAME$$
SET $$COLUMNS$$
@@ -16,7 +46,7 @@ public class Constants
$$FILTERS$$";
public const string SqlDeleteTemplate = @"
-DELETE $$TABLEFULLNAME$$
+DELETE FROM $$TABLEFULLNAME$$
WHERE 1=1
$$FILTERS$$";
}
diff --git a/src/DbBroker/DataModelKeyComparer.cs b/src/DbBroker/DataModelKeyComparer.cs
index 2a2fe3a..8e807bb 100644
--- a/src/DbBroker/DataModelKeyComparer.cs
+++ b/src/DbBroker/DataModelKeyComparer.cs
@@ -4,7 +4,7 @@
namespace DbBroker;
-public class DataModelKeyComparer : IEqualityComparer
\ No newline at end of file
diff --git a/src/DbBroker/Extensions/NetStandard2LinqExtensions.cs b/src/DbBroker/Extensions/NetStandard2LinqExtensions.cs
index 8903074..3a9d78c 100644
--- a/src/DbBroker/Extensions/NetStandard2LinqExtensions.cs
+++ b/src/DbBroker/Extensions/NetStandard2LinqExtensions.cs
@@ -4,6 +4,9 @@
namespace DbBroker.Extensions;
+///
+/// Provides LINQ extension methods for .NET Standard 2.0
+///
public static class NetStandard2LinqExtensions
{
///
diff --git a/src/DbBroker/Extensions/ObjectExtensions.cs b/src/DbBroker/Extensions/ObjectExtensions.cs
new file mode 100644
index 0000000..46045d3
--- /dev/null
+++ b/src/DbBroker/Extensions/ObjectExtensions.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Reflection;
+
+namespace DbBroker.Extensions;
+
+///
+/// Provides extension methods for objects
+///
+public static class ObjectExtensions
+{
+ ///
+ /// Gets the value of the property marked with [Key] attribute
+ ///
+ ///
+ ///
+ public static object GetKeyValue(this object obj)
+ {
+ if (obj is null)
+ {
+ return null;
+ }
+
+ var keyProperty = obj
+ .GetType()
+ .GetProperties()
+ .Where(p => p.GetCustomAttribute() != null)
+ .FirstOrDefault();
+
+ return keyProperty?.GetValue(obj);
+ }
+
+ ///
+ /// Gets the list of columns for a given type, using the [Column] attribute to get the column name.
+ ///
+ ///
+ ///
+ ///
+ public static IEnumerable GetSelectColumnsFromType(this Type type, string alias = "d0.")
+ {
+ foreach (var prop in type.GetProperties())
+ {
+ var column = prop.GetCustomAttribute().Name;
+ yield return $"\"{alias}{column}\" AS {prop.Name}";
+ }
+ }
+}
diff --git a/src/DbBroker/Extensions/ResolversExtensions.cs b/src/DbBroker/Extensions/ResolversExtensions.cs
index d7c5cdd..62a2927 100644
--- a/src/DbBroker/Extensions/ResolversExtensions.cs
+++ b/src/DbBroker/Extensions/ResolversExtensions.cs
@@ -9,14 +9,16 @@
namespace DbBroker.Extensions;
+///
+/// Provides extension methods for resolvers
+///
public static class ResolversExtensions
{
///
/// Creates and returns a new instance according to the Data Model type provider.
/// Check the for details on the required database client library reference.
///
- /// The Data Model as argument type
- /// An instance of any Data Model from the target context resolved by its Namespace
+ /// The Data Model as argument type
/// Specify if you want to ignore 'dbbroker.config.json' connection string value
/// A new instance of .
public static DbConnection GetConnection(this Type dataModelType, string connectionString = null)
@@ -35,6 +37,7 @@ public static DbConnection GetConnection(this Type dataModelType, string connect
{
SupportedDatabaseProviders.SqlServer => Activator.CreateInstance(Type.GetType("System.Data.SqlClient.SqlConnection, System.Data.SqlClient"), connectionString) as DbConnection,
SupportedDatabaseProviders.Oracle => Activator.CreateInstance(Type.GetType("Oracle.ManagedDataAccess.Client.OracleConnection, Oracle.ManagedDataAccess"), connectionString) as DbConnection,
+ SupportedDatabaseProviders.Postgres => Activator.CreateInstance(Type.GetType("Npgsql.NpgsqlConnection, Npgsql"), connectionString) as DbConnection,
_ => throw new ArgumentException($"Not supported database provider: {dataModelBase?.DataModelMap?.Provider}"),
};
}
@@ -51,27 +54,86 @@ public static DbConnection GetConnection(this DataModel
{
if (string.IsNullOrEmpty(connectionString))
{
- // get from dbbroker.config.json or application settings
+ // TODO get from dbbroker.config.json or application settings
}
return dataModelBase.DataModelMap.Provider switch
{
- SupportedDatabaseProviders.SqlServer => Activator.CreateInstance(Type.GetType("System.Data.SqlClient.SqlConnection, System.Data.SqlClient"), connectionString) as DbConnection,
+ SupportedDatabaseProviders.SqlServer => Activator.CreateInstance(Type.GetType("Microsoft.Data.SqlClient.SqlConnection, Microsoft.Data.SqlClient"), connectionString) as DbConnection,
SupportedDatabaseProviders.Oracle => Activator.CreateInstance(Type.GetType("Oracle.ManagedDataAccess.Client.OracleConnection, Oracle.ManagedDataAccess"), connectionString) as DbConnection,
+ SupportedDatabaseProviders.Postgres => Activator.CreateInstance(Type.GetType("Npgsql.NpgsqlConnection, Npgsql"), connectionString) as DbConnection,
_ => throw new ArgumentException($"Not supported database provider: {dataModelBase.DataModelMap.Provider}"),
};
}
+ ///
+ /// Creates and returns a new instance according to the database provider.
+ /// Check the for details on the required database client library reference.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public static DbParameter GetDbParameter(this SupportedDatabaseProviders provider, string name, object value)
{
- return provider switch
+ switch (provider)
{
- SupportedDatabaseProviders.SqlServer => Activator.CreateInstance(Type.GetType("System.Data.SqlClient.SqlParameter, System.Data.SqlClient"), $"@{name}", value) as DbParameter,
- SupportedDatabaseProviders.Oracle => Activator.CreateInstance(Type.GetType("Oracle.ManagedDataAccess.Client.OracleParameter, Oracle.ManagedDataAccess"), $":{name}", value) as DbParameter,
- _ => throw new ArgumentException($"Not supported database provider: {provider}"),
- };
+ case SupportedDatabaseProviders.SqlServer:
+ return Activator.CreateInstance(Type.GetType("Microsoft.Data.SqlClient.SqlParameter, Microsoft.Data.SqlClient"), $"@{name}", value) as DbParameter;
+
+ case SupportedDatabaseProviders.Oracle:
+ return Activator.CreateInstance(Type.GetType("Oracle.ManagedDataAccess.Client.OracleParameter, Oracle.ManagedDataAccess"), $":{name}", value) as DbParameter;
+
+ case SupportedDatabaseProviders.Postgres:
+ return Activator.CreateInstance(Type.GetType("Npgsql.NpgsqlParameter, Npgsql"), $"@{name}", value) as DbParameter;
+
+ default:
+ throw new ArgumentException($"Not supported database provider: {provider}");
+ }
+ }
+
+ ///
+ /// Creates and returns a new instance according to the database provider.
+ /// Check the for details on the required database client library reference.
+ /// Uses the to get the parameter name and type.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static DbParameter GetDbParameter(this SupportedDatabaseProviders provider, object dataModel, DataModelMapProperty dataModelMapProperty)
+ {
+ switch (provider)
+ {
+ case SupportedDatabaseProviders.SqlServer:
+ return Activator.CreateInstance(Type.GetType("Microsoft.Data.SqlClient.SqlParameter, Microsoft.Data.SqlClient"), $"@{dataModelMapProperty.ColumnName}", dataModelMapProperty.PropertyInfo.GetValue(dataModel) ?? DBNull.Value) as DbParameter;
+
+ case SupportedDatabaseProviders.Oracle:
+ var parameter = Activator.CreateInstance(Type.GetType("Oracle.ManagedDataAccess.Client.OracleParameter, Oracle.ManagedDataAccess")) as DbParameter;
+ parameter.ParameterName = $":{dataModelMapProperty.ColumnName}";
+ parameter.Value = dataModelMapProperty.PropertyInfo.GetValue(dataModel) ?? DBNull.Value;
+ //parameter.DbType = GetOracleDbType(dataModelMapProperty.PropertyInfo);
+ parameter.GetType().GetProperty("OracleDbType").SetValue(parameter, dataModelMapProperty.ProviderDbType);
+ return parameter;
+
+ case SupportedDatabaseProviders.Postgres:
+ return Activator.CreateInstance(Type.GetType("Npgsql.NpgsqlParameter, Npgsql"), $"@{dataModelMapProperty.ColumnName}", dataModelMapProperty.PropertyInfo.GetValue(dataModel) ?? DBNull.Value) as DbParameter;
+
+ default:
+ throw new ArgumentException($"Not supported database provider: {provider}");
+ }
}
+ ///
+ /// Gets the for a given property according to the database provider.
+ /// Check the for details on the required database client library reference.
+ ///
+ ///
+ ///
+ ///
+ ///
public static DbType GetDbType(this SupportedDatabaseProviders provider, PropertyInfo propertyInfo)
{
// TODO WIP https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/data-type-mappings-in-ado-net
@@ -79,10 +141,31 @@ public static DbType GetDbType(this SupportedDatabaseProviders provider, Propert
{
SupportedDatabaseProviders.Oracle => GetOracleDbType(propertyInfo),
SupportedDatabaseProviders.SqlServer => GetSqlServerDbType(propertyInfo),
+ SupportedDatabaseProviders.Postgres => GetPostgresDbType(propertyInfo),
_ => throw new ArgumentException($"Not supported database provider: {provider}"),
};
}
+ ///
+ /// Gets the client namespace import statement for a given database provider.
+ /// Check the for details on the required database client library reference.
+ ///
+ ///
+ ///
+ ///
+ public static string GetClientNamespace(this SupportedDatabaseProviders provider)
+ {
+ return provider switch
+ {
+ SupportedDatabaseProviders.Oracle => "using Oracle.ManagedDataAccess.Client;",
+ _ => throw new NotImplementedException($"Client namespace not implemented for {provider}"),
+ };
+ }
+
+ private static readonly Type[] dateTimeTypes = [typeof(DateTime), typeof(DateTime?)];
+
+ private static readonly Type[] integerTypes = [typeof(int), typeof(byte)];
+
private static DbType GetSqlServerDbType(PropertyInfo propertyInfo)
{
if (propertyInfo.PropertyType == typeof(decimal))
@@ -90,12 +173,17 @@ private static DbType GetSqlServerDbType(PropertyInfo propertyInfo)
return DbType.Decimal;
}
- if (new Type[]{ typeof(int), typeof(byte) }.Contains(propertyInfo.PropertyType))
+ if (integerTypes.Contains(propertyInfo.PropertyType))
{
return DbType.Int32;
}
- return DbType.String;
+ if (dateTimeTypes.Contains(propertyInfo.PropertyType))
+ {
+ return DbType.DateTime;
+ }
+
+ throw new ArgumentException($"Database type mapping not available: {propertyInfo.PropertyType.Name}");
}
private static DbType GetOracleDbType(PropertyInfo propertyInfo)
@@ -110,6 +198,41 @@ private static DbType GetOracleDbType(PropertyInfo propertyInfo)
return DbType.Decimal;
}
- return DbType.String;
+ if (integerTypes.Contains(propertyInfo.PropertyType))
+ {
+ return DbType.Int32;
+ }
+
+ if (dateTimeTypes.Contains(propertyInfo.PropertyType))
+ {
+ return DbType.DateTime;
+ }
+
+ throw new ArgumentException($"Database type mapping not available: {propertyInfo.PropertyType.Name}");
+ }
+
+ private static DbType GetPostgresDbType(PropertyInfo propertyInfo)
+ {
+ if (new Type[] { typeof(int), typeof(byte) }.Contains(propertyInfo.PropertyType))
+ {
+ return DbType.Int32;
+ }
+
+ if (propertyInfo.PropertyType == typeof(decimal))
+ {
+ return DbType.Decimal;
+ }
+
+ if (integerTypes.Contains(propertyInfo.PropertyType))
+ {
+ return DbType.Int32;
+ }
+
+ if (dateTimeTypes.Contains(propertyInfo.PropertyType))
+ {
+ return DbType.DateTime;
+ }
+
+ throw new ArgumentException($"Database type mapping not available: {propertyInfo.PropertyType.Name}");
}
}
diff --git a/src/DbBroker/Extensions/SqlRenderExtensions.cs b/src/DbBroker/Extensions/SqlRenderExtensions.cs
index 4d88423..a9f6dc2 100644
--- a/src/DbBroker/Extensions/SqlRenderExtensions.cs
+++ b/src/DbBroker/Extensions/SqlRenderExtensions.cs
@@ -5,28 +5,55 @@
namespace DbBroker.Extensions;
+///
+/// Provides extension methods for SQL rendering
+///
public static class SqlRenderExtensions
{
+ ///
+ /// Renders the WHERE clause from a list of .
+ /// Each filter is combined using AND operator.
+ /// If the list is empty or null, an empty string is returned.
+ ///
+ ///
+ ///
public static string RenderWhereClause(this IEnumerable filters)
{
if (filters is null || !filters.Any())
{
- return "1=1";
+ return string.Empty;
}
var whereClause = new StringBuilder();
foreach (var filter in filters)
{
- whereClause.AppendLine($"AND {filter.RenderSql()}");
+ var filterSql = filter.RenderSql();
+
+ if (string.IsNullOrEmpty(filterSql))
+ continue;
+
+ whereClause.AppendLine(filterSql);
}
return whereClause.ToString();
}
+ ///
+ /// Renders the SELECT columns from a list of .
+ /// If the list is empty or null, an empty string is returned.
+ ///
+ ///
+ ///
public static string RenderJoinsColumns(this IEnumerable joins)
{
return string.Empty;
}
+ ///
+ /// Renders the JOIN clauses from a list of .
+ /// If the list is empty or null, an empty string is returned.
+ ///
+ ///
+ ///
public static string RenderJoins(this IEnumerable joins)
{
StringBuilder joinsClause = new();
diff --git a/src/DbBroker/Model/CommandFilter.cs b/src/DbBroker/Model/CommandFilter.cs
index 840b438..f4ea742 100644
--- a/src/DbBroker/Model/CommandFilter.cs
+++ b/src/DbBroker/Model/CommandFilter.cs
@@ -3,20 +3,70 @@
namespace DbBroker.Model;
+///
+/// Represents a filter applied to a SQL command
+///
public class CommandFilter
{
+ ///
+ /// The property of the data model being filtered
+ ///
public DataModelMapProperty DataModelMapProperty { get; set; }
+ ///
+ /// Index of the filter in the command's filter list. -1 indicates grouped filter.
+ ///
public int Index { get; set; }
+ ///
+ /// Used to chain multiple filters together (AND, OR)
+ ///
+ public SqlOperator Operator { get; set; }
+
+ ///
+ /// The SQL expression representing the filter condition
+ ///
public SqlExpression SqlExpression { get; set; }
+ ///
+ /// The chained SQL expressions for the filter
+ ///
+ public List GroupedExpressions { get; set; } = [];
+
+ ///
+ /// Parameters associated with the filter
+ ///
public IEnumerable Parameters { get; set; }
+ ///
+ /// Alias for the data model in the SQL command
+ ///
public string Alias { get; set; }
+ ///
+ /// Renders the SQL representation of the filter
+ ///
+ ///
public string RenderSql()
{
- return SqlExpression.RenderSql(Alias, DataModelMapProperty.ColumnName, Parameters, 0);
+ var sql = SqlExpression.RenderSql(Alias, DataModelMapProperty.ColumnName, Parameters, 0);
+
+ if (GroupedExpressions.Count == 0)
+ {
+ return $"AND {sql}";
+ }
+
+ foreach (var groupedExpression in GroupedExpressions)
+ {
+ var groupedSql = groupedExpression.SqlExpression.RenderSql(Alias, groupedExpression.DataModelMapProperty.ColumnName, groupedExpression.Parameters, 0);
+
+ if (!string.IsNullOrEmpty(groupedSql))
+ {
+ sql += $" {groupedExpression.Operator} {groupedSql}";
+ }
+ }
+ sql = $"AND ({sql})";
+
+ return sql;
}
}
diff --git a/src/DbBroker/Model/DataModel.cs b/src/DbBroker/Model/DataModel.cs
index 9fcff7d..6ef58ee 100644
--- a/src/DbBroker/Model/DataModel.cs
+++ b/src/DbBroker/Model/DataModel.cs
@@ -2,15 +2,21 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Diagnostics;
using System.Linq;
using System.Reflection;
using DbBroker.Attributes;
using DbBroker.Common;
+using DbBroker.Common.Model;
using DbBroker.Common.Model.Interfaces;
using DbBroker.Model.Interfaces;
namespace DbBroker.Model;
+///
+/// Base class for Data Models
+///
+/// DBBroker generated Data Model
public abstract class DataModel : IDataModel
{
private static IEnumerable _properties;
@@ -37,41 +43,97 @@ internal DataModelMap DataModelMap
DataModelMap IDataModel.DataModelMap => DataModelMap;
- // TODO Still makes sense? loading all properties is the default behavior
- // ///
- // /// Meta column to represent '*' in SQL SELECT commands
- // ///
- // [DataModelMetaColumn]
- // public dynamic All { get; }
-
- protected Dictionary _IsNotPristine { get; set; } = [];
+ ///
+ /// Tracks which properties are not pristine (i.e. have been modified)
+ ///
+ protected Dictionary _IsNotPristine { get; private set; } = [];
+ ///
+ /// Checks if the property is not pristine (i.e. has been modified)
+ ///
+ ///
+ ///
public bool IsNotPristine(string propertyName) => _IsNotPristine.ContainsKey(propertyName);
+ ///
+ /// DbBroker will ignore pristine properties on INSERT, UPDATE, and UPSERT commands
+ ///
+ /// The property name. Prefer using nameof().
+ public void SetPristine(string propertyName)
+ {
+ if (_IsNotPristine.ContainsKey(propertyName ?? string.Empty))
+ {
+ _IsNotPristine.Remove(propertyName);
+ }
+ }
+
+ ///
+ /// DbBroker will ignore pristine properties on INSERT, UPDATE, and UPSERT commands
+ ///
+ /// The properties names to set as pristine. Prefer using nameof().
+ public void SetPristine(params string[] propertiesNames)
+ {
+ foreach (var propertyName in propertiesNames ?? [])
+ {
+ if (_IsNotPristine.ContainsKey(propertyName ?? string.Empty))
+ {
+ _IsNotPristine.Remove(propertyName);
+ }
+ }
+ }
+
+ ///
+ /// The full name of the ISqlInsertTemplate implementation to use for this DataModel.
+ /// The type must have a constructor with the following signature:
+ /// (string tableName, IEnumerable<DataModelMapProperty> mappedProperties, SupportedDatabaseProviders provider)
+ ///
protected static string SqlInsertTemplateTypeFullName;
+ ///
+ /// The arguments to pass to the ISqlInsertTemplate implementation constructor.
+ ///
protected static object[] SqlInsertTemplateTypeArguments;
+ ///
+ /// The database provider this DataModel is targeting.
+ ///
protected static SupportedDatabaseProviders Provider;
- readonly string[] _internalProperties = ["ALL", "DataModelMap"];
+ ///
+ /// Properties to ignore when building the DataModelMap
+ ///
+ readonly string[] _ignoreProperties = ["ALL", "DataModelMap"];
private DataModelMap GetDataModelMap()
{
- var sqlInsertTemplate = Activator.CreateInstance(Type.GetType(SqlInsertTemplateTypeFullName), SqlInsertTemplateTypeArguments);
+ // TODO Allow Views to INSERT, UPDATE, DELETE (using database view triggers)
+ // var sqlInsertTemplate = Activator.CreateInstance(Type.GetType(SqlInsertTemplateTypeFullName), SqlInsertTemplateTypeArguments);
DataModelMap dataModelMap = new()
{
SchemaName = typeof(T).GetCustomAttribute().Schema,
TableName = typeof(T).GetCustomAttribute().Name,
- Provider = Provider,
- SqlInsertTemplate = sqlInsertTemplate as ISqlInsertTemplate,
+ Provider = Provider
};
+ var insertTemplateType = Type.GetType(SqlInsertTemplateTypeFullName);
+
+ if (insertTemplateType is null && SqlInsertTemplateTypeFullName is not null)
+ {
+ throw new ApplicationException($"Could not find type '{SqlInsertTemplateTypeFullName}' for DataModel '{typeof(T).FullName}'.");
+ }
+
+ if (insertTemplateType is not null && this is not IViewDataModel)
+ {
+ dataModelMap.SqlInsertTemplate =
+ Activator.CreateInstance(insertTemplateType, SqlInsertTemplateTypeArguments) as ISqlInsertTemplate;
+ }
+
+
var index = 0;
foreach (var property in Properties)
{
- if (_internalProperties.Contains(property.Name))
+ if (_ignoreProperties.Contains(property.Name))
{
continue;
}
@@ -125,7 +187,8 @@ private DataModelMap GetDataModelMap()
ColumnName = property.GetCustomAttribute().Name,
IsKey = property.GetCustomAttribute() != null,
Index = index++,
- PropertyInfo = property
+ PropertyInfo = property,
+ ProviderDbType = property.GetCustomAttribute().ProviderDbType
};
dataModelMap.MappedProperties.Add(property.Name, dataModelProperty);
diff --git a/src/DbBroker/Model/DataModelHashSet.cs b/src/DbBroker/Model/DataModelHashSet.cs
index 247ae32..98b4597 100644
--- a/src/DbBroker/Model/DataModelHashSet.cs
+++ b/src/DbBroker/Model/DataModelHashSet.cs
@@ -3,12 +3,22 @@
namespace DbBroker.Model;
+///
+/// A HashSet that uses DataModelKeyComparer for equality comparison and contains a reference to the DataModel type.
+///
+///
public class DataModelHashSet : HashSet where TDataModel : IDataModel
{
+ ///
+ /// The DataModel type associated with this HashSet.
+ ///
public TDataModel DataModel { get; set; }
+ ///
+ /// Initializes a new instance of the DataModelHashSet class that is empty and uses DataModelKeyComparer for equality comparison.
+ ///
public DataModelHashSet() : base(DataModelKeyComparer.Instance)
{
-
+
}
}
diff --git a/src/DbBroker/Model/DataModelMap.cs b/src/DbBroker/Model/DataModelMap.cs
index 22de1b1..d6369a4 100644
--- a/src/DbBroker/Model/DataModelMap.cs
+++ b/src/DbBroker/Model/DataModelMap.cs
@@ -5,29 +5,53 @@
namespace DbBroker.Model;
+///
+/// Mapping details for a Data Model type.
+///
public class DataModelMap
{
+ ///
+ /// The Data Model type this mapping applies to.
+ ///
public string TableName { get; set; }
+ ///
+ /// The schema name for the table, if applicable.
+ ///
public string SchemaName { get; set; }
+ ///
+ /// The full table name including schema.
+ ///
public string TableFullName => $"{SchemaName}.{TableName}";
+ ///
+ /// The PropertyInfo for the key property of the Data Model.
+ ///
public PropertyInfo KeyProperty { get; set; }
///
/// The Database provider or vendor for this Data Model and its context.
///
public SupportedDatabaseProviders Provider { get; set; }
-
+
///
/// Key: Database colum name; Value: mapping details
///
public Dictionary MappedProperties { get; set; } = [];
+ ///
+ /// Key: Property name; Value: mapping details
+ ///
public Dictionary MappedReferences { get; set; } = [];
+ ///
+ /// Key: Property name; Value: mapping details
+ ///
public Dictionary MappedCollections { get; set; } = [];
+ ///
+ /// The SQL Insert template for this Data Model type.
+ ///
public ISqlInsertTemplate SqlInsertTemplate { get; set; }
}
diff --git a/src/DbBroker/Model/DataModelMapCollectionReference.cs b/src/DbBroker/Model/DataModelMapCollectionReference.cs
index 59a7d1a..fa4bedc 100644
--- a/src/DbBroker/Model/DataModelMapCollectionReference.cs
+++ b/src/DbBroker/Model/DataModelMapCollectionReference.cs
@@ -3,25 +3,58 @@
namespace DbBroker.Model;
+///
+/// Represents a reference from a data model to a collection of related data models.
+///
public class DataModelMapCollectionReference
{
+ ///
+ /// The schema name of the table containing the foreign key column.
+ ///
public string SchemaName { get; set; }
+ ///
+ /// The name of the table containing the foreign key column.
+ ///
public string TableName { get; set; }
+ ///
+ /// The name of the foreign key column.
+ ///
public string ColumnName { get; set; }
+ ///
+ /// The schema name of the referenced table.
+ ///
public string RefSchemaName { get; set; }
+ ///
+ /// The name of the referenced table.
+ ///
public string RefTableName { get; set; }
+ ///
+ /// The name of the primary key column in the referenced table.
+ ///
public string RefTablePrimaryKeyColumnName { get; set; }
+ ///
+ /// The name of the column in the referenced table that the foreign key points to.
+ ///
public string RefColumnName { get; set; }
+ ///
+ /// The index of the reference in the collection of references.
+ ///
public int Index { get; set; }
+ ///
+ /// The PropertyInfo of the property in the data model that holds the collection of related data models.
+ ///
public PropertyInfo PropertyInfo { get; set; }
+ ///
+ /// The type of the data model that the collection references.
+ ///
public Type DataModelCollectionType { get; set; }
}
diff --git a/src/DbBroker/Model/DataModelMapProperty.cs b/src/DbBroker/Model/DataModelMapProperty.cs
index ac13187..8ff9e10 100644
--- a/src/DbBroker/Model/DataModelMapProperty.cs
+++ b/src/DbBroker/Model/DataModelMapProperty.cs
@@ -2,15 +2,38 @@
namespace DbBroker.Model;
+///
+/// Represents a mapping between a property in a data model and a column in a database table.
+///
public class DataModelMapProperty
{
+ ///
+ /// The schema name of the table containing the column.
+ ///
public string ColumnName { get; set; }
+ ///
+ /// The name of the property in the data model.
+ ///
public string PropertyName => PropertyInfo?.Name;
+ ///
+ /// Indicates whether the column is part of the primary key.
+ ///
public bool IsKey { get; set; }
+ ///
+ /// The index of the property in the collection of properties.
+ ///
public int Index { get; set; }
+ ///
+ /// The database type of the column (e.g., SqlDbType for SQL Server).
+ ///
+ public object ProviderDbType { get; set; }
+
+ ///
+ /// The PropertyInfo of the property in the data model.
+ ///
public PropertyInfo PropertyInfo { get; set; }
}
diff --git a/src/DbBroker/Model/DataModelMapReference.cs b/src/DbBroker/Model/DataModelMapReference.cs
index f54a4e6..fdc550d 100644
--- a/src/DbBroker/Model/DataModelMapReference.cs
+++ b/src/DbBroker/Model/DataModelMapReference.cs
@@ -2,25 +2,58 @@
namespace DbBroker.Model;
+///
+/// Data model map reference.
+///
public class DataModelMapReference
{
+ ///
+ /// Referencing schema name.
+ ///
public string SchemaName { get; set; }
-
+
+ ///
+ /// Referencing table name.
+ ///
public string TableName { get; set; }
+ ///
+ /// Referencing column name.
+ ///
public string ColumnName { get; set; }
+ ///
+ /// Indicates whether the column allows nulls.
+ ///
public bool ColumnAllowNulls { get; set; }
- public string RefSchemaName { get; set;}
+ ///
+ /// Referenced schema name.
+ ///
+ public string RefSchemaName { get; set; }
+ ///
+ /// Referenced table name.
+ ///
public string RefTableName { get; set; }
+ ///
+ /// Referenced table primary key column name.
+ ///
public string RefTablePrimaryKeyColumnName { get; set; }
+ ///
+ /// Referenced column name.
+ ///
public string RefColumnName { get; set; }
+ ///
+ /// Index of the reference in the data model map's References collection.
+ ///
public int Index { get; set; }
+ ///
+ /// Property info of the referencing property.
+ ///
public PropertyInfo PropertyInfo { get; set; }
}
diff --git a/src/DbBroker/Model/Interfaces/IDataModel.cs b/src/DbBroker/Model/Interfaces/IDataModel.cs
index 6ff39d9..7c5d16f 100644
--- a/src/DbBroker/Model/Interfaces/IDataModel.cs
+++ b/src/DbBroker/Model/Interfaces/IDataModel.cs
@@ -1,10 +1,15 @@
-using System;
-using DbBroker.Model;
-
namespace DbBroker.Model.Interfaces;
+///
+/// Interface for data models to track changes and provide mapping information.
+///
public interface IDataModel
{
+ ///
+ /// Checks if the specified property has been modified since the object was created or last reset.
+ ///
+ ///
+ ///
bool IsNotPristine(string propertyName);
internal DataModelMap DataModelMap { get; }
diff --git a/src/DbBroker/Model/Interfaces/IFilteredCommand.cs b/src/DbBroker/Model/Interfaces/IFilteredCommand.cs
index a6a0df3..b05abcf 100644
--- a/src/DbBroker/Model/Interfaces/IFilteredCommand.cs
+++ b/src/DbBroker/Model/Interfaces/IFilteredCommand.cs
@@ -3,9 +3,44 @@
namespace DbBroker.Model.Interfaces;
+///
+/// Interface for commands that can have filters applied to them
+///
+///
+///
public interface IFilteredCommand where TDataModel : DataModel
{
- IFilteredCommand AddFilter(Expression> propertyLambda, SqlExpression sqlExpression);
+ ///
+ /// Add a filter to the command
+ ///
+ ///
+ ///
+ ///
+ ///
+ SqlCommand AddFilter(Expression> propertyLambda, SqlExpression sqlExpression);
- TReturn Execute();
+ ///
+ /// Group an AND expression to the last added filter
+ ///
+ ///
+ ///
+ ///
+ ///
+ SqlCommand And(Expression> propertyLambda, SqlExpression sqlExpression);
+
+ ///
+ /// Group an OR expression to the last added filter
+ ///
+ ///
+ ///
+ ///
+ ///
+ SqlCommand Or(Expression> propertyLambda, SqlExpression sqlExpression);
+
+ ///
+ /// Execute the command with the applied filters
+ ///
+ ///
+ ///
+ TReturn Execute(int commandTimeout = 0);
}
diff --git a/src/DbBroker/Model/Interfaces/ISqlExpression.cs b/src/DbBroker/Model/Interfaces/ISqlExpression.cs
index b0bb926..b6f3afd 100644
--- a/src/DbBroker/Model/Interfaces/ISqlExpression.cs
+++ b/src/DbBroker/Model/Interfaces/ISqlExpression.cs
@@ -3,7 +3,7 @@
namespace DbBroker.Model.Interfaces;
-public interface ISqlExpression
+internal interface ISqlExpression
{
SqlOperator SqlOperator { get; }
diff --git a/src/DbBroker/Model/Interfaces/IViewDataModel.cs b/src/DbBroker/Model/Interfaces/IViewDataModel.cs
new file mode 100644
index 0000000..5139df4
--- /dev/null
+++ b/src/DbBroker/Model/Interfaces/IViewDataModel.cs
@@ -0,0 +1,9 @@
+namespace DbBroker.Model.Interfaces;
+
+///
+/// Marker interface for data models that represent database views
+///
+public interface IViewDataModel
+{
+
+}
diff --git a/src/DbBroker/Model/Interfaces/IViewDataModelTuple.cs b/src/DbBroker/Model/Interfaces/IViewDataModelTuple.cs
new file mode 100644
index 0000000..5cd5fba
--- /dev/null
+++ b/src/DbBroker/Model/Interfaces/IViewDataModelTuple.cs
@@ -0,0 +1,9 @@
+namespace DbBroker.Model.Interfaces;
+
+///
+/// Marker interface for data models that represent database view tuples
+///
+public interface IViewDataModelTuple
+{
+
+}
diff --git a/src/DbBroker/Model/Providers/Oracle/OracleExplicitKeySqlInsertTemplate.cs b/src/DbBroker/Model/Providers/Oracle/OracleExplicitKeySqlInsertTemplate.cs
index c981d9d..af54a3b 100644
--- a/src/DbBroker/Model/Providers/Oracle/OracleExplicitKeySqlInsertTemplate.cs
+++ b/src/DbBroker/Model/Providers/Oracle/OracleExplicitKeySqlInsertTemplate.cs
@@ -3,24 +3,50 @@
namespace DbBroker.Model.Providers.Oracle;
+///
+/// SQL Insert template for Oracle databases when the primary key is explicitly provided
+///
public class OracleExplicitKeySqlInsertTemplate : ISqlInsertTemplate
{
+ ///
+ /// SQL Insert template string
+ ///
public string SqlTemplate =>
@$"
INSERT INTO $$TABLEFULLNAME$$($$COLUMNS$$)
VALUES ($$PARAMETERS$$)";
+ ///
+ /// Indicates whether the key column should be included in the insert statement
+ ///
public bool IncludeKeyColumn => true;
+ ///
+ /// Indicates whether the key can be retrieved after insertion
+ ///
public bool TryRetrieveKey => false;
+ ///
+ /// Parameters dictionary (empty for this template)
+ ///
public Dictionary Parameters => [];
private static OracleExplicitKeySqlInsertTemplate _instance = new();
+ ///
+ /// Singleton instance of the template
+ ///
public ISqlInsertTemplate Instance => _instance;
+ ///
+ /// Indicates whether the insert operation is compatible with upsert operations
+ ///
public bool UpsertCompatible => true;
+ ///
+ /// Replaces parameters in the SQL insert string (no-op for this template)
+ ///
+ ///
+ ///
public string ReplaceParameters(string sqlInsert) => sqlInsert;
}
diff --git a/src/DbBroker/Model/Providers/Oracle/OracleSequenceSqlInsertTemplate.cs b/src/DbBroker/Model/Providers/Oracle/OracleSequenceSqlInsertTemplate.cs
index 13033e9..6ba1b78 100644
--- a/src/DbBroker/Model/Providers/Oracle/OracleSequenceSqlInsertTemplate.cs
+++ b/src/DbBroker/Model/Providers/Oracle/OracleSequenceSqlInsertTemplate.cs
@@ -3,8 +3,14 @@
namespace DbBroker.Model.Providers.Oracle;
+///
+/// SQL Insert template for Oracle databases when using a sequence to generate primary keys
+///
public class OracleSequenceSqlInsertTemplate : ISqlInsertTemplate
{
+ ///
+ /// SQL Insert template string
+ ///
public string SqlTemplate =>
@$"BEGIN
INSERT INTO $$TABLEFULLNAME$$($$KEY_COLUMN$$, $$COLUMNS$$)
@@ -18,25 +24,55 @@ public class OracleSequenceSqlInsertTemplate : ISqlInsertTemplate
{ "SequenceName", "Name of the database sequence associated with this template and/or target table." }
};
+ ///
+ /// Indicates whether the key column should be included in the insert statement
+ ///
public bool IncludeKeyColumn => false;
+ ///
+ /// Indicates whether the key can be retrieved after insertion
+ ///
public bool TryRetrieveKey => true;
+ ///
+ /// Name of the database sequence associated with this template and/or target table.
+ ///
public string SequenceName { get; private set; }
+ ///
+ /// Parameters dictionary
+ ///
public Dictionary Parameters => _parameters;
+ ///
+ /// Singleton instance of the template
+ ///
public ISqlInsertTemplate Instance => null;
+ ///
+ /// Indicates whether the insert operation is compatible with upsert operations
+ ///
public bool UpsertCompatible => false;
+ ///
+ /// Replaces parameters in the SQL insert string
+ ///
+ ///
+ ///
public string ReplaceParameters(string sqlInsert)
{
return sqlInsert.Replace($"$$SEQUENCENAME$$", SequenceName);
}
+ ///
+ /// Constructor for the template
+ ///
public OracleSequenceSqlInsertTemplate() { }
+ ///
+ /// Constructor for the template with sequence name
+ ///
+ ///
public OracleSequenceSqlInsertTemplate(string sequenceName)
{
SequenceName = sequenceName;
diff --git a/src/DbBroker/Model/Providers/Postgres/PostgresExplicitKeySqlInsertTemplate.cs b/src/DbBroker/Model/Providers/Postgres/PostgresExplicitKeySqlInsertTemplate.cs
new file mode 100644
index 0000000..a20f442
--- /dev/null
+++ b/src/DbBroker/Model/Providers/Postgres/PostgresExplicitKeySqlInsertTemplate.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using DbBroker.Common.Model.Interfaces;
+
+namespace DbBroker.Model.Providers.Postgres;
+
+///
+/// SQL Insert template for Postgres databases when the primary key is explicitly provided
+///
+public class PostgresExplicitKeySqlInsertTemplate : ISqlInsertTemplate
+{
+ ///
+ /// SQL Insert template string
+ ///
+ public string SqlTemplate =>
+ @$"
+ INSERT INTO $$TABLEFULLNAME$$($$COLUMNS$$)
+ VALUES ($$PARAMETERS$$)";
+
+ ///
+ /// Indicates whether the key column should be included in the insert statement
+ ///
+ public bool IncludeKeyColumn => true;
+
+ ///
+ /// Indicates whether the key can be retrieved after insertion
+ ///
+ public bool TryRetrieveKey => false;
+
+ ///
+ /// Parameters dictionary (empty for this template)
+ ///
+ public Dictionary Parameters => [];
+
+ private static PostgresExplicitKeySqlInsertTemplate _instance = new();
+
+ ///
+ /// Singleton instance of the template
+ ///
+ public ISqlInsertTemplate Instance => _instance;
+
+ ///
+ /// Indicates whether the insert operation is compatible with upsert operations
+ ///
+ public bool UpsertCompatible => true;
+
+ ///
+ /// Replaces parameters in the SQL insert string (no-op for this template)
+ ///
+ ///
+ ///
+ public string ReplaceParameters(string sqlInsert) => sqlInsert;
+}
diff --git a/src/DbBroker/Model/Providers/SqlServer/SqlServerExplicitKeySqlInsertTemplate.cs b/src/DbBroker/Model/Providers/SqlServer/SqlServerExplicitKeySqlInsertTemplate.cs
index 2e571b9..695de51 100644
--- a/src/DbBroker/Model/Providers/SqlServer/SqlServerExplicitKeySqlInsertTemplate.cs
+++ b/src/DbBroker/Model/Providers/SqlServer/SqlServerExplicitKeySqlInsertTemplate.cs
@@ -3,23 +3,49 @@
namespace DbBroker.Model.Providers;
+///
+/// SQL Insert template for SQL Server databases when the primary key is explicitly provided
+///
public class SqlServerExplicitKeySqlInsertTemplate : ISqlInsertTemplate
{
+ ///
+ /// Indicates whether the key column should be included in the insert statement
+ ///
public bool IncludeKeyColumn => true;
- public string SqlTemplate =>
+ ///
+ /// SQL Insert template string
+ ///
+ public string SqlTemplate =>
@$"INSERT INTO $$TABLEFULLNAME$$($$COLUMNS$$)
VALUES ($$PARAMETERS$$);";
+ ///
+ /// Indicates whether the key can be retrieved after insertion
+ ///
public bool TryRetrieveKey => false;
+ ///
+ /// Parameters dictionary (empty for this template)
+ ///
public Dictionary Parameters => [];
private static SqlServerExplicitKeySqlInsertTemplate _instance = new();
+ ///
+ /// Singleton instance of the template
+ ///
public ISqlInsertTemplate Instance => _instance;
+ ///
+ /// Indicates whether the insert operation is compatible with upsert operations
+ ///
public bool UpsertCompatible => true;
+ ///
+ /// Replaces parameters in the SQL insert string (no-op for this template)
+ ///
+ ///
+ ///
public string ReplaceParameters(string sqlInsert) => sqlInsert;
}
diff --git a/src/DbBroker/Model/Providers/SqlServer/SqlServerIdentityKeySqlInsertTemplate.cs b/src/DbBroker/Model/Providers/SqlServer/SqlServerIdentityKeySqlInsertTemplate.cs
index 391be53..a9c8c71 100644
--- a/src/DbBroker/Model/Providers/SqlServer/SqlServerIdentityKeySqlInsertTemplate.cs
+++ b/src/DbBroker/Model/Providers/SqlServer/SqlServerIdentityKeySqlInsertTemplate.cs
@@ -3,25 +3,51 @@
namespace DbBroker.Model.Providers;
+///
+/// SQL Insert template for SQL Server databases when the primary key is an identity column
+///
public class SqlServerIdentityKeySqlInsertTemplate : ISqlInsertTemplate
{
+ ///
+ /// SQL Insert template string
+ ///
public string SqlTemplate =>
@$"INSERT INTO $$TABLEFULLNAME$$($$COLUMNS$$)
VALUES ($$PARAMETERS$$);
SELECT SCOPE_IDENTITY();";
+ ///
+ /// Indicates whether the key column should be included in the insert statement
+ ///
public bool IncludeKeyColumn => false;
+ ///
+ /// Indicates whether the key can be retrieved after insertion
+ ///
public bool TryRetrieveKey => true;
+ ///
+ /// Parameters dictionary (empty for this template)
+ ///
public Dictionary Parameters => [];
private static SqlServerIdentityKeySqlInsertTemplate _instance = new();
-
+
+ ///
+ /// Singleton instance of the template
+ ///
public ISqlInsertTemplate Instance => _instance;
+ ///
+ /// Indicates whether the insert operation is compatible with upsert operations
+ ///
public bool UpsertCompatible => false;
+ ///
+ /// Replaces parameters in the SQL insert string (no-op for this template)
+ ///
+ ///
+ ///
public string ReplaceParameters(string sqlInsert) => sqlInsert;
}
diff --git a/src/DbBroker/Model/SqlExpression.cs b/src/DbBroker/Model/SqlExpression.cs
index 761d0d1..d5bc46d 100644
--- a/src/DbBroker/Model/SqlExpression.cs
+++ b/src/DbBroker/Model/SqlExpression.cs
@@ -4,16 +4,37 @@
namespace DbBroker.Model;
+///
+/// Base class for all SQL Expressions
+///
public abstract class SqlExpression : ISqlExpression
{
+ ///
+ /// The SQL Operator for this expression
+ ///
public SqlOperator SqlOperator { get; private set; }
+ ///
+ /// The parameters for this expression
+ ///
public IEnumerable Parameters { get; protected set; } = [];
+ ///
+ /// Constructor for SqlExpression
+ ///
+ ///
public SqlExpression(SqlOperator sqlOperator)
{
SqlOperator = sqlOperator;
}
+ ///
+ /// Renders the SQL for this expression
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public abstract string RenderSql(string alias, string columnName, IEnumerable parameters, int index);
}
diff --git a/src/DbBroker/Model/SqlExpressionBetween.cs b/src/DbBroker/Model/SqlExpressionBetween.cs
new file mode 100644
index 0000000..9812798
--- /dev/null
+++ b/src/DbBroker/Model/SqlExpressionBetween.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Linq;
+using DbBroker.Model;
+
+namespace DbBroker;
+
+///
+/// SQL Expression for BETWEEN operator
+///
+public class SqlBetween : SqlExpression
+{
+ private SqlBetween(object value1, object value2) : base(SqlOperator.Between)
+ {
+ if (value1 is not null && value2 is not null)
+ {
+ Parameters = [value1, value2];
+ }
+ }
+
+ // TODO include the ability to compare with another column
+
+ ///
+ /// Column specified BETWEEN AND
+ ///
+ ///
+ ///
+ ///
+ public static SqlBetween These(object value1, object value2)
+ {
+ return new SqlBetween(value1, value2);
+ }
+
+ ///
+ /// Renders the SQL for this expression
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override string RenderSql(string alias, string columnName, IEnumerable parameters, int index)
+ {
+ if (!Parameters.Any())
+ {
+ return string.Empty;
+ }
+
+ return $"{(string.IsNullOrEmpty(alias) ? string.Empty : $"{alias}.")}{columnName} BETWEEN {parameters.First()?.ParameterName} AND {parameters.ElementAt(1)?.ParameterName}";
+ }
+}
diff --git a/src/DbBroker/Model/SqlExpressionEquals.cs b/src/DbBroker/Model/SqlExpressionEquals.cs
index 4ca904a..2f3bee3 100644
--- a/src/DbBroker/Model/SqlExpressionEquals.cs
+++ b/src/DbBroker/Model/SqlExpressionEquals.cs
@@ -1,23 +1,57 @@
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
+using DbBroker.Model;
-namespace DbBroker.Model;
+namespace DbBroker;
+///
+/// Apply a filter using the equals to expression (x = y)
+///
public class SqlEquals : SqlExpression
{
- private SqlEquals(object value) : base(SqlOperator.Equals)
+ private bool _lowerAll;
+
+ private SqlEquals(object value, bool lowerAll) : base(SqlOperator.Equals)
{
- Parameters = [value];
+ _lowerAll = lowerAll;
+
+ if (value is not null)
+ {
+ Parameters = [value];
+ }
}
- public static SqlEquals To(object value)
+ ///
+ /// Specifies the right side of the Equals Expression
+ ///
+ /// The value to compare to
+ /// If true, applies the SQL function lower() on both sides of the expression. Default value is false.
+ public static SqlEquals To(object value, bool lowerAll = false)
{
- return new SqlEquals(value);
+ return new SqlEquals(value, lowerAll);
}
+ ///
+ /// Renders the SQL for this expression
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public override string RenderSql(string alias, string columnName, IEnumerable parameters, int index)
{
+ if (!Parameters.Any())
+ {
+ return string.Empty;
+ }
+
+ if (_lowerAll)
+ {
+ return $"lower({(string.IsNullOrEmpty(alias) ? string.Empty : $"{alias}.")}{columnName}) = lower({parameters.FirstOrDefault()?.ParameterName})";
+ }
+
return $"{(string.IsNullOrEmpty(alias) ? string.Empty : $"{alias}.")}{columnName} = {parameters.FirstOrDefault()?.ParameterName}";
}
}
diff --git a/src/DbBroker/Model/SqlExpressionGreater.cs b/src/DbBroker/Model/SqlExpressionGreater.cs
new file mode 100644
index 0000000..91f9a41
--- /dev/null
+++ b/src/DbBroker/Model/SqlExpressionGreater.cs
@@ -0,0 +1,65 @@
+using DbBroker.Model;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Linq;
+
+namespace DbBroker;
+
+///
+/// SQL Expression for Greater Than and Greater Than or Equal To operators
+///
+public class SqlGreater : SqlExpression
+{
+
+ private string _operator;
+
+ private SqlGreater(SqlOperator sqlOperator, object value) : base(sqlOperator)
+ {
+ if (value is not null)
+ {
+ Parameters = [value];
+ }
+
+ _operator = sqlOperator == SqlOperator.LessThan ? ">" : ">=";
+ }
+
+ // TODO include the ability to compare with another column
+
+ ///
+ /// Specifies the right side of the Greater Than Expression (x > y)
+ ///
+ ///
+ ///
+ public static SqlGreater Than(object value)
+ {
+ return new SqlGreater(SqlOperator.GreaterThan, value);
+ }
+
+ ///
+ /// Specifies the right side of the Greater Than or Equal To Expression (x >= y)
+ ///
+ ///
+ ///
+ public static SqlGreater ThanOrEqualTo(object value)
+ {
+ return new SqlGreater(SqlOperator.GreaterThanOrEqual, value);
+ }
+
+ ///
+ /// Renders the SQL for this expression
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override string RenderSql(string alias, string columnName, IEnumerable parameters, int index)
+ {
+ if (!Parameters.Any())
+ {
+ return string.Empty;
+ }
+
+ return $"{(string.IsNullOrEmpty(alias) ? string.Empty : $"{alias}.")}{columnName} {_operator} {parameters.FirstOrDefault()?.ParameterName}";
+ }
+}
diff --git a/src/DbBroker/Model/SqlExpressionIn.cs b/src/DbBroker/Model/SqlExpressionIn.cs
new file mode 100644
index 0000000..9eb1f41
--- /dev/null
+++ b/src/DbBroker/Model/SqlExpressionIn.cs
@@ -0,0 +1,38 @@
+using DbBroker.Model;
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+
+namespace DbBroker;
+
+///
+/// SQL Expression for IN operator
+///
+public class SqlExpressionIn : SqlExpression
+{
+ private SqlExpressionIn() : base(SqlOperator.In)
+ {
+ }
+
+ ///
+ /// Specifies the IN expression
+ ///
+ ///
+ public static SqlExpressionIn In()
+ {
+ return new SqlExpressionIn();
+ }
+
+ ///
+ /// Renders the SQL for this expression
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override string RenderSql(string alias, string columnName, IEnumerable parameters, int index)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/src/DbBroker/Model/SqlExpressionIs.cs b/src/DbBroker/Model/SqlExpressionIs.cs
new file mode 100644
index 0000000..9a987a8
--- /dev/null
+++ b/src/DbBroker/Model/SqlExpressionIs.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using System.Data.Common;
+using DbBroker.Model;
+
+namespace DbBroker;
+
+///
+/// SQL Expression for IS NULL and IS NOT NULL operators
+///
+public class SqlIs : SqlExpression
+{
+ private readonly bool _isNull;
+
+ private SqlIs(SqlOperator sqlOperator, bool isNull) : base(sqlOperator)
+ {
+ _isNull = isNull;
+ Parameters = [];
+ }
+
+ ///
+ /// Specifies the IS NULL expression
+ ///
+ ///
+ public static SqlIs Null()
+ {
+ return new SqlIs(SqlOperator.Is, true);
+ }
+
+ ///
+ /// Specifies the IS NOT NULL expression
+ ///
+ ///
+ public static SqlIs NotNull()
+ {
+ return new SqlIs(SqlOperator.Is, false);
+ }
+
+ ///
+ /// Renders the SQL for this expression
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override string RenderSql(string alias, string columnName, IEnumerable parameters, int index)
+ {
+ return $"{(string.IsNullOrEmpty(alias) ? string.Empty : $"{alias}.")}{columnName} IS {(_isNull ? string.Empty : "NOT")} NULL";
+ }
+}
diff --git a/src/DbBroker/Model/SqlExpressionLess.cs b/src/DbBroker/Model/SqlExpressionLess.cs
new file mode 100644
index 0000000..5f36cfd
--- /dev/null
+++ b/src/DbBroker/Model/SqlExpressionLess.cs
@@ -0,0 +1,64 @@
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Linq;
+using DbBroker.Model;
+
+namespace DbBroker;
+
+///
+/// Represents a SQL "less than" or "less than or equal to" expression.
+///
+public class SqlLess : SqlExpression
+{
+ private string _operator;
+
+ private SqlLess(SqlOperator sqlOperator, object value) : base(sqlOperator)
+ {
+ if (value is not null)
+ {
+ Parameters = [value];
+ }
+
+ _operator = sqlOperator == SqlOperator.LessThan ? "<" : "<=";
+ }
+
+ // TODO include the ability to compare with another column
+
+ ///
+ /// Creates a "less than" SQL expression.
+ ///
+ ///
+ ///
+ public static SqlLess Than(object value)
+ {
+ return new SqlLess(SqlOperator.LessThan, value);
+ }
+
+ ///
+ /// Creates a "less than or equal to" SQL expression.
+ ///
+ ///
+ ///
+ public static SqlLess ThanOrEqualTo(object value)
+ {
+ return new SqlLess(SqlOperator.LessThanOrEqual, value);
+ }
+
+ ///
+ /// Renders the SQL representation of the expression.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override string RenderSql(string alias, string columnName, IEnumerable parameters, int index)
+ {
+ if (!Parameters.Any())
+ {
+ return string.Empty;
+ }
+
+ return $"{(string.IsNullOrEmpty(alias) ? string.Empty : $"{alias}.")}{columnName} {_operator} {parameters.FirstOrDefault()?.ParameterName}";
+ }
+}
diff --git a/src/DbBroker/Model/SqlExpressionLike.cs b/src/DbBroker/Model/SqlExpressionLike.cs
new file mode 100644
index 0000000..1626c8a
--- /dev/null
+++ b/src/DbBroker/Model/SqlExpressionLike.cs
@@ -0,0 +1,58 @@
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Linq;
+using DbBroker.Model;
+
+namespace DbBroker;
+
+///
+/// Represents a SQL "like" expression.
+///
+public class SqlLike : SqlExpression
+{
+ private readonly bool _lowerAll;
+
+ private SqlLike(object value, bool lowerAll) : base(SqlOperator.Equals)
+ {
+ _lowerAll = lowerAll;
+
+ if (value is not null)
+ {
+ Parameters = [value];
+ }
+ }
+
+ ///
+ /// Creates a "like" SQL expression.
+ ///
+ ///
+ ///
+ ///
+ public static SqlLike To(string value, bool lowerAll = true)
+ {
+ return new SqlLike(value, lowerAll);
+ }
+
+ ///
+ /// Renders the SQL representation of the expression.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override string RenderSql(string alias, string columnName, IEnumerable parameters, int index)
+ {
+ if (!Parameters.Any())
+ {
+ return string.Empty;
+ }
+
+ if (_lowerAll)
+ {
+ return $"lower({(string.IsNullOrEmpty(alias) ? string.Empty : $"{alias}.")}{columnName}) LIKE lower({parameters.FirstOrDefault()?.ParameterName})";
+ }
+
+ return $"{(string.IsNullOrEmpty(alias) ? string.Empty : $"{alias}.")}{columnName} LIKE {parameters.FirstOrDefault()?.ParameterName}";
+ }
+}
diff --git a/src/DbBroker/Model/SqlExpressionNotBetween.cs b/src/DbBroker/Model/SqlExpressionNotBetween.cs
new file mode 100644
index 0000000..8c91231
--- /dev/null
+++ b/src/DbBroker/Model/SqlExpressionNotBetween.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Linq;
+
+namespace DbBroker.Model;
+
+///
+/// Represents a SQL Expression for NOT BETWEEN operator
+///
+public class SqlExpressionNotBetween : SqlExpression
+{
+ private SqlExpressionNotBetween(object value1, object value2) : base(SqlOperator.NotBetween)
+ {
+ if (value1 is not null && value2 is not null)
+ {
+ Parameters = [value1, value2];
+ }
+ }
+
+ // TODO add the ability to compare with another column
+
+ ///
+ /// Column specified NOT BETWEEN AND
+ ///
+ ///
+ ///
+ ///
+ public static SqlExpressionNotBetween These(object value1, object value2)
+ {
+ return new SqlExpressionNotBetween(value1, value2);
+ }
+
+ ///
+ /// Renders the SQL for this expression
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override string RenderSql(string alias, string columnName, IEnumerable parameters, int index)
+ {
+ if (!Parameters.Any())
+ {
+ return string.Empty;
+ }
+ return $"{(string.IsNullOrEmpty(alias) ? string.Empty : $"{alias}.")}{columnName} NOT BETWEEN {parameters.First()?.ParameterName} AND {parameters.ElementAt(1)?.ParameterName}";
+ }
+}
diff --git a/src/DbBroker/Model/SqlExpressionNotEquals.cs b/src/DbBroker/Model/SqlExpressionNotEquals.cs
new file mode 100644
index 0000000..1c11e7b
--- /dev/null
+++ b/src/DbBroker/Model/SqlExpressionNotEquals.cs
@@ -0,0 +1,57 @@
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Linq;
+using DbBroker.Model;
+
+namespace DbBroker;
+
+///
+/// Represents a SQL "not equals" expression.
+///
+public class SqlNotEquals : SqlExpression
+{
+ private bool _lowerAll;
+
+ private SqlNotEquals(object value, bool lowerAll) : base(SqlOperator.Equals)
+ {
+ _lowerAll = lowerAll;
+
+ if (value is not null)
+ {
+ Parameters = [value];
+ }
+ }
+
+ ///
+ /// Specifies the right side of the Not Equals Expression
+ ///
+ /// The value to compare to
+ /// If true, applies the SQL function lower() on both sides of the expression. Default value is false.
+ public static SqlNotEquals To(object value, bool lowerAll = false)
+ {
+ return new SqlNotEquals(value, lowerAll);
+ }
+
+ ///
+ /// Renders the SQL representation of the expression.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override string RenderSql(string alias, string columnName, IEnumerable parameters, int index)
+ {
+ if (!Parameters.Any())
+ {
+ return string.Empty;
+ }
+
+ if (_lowerAll)
+ {
+ return $"lower({(string.IsNullOrEmpty(alias) ? string.Empty : $"{alias}.")}{columnName}) <> lower({parameters.FirstOrDefault()?.ParameterName})";
+ }
+
+ return $"{(string.IsNullOrEmpty(alias) ? string.Empty : $"{alias}.")}{columnName} <> {parameters.FirstOrDefault()?.ParameterName}";
+ }
+}
diff --git a/src/DbBroker/Model/SqlJoin.cs b/src/DbBroker/Model/SqlJoin.cs
index 4cb77c7..9e3f8f6 100644
--- a/src/DbBroker/Model/SqlJoin.cs
+++ b/src/DbBroker/Model/SqlJoin.cs
@@ -1,46 +1,107 @@
+using DbBroker.Model;
using System;
using System.Collections.Generic;
using System.Reflection;
-namespace DbBroker.Model;
+namespace DbBroker;
+///
+/// Represents a SQL Join between two tables
+///
public class SqlJoin
{
+ ///
+ /// The depth of the join in the overall query
+ ///
public int Depth { get; set; }
+ ///
+ /// The index of the join at this depth
+ ///
public int Index { get; set; }
+ ///
+ /// The path to this join from the root data model
+ ///
public string Path { get; set; }
+ ///
+ /// The DataModelMap for the referenced table
+ ///
public DataModelMap DataModelMap { get; set; }
+ ///
+ /// The schema name of the referenced table
+ ///
public string SchemaName { get; set; }
+ ///
+ /// The parent table name
+ ///
public string ParentTableName { get; set; }
+ ///
+ /// The primary key column name of the parent table
+ ///
public string ParentTablePrimaryKeyColumnName { get; set; }
+ ///
+ /// The alias name of the parent table
+ ///
public string ParentTableAliasName { get; set; }
+ ///
+ /// The parent column name that references the foreign key in the referenced table
+ ///
public string ParentColumnName { get; set; }
+ ///
+ /// The column name in the referenced table that is referenced by the foreign key in the parent table
+ ///
public bool ColumnAllowNulls { get; set; }
+ ///
+ /// The referenced table name
+ ///
public string RefTableName { get; set; }
+ ///
+ /// The referenced column name that is referenced by the foreign key in the parent table
+ ///
public string RefColumnName { get; set; }
+ ///
+ /// The alias name of the referenced table
+ ///
public string RefTableNameAlias => $"d{Depth}_j{Index}";
+ ///
+ /// The PropertyInfo of the property in the data model that represents this join
+ ///
public PropertyInfo RefPropertyInfo { get; set; }
+ ///
+ /// The type of the property in the data model that represents this join
+ ///
public Type RefPropertyCollectionType { get; set; }
+ ///
+ /// The transient reference object for this join
+ ///
public object TransientRef { get; set; }
+ ///
+ /// A buffer to hold already mapped objects to avoid circular references and duplicate objects
+ ///
public Dictionary MapBuffer { get; set; } = new Dictionary(comparer: new DataModelKeyComparer());
+ ///
+ /// Indicates if the property in the data model that represents this join is a collection
+ ///
public bool IsCollection => RefPropertyCollectionType is not null;
+ ///
+ /// Indicates if the parent property in the data model that represents this join is a collection
+ ///
public bool ParentIsCollection { get; set; }
}
diff --git a/src/DbBroker/Model/SqlOperator.cs b/src/DbBroker/Model/SqlOperator.cs
index 7c33a06..4374ade 100644
--- a/src/DbBroker/Model/SqlOperator.cs
+++ b/src/DbBroker/Model/SqlOperator.cs
@@ -1,11 +1,72 @@
namespace DbBroker.Model;
+///
+/// Enumeration of SQL Operators
+///
public enum SqlOperator
{
+ ///
+ /// AND SQL operator
+ ///
+ AND,
+
+ ///
+ /// OR SQL operator
+ ///
+ OR,
+
+ ///
+ /// Equals SQL operator
+ ///
Equals,
+
+ ///
+ /// Not equals SQL operator
+ ///
+ NotEquals,
+
+ ///
+ /// Less than or less than or equal to SQL operator
+ ///
LessThan,
+
+ ///
+ /// Less than or equal to SQL operator
+ ///
LessThanOrEqual,
+
+ ///
+ /// Greater than or greater than or equal to SQL operator
+ ///
GreaterThan,
+
+ ///
+ /// Greater than or equal to SQL operator
+ ///
GreaterThanOrEqual,
- Between
+
+ ///
+ /// Between SQL operator
+ ///
+ Between,
+
+ ///
+ /// Not between SQL operator
+ ///
+ NotBetween,
+
+ ///
+ /// Is SQL operator
+ ///
+ Is,
+
+ ///
+ /// In SQL operator
+ ///
+ In,
+
+ ///
+ /// Like SQL operator
+ ///
+ Like
}
diff --git a/src/DbBroker/Model/SqlOrderBy.cs b/src/DbBroker/Model/SqlOrderBy.cs
index b1a6c25..2e95a5f 100644
--- a/src/DbBroker/Model/SqlOrderBy.cs
+++ b/src/DbBroker/Model/SqlOrderBy.cs
@@ -1,24 +1,39 @@
using System;
using System.Linq.Expressions;
-namespace DbBroker.Model;
+namespace DbBroker;
+///
+/// Represents an ORDER BY clause in SQL.
+///
+///
public class SqlOrderBy
{
private SqlOrderBy() { }
- internal Expression> Expression {get; private set;}
+ internal Expression> Expression { get; private set; }
- internal bool Asc {get; private set;}
+ internal bool Asc { get; private set; }
+ ///
+ /// Creates an ascending order by clause for the specified property.
+ ///
+ ///
+ ///
public static SqlOrderBy Ascending(Expression> property)
{
- return new SqlOrderBy(){
+ return new SqlOrderBy()
+ {
Expression = property,
Asc = true
};
}
+ ///
+ /// Creates a descending order by clause for the specified property.
+ ///
+ ///
+ ///
public static SqlOrderBy Descending(Expression> property)
{
return new SqlOrderBy()
diff --git a/src/DbBroker/Model/ViewDataModel.cs b/src/DbBroker/Model/ViewDataModel.cs
new file mode 100644
index 0000000..054772b
--- /dev/null
+++ b/src/DbBroker/Model/ViewDataModel.cs
@@ -0,0 +1,14 @@
+using DbBroker.Common.Model;
+using DbBroker.Model;
+using DbBroker.Model.Interfaces;
+
+namespace DbBroker;
+
+///
+/// Base class for view data models.
+///
+/// DBBroker generated Data Model
+public abstract class ViewDataModel : DataModel where T : class, IViewDataModel
+{
+ internal static DbBrokerConfigContextView DbBrokerConfigContextView { get; set; }
+}
diff --git a/src/DbBroker/Model/ViewSplitOnItem.cs b/src/DbBroker/Model/ViewSplitOnItem.cs
new file mode 100644
index 0000000..18ae027
--- /dev/null
+++ b/src/DbBroker/Model/ViewSplitOnItem.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace DbBroker.Model;
+
+internal class ViewSplitOnItem
+{
+ public Type Type { get; set; }
+
+ public string SplitOnColumn { get; set; }
+
+ public bool IsCollection { get; set; }
+
+ public int Index { get; set; }
+
+ public PropertyInfo PropertyInfo { get; set; }
+
+ public Dictionary MapBuffer { get; set; } = new Dictionary(comparer: new DataModelKeyComparer());
+
+ public object TransientRef { get; set; }
+}
diff --git a/src/DbBroker/PropertyPathHelper.cs b/src/DbBroker/PropertyPathHelper.cs
index b184637..50e3de6 100644
--- a/src/DbBroker/PropertyPathHelper.cs
+++ b/src/DbBroker/PropertyPathHelper.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq.Expressions;
+using DbBroker.Model;
namespace DbBroker;
@@ -14,7 +15,7 @@ public static string GetPropertyPath(Expression
return member.Member.Name;
}
- public static string GetNestedPropertyPath(Expression> propertyLambda)
+ public static string GetNestedPropertyPath(Expression> propertyLambda)
{
if (propertyLambda.Body is not MemberExpression member)
{
@@ -30,4 +31,9 @@ public static string GetNestedPropertyPath(Expression(Expression> propertyLambda) where TProperty : class
+ {
+ return propertyLambda.Body is UnaryExpression;
+ }
}
diff --git a/src/DbBroker/README.md b/src/DbBroker/README.md
index 34778b3..8a12b48 100644
--- a/src/DbBroker/README.md
+++ b/src/DbBroker/README.md
@@ -1,55 +1,158 @@
-# DBBroker
+# DBBroker 3.x
-A lightweight and simple to use .NET library for manipulating database records.
+
-## Quick Start ⚡
+A lightweight and easy to use .NET tool and library for effortless database records manipulation.
-Install [DBBroker.Cli](https://www.nuget.org/packages/DBBroker.Cli) package to generate the Data Models.
+## Benefits
+
+- Automatically generated Data Models
+- Zero-SQL
+- Compile time database compatibility and change check
+
+## NuGet
+
+| Package | Latest | |
+|----|----|----|
+| [DBBroker](https://www.nuget.org/packages/DBBroker) |  | [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0#select-net-standard-version) |
+| [DBBroker.Cli](https://www.nuget.org/packages/DBBroker.Cli) |  | |
+
+> [!WARNING]
+> Version 3.x has no backward compatibility with DBBroker 1.x and 2.x
+
+## How it works?
+
+DBBroker approach is powerful and different from other ORMs because it associates a [.NET CLI tool](https://nuget.org/dbbroker.cli) to automatically generate **Data Models** and a [library](https://nuget.org/dbbroker) that uses those Data Models at runtime to generate SQL and manipulate database records.
+
+## Philosophy
+
+DBBroker offers features that benefit any kind of application, from the simplest to the most complex, particularly in security-restricted scenarios. Here are some key requirements it addresses:
+
+- Applications should **NOT** be responsible for executing [DDL](https://en.wikipedia.org/wiki/Data_definition_language).
+
+- Applications' Data Access Objects should act as **clients** to the database, not the other way around.
+
+- [Schema Migrations](https://en.wikipedia.org/wiki/Schema_migration) are not an option due to a development process led by **Database Administrators**.
+
+These are common requirements for many organizations or database-centered solutions, and, arguably, by everyone who likes to keep things simple.
+
+## Quick Start⚡
+
+Open your terminal and navigate to your application's *.csproj directory. Then follow these steps:
+
+**Step 1:** Install `DBBroker.Cli` .NET tool globally.
```bash
-dotnet install -g DBBroker.Cli
+dotnet tool install DBBroker.Cli --global
```
-On the root of your .NET project initialize the `dbbroker.config.json`.
+**Step 2:** Add `DBBroker` package to your project.
```bash
-dbbroker init --namespace "EShop.DataModels" --connectionString "" --provider "SqlServer"
+dotnet nuget add DBBroker
```
-Generate/synchronize your Data Models.
+**Step 3:** Create a `dbbroker.config.json` file at the root of your project.
+
+```bash
+dbbroker init --namespace="MyApp.DataModels" --connection-string "" --provider Oracle
+```
+
+**Step 4:** Synchronize your project with your database schemas to generate the Data Models.
```bash
dbbroker sync
```
-Start using!
+## Examples
+
+Entity persistence.
```C#
-CustomersDataModel customer = new()
+// Data Models classes are generated by DBBroker (dbbroker sync)
+var customer = new CustomersDataModel();
+customer.Name = "John Three Sixteen";
+customer.Birthday = new DateTime(1980, 3, 16);
+
+var id = await dbConnection.InsertAsync(customer);
+```
+
+Entity persistence with transactions.
+
+```C#
+using var dbConnection = new SqlConnection();
+dbConnection.Open();
+var transaction = dbConnection.GetTransaction();
+
+try
+{
+ var customer = new CustomersDataModel();
+ customer.Id = Guid.NewGuid();
+ customer.Name = "John Three Sixteen";
+ customer.Birthday = new DateTime(1980, 3, 16);
+
+ await dbConnection.InsertAsync(customer, transaction);
+
+ var car = new CarsDataModel();
+ car.Model = "Renault Twingo";
+ car.Year = 2001;
+ car.CustomerId = customer.Id;
+
+ await dbConnection.InsertAsync(car, transaction);
+ transaction.Commit();
+}
+catch(Exception ex)
{
- Id = Guid.NewGuid(),
- Name = "John Three Sixteen",
- CreatedAt = DateTime.Now
-};
+ transaction.Rollback();
+}
+```
-using var connection = DbBroker.GetDbConnection();
-connection.Insert(customer);
+Retrieving a record.
+
+```C#
+var result = await dbConnection
+ .Select()
+ .AddFilter(x => x.Id, SqlEquals.To("1e6fc0e6-1fe2-49c0-ba37-ec14bf8eddc4"))
+ .ExecuteAsync();
+
+var customer = result.FirstOrDefault();
+```
+
+Retrieving multiple and filtered records.
+
+```C#
+var inactiveCustomers = await dbConnection.Select()
+ .AddFilter(x => x.StatusId, SqlEquals.To(3))
+ .ExecuteAsync();
```
-For more examples go [here](https://github.com/diegosiao/DBBroker).
+Retrieving multiple records loading only specified columns.
+
+```C#
+var inactiveCustomers = await dbConnection
+ .Select([
+ x => x.Id,
+ x => x.Name,
+ x => x.Birthday
+ ])
+ .OrderBy(x => x.Name)
+ .AddFilter(x => x.StatusId, SqlEquals.To(3))
+ .ExecuteAsync();
+```
## Supported Databases
-| Database | Status |
-|----------|--------|
-| SQL Server | ✅ |
-| Oracle | 🛠️(WIP) |
-| Postgres | ❌ |
-| MySQL | ❌ |
-| MariaDB | ❌ |
+| Database | Status | --provider |
+|----------|--------|--------|
+| SQL Server | ✅ | SqlServer |
+| Oracle | ✅ | Oracle |
+| Postgres | ⚒️ | Postgres |
+| MySQL | 🛣️ | MySql |
## Contribute
-Is there a Database provider you want to see supported? Contributors are welcome!
+All contributions are appreciated, whether they're bug reports, feature suggestions, or pull requests. Thank you for your interest and support in improving this project!
+
+Financial support is also welcome, whether large or small contributions will help to keep this project moving and always secure.
[](https://www.buymeacoffee.com/diegosiao)
diff --git a/src/DbBroker/SqlCommand.cs b/src/DbBroker/SqlCommand.cs
index bb24e6c..24df09a 100644
--- a/src/DbBroker/SqlCommand.cs
+++ b/src/DbBroker/SqlCommand.cs
@@ -2,30 +2,74 @@
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
+using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
+using System.Threading.Tasks;
using DbBroker.Extensions;
using DbBroker.Model;
using DbBroker.Model.Interfaces;
namespace DbBroker;
+///
+/// Base class for all SQL Commands
+///
+///
+///
public abstract class SqlCommand : IFilteredCommand where TDataModel : DataModel
{
+ ///
+ /// List of filters to apply to the SQL command
+ ///
protected List Filters { get; set; } = [];
+ private int paramIndex = 0;
+
+ ///
+ /// The DataModel instance
+ ///
protected readonly TDataModel DataModel;
+ ///
+ /// The list of columns to be used in the SQL command
+ ///
protected readonly IEnumerable Columns;
+ ///
+ /// The list of parameters to be used in the SQL command
+ ///
protected readonly IEnumerable Parameters;
+ ///
+ /// The database connection
+ ///
protected readonly DbConnection Connection;
+ ///
+ /// The database transaction
+ ///
protected readonly DbTransaction Transaction;
+ ///
+ /// The SQL template to be used in the command
+ ///
protected string SqlTemplate { get; set; }
+ ///
+ /// Indicates if the command requires at least one filter to be executed
+ ///
+ protected bool RequireFilter { get; set; }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public SqlCommand(TDataModel dataModel,
IEnumerable columns,
IEnumerable parameters,
@@ -40,36 +84,127 @@ public SqlCommand(TDataModel dataModel,
Transaction = transaction;
SqlTemplate = sqlTemplate;
}
-
- int paramIndex = 0;
- public IFilteredCommand AddFilter(Expression> propertyLambda, SqlExpression sqlExpression)
+
+ ///
+ /// Add a filter to the SQL command
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SqlCommand AddFilter(
+ Expression> propertyLambda,
+ SqlExpression sqlExpression)
{
var propertyName = ((MemberExpression)propertyLambda.Body).Member.Name;
- Filters.Add(new CommandFilter
+ var commandFilter = new CommandFilter
{
DataModelMapProperty = DataModel.DataModelMap.MappedProperties[propertyName],
SqlExpression = sqlExpression,
+ // ChainedExpressions = chainedExpressions,
Index = Filters.Count,
Parameters = sqlExpression
.Parameters
.Select(x => DataModel.DataModelMap.Provider.GetDbParameter($"{propertyName}{Filters.Count}_f{paramIndex++}", x))
.ToArray()
- });
+ };
+
+ Filters.Add(commandFilter);
return this;
}
- protected virtual string RenderSqlCommand()
+ ///
+ /// Group an additional filter with AND operator to the last added filter
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SqlCommand And(Expression> propertyLambda, SqlExpression sqlExpression)
+ {
+ var propertyName = ((MemberExpression)propertyLambda.Body).Member.Name;
+
+ var commandFilter = new CommandFilter
+ {
+ DataModelMapProperty = DataModel.DataModelMap.MappedProperties[propertyName],
+ SqlExpression = sqlExpression,
+ Index = -1,
+ Operator = SqlOperator.AND,
+ Parameters = sqlExpression
+ .Parameters
+ .Select(x => DataModel.DataModelMap.Provider.GetDbParameter($"{propertyName}{Filters.Count}_f{paramIndex++}", x))
+ .ToArray()
+ };
+
+ var lastFilter = Filters.LastOrDefault();
+
+ if (lastFilter == null)
+ {
+ throw new InvalidOperationException("Cannot apply AND operator without a previous filter. ");
+ }
+
+ lastFilter.GroupedExpressions.Add(commandFilter);
+
+ return this;
+ }
+
+ ///
+ /// Group an additional filter with OR operator to the last added filter
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SqlCommand Or(Expression> propertyLambda, SqlExpression sqlExpression)
+ {
+ var propertyName = ((MemberExpression)propertyLambda.Body).Member.Name;
+
+ var commandFilter = new CommandFilter
+ {
+ DataModelMapProperty = DataModel.DataModelMap.MappedProperties[propertyName],
+ SqlExpression = sqlExpression,
+ Index = -1,
+ Operator = SqlOperator.OR,
+ Parameters = sqlExpression
+ .Parameters
+ .Select(x => DataModel.DataModelMap.Provider.GetDbParameter($"{propertyName}{Filters.Count}_f{paramIndex++}", x))
+ .ToArray()
+ };
+
+ var lastFilter = Filters.LastOrDefault();
+
+ if (lastFilter == null)
+ {
+ throw new InvalidOperationException("Cannot apply AND operator without a previous filter. ");
+ }
+
+ lastFilter.GroupedExpressions.Add(commandFilter);
+
+ return this;
+ }
+
+ ///
+ /// Renders the SQL command text
+ ///
+ internal protected virtual string RenderSqlCommand()
{
return SqlTemplate
- .Replace("$$TABLEFULLNAME$$", DataModel.DataModelMap.TableFullName)
.Replace("$$COLUMNS$$", string.Join(",", Columns.Select(x => $"{x.ColumnName}={Parameters.First(p => p.ParameterName[1..].Equals(x.ColumnName)).ParameterName}")))
+ .Replace("$$TABLEFULLNAME$$", DataModel.DataModelMap.TableFullName)
.Replace("$$FILTERS$$", Filters.RenderWhereClause()); // TODO: Expose a configuration flag to avoid not filtered UPDATE or DELETE by default
}
- public virtual TReturn Execute()
+ private DbCommand PrepareCommand(int commandTimeout = 0)
{
+ if (RequireFilter && Filters.Count == 0)
+ {
+ throw new InvalidOperationException("This operation is potentially destructive and requires a filter. ");
+ }
+
if (Connection.State != ConnectionState.Open)
{
Connection.Open();
@@ -86,8 +221,34 @@ public virtual TReturn Execute()
command.Parameters.AddRange(Parameters.ToArray());
command.Parameters.AddRange(filterParameters.ToArray());
command.Transaction = Transaction;
- // command.CommandTimeout
+ command.CommandTimeout = commandTimeout;
+
+ Debug.WriteLine(command.CommandText);
+ return command;
+ }
+
+ ///
+ /// Executes the SQL command
+ ///
+ /// The time in seconds to wait the execution
+ ///
+ ///
+ public virtual TReturn Execute(int commandTimeout = 0)
+ {
+ var command = PrepareCommand(commandTimeout);
return (TReturn)(object)command.ExecuteNonQuery();
}
+
+ ///
+ /// Executes the SQL command asynchronously
+ ///
+ /// The time in seconds to wait the execution
+ ///
+ ///
+ public virtual async Task ExecuteAsync(int commandTimeout = 0)
+ {
+ var command = PrepareCommand(commandTimeout);
+ return (TReturn)(object) await command.ExecuteNonQueryAsync();
+ }
}
diff --git a/src/DbBroker/SqlDeleteCommand.cs b/src/DbBroker/SqlDeleteCommand.cs
index 9b3134b..93755ef 100644
--- a/src/DbBroker/SqlDeleteCommand.cs
+++ b/src/DbBroker/SqlDeleteCommand.cs
@@ -1,14 +1,20 @@
-using System.Collections.Generic;
using System.Data.Common;
using DbBroker.Model;
namespace DbBroker;
+///
+/// SQL DELETE command
+///
+///
public class SqlDeleteCommand : SqlCommand where TDataModel : DataModel
{
internal SqlDeleteCommand(
- TDataModel dataModel,
- DbConnection connection,
- DbTransaction transaction) :
- base(dataModel, [], [], connection, transaction, Constants.SqlDeleteTemplate) {}
+ TDataModel dataModel,
+ DbConnection connection,
+ DbTransaction transaction) :
+ base(dataModel, [], [], connection, transaction, Constants.SqlDeleteTemplate)
+ {
+ RequireFilter = true;
+ }
}
diff --git a/src/DbBroker/SqlInsertCommand.cs b/src/DbBroker/SqlInsertCommand.cs
index b2a6413..23e1a46 100644
--- a/src/DbBroker/SqlInsertCommand.cs
+++ b/src/DbBroker/SqlInsertCommand.cs
@@ -1,8 +1,126 @@
+using DbBroker.Extensions;
+using DbBroker.Model;
using System;
+using System.Data;
+using System.Data.Common;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
namespace DbBroker;
-public class SqlInsertCommand
+///
+/// SQL INSERT command
+///
+///
+public class SqlInsertCommand where TDataModel : DataModel
{
- // TODO Refactor to bring logic from DbBroker class to here
+ private TDataModel DataModel { get; set; }
+
+ private DbConnection Connection { get; set; }
+
+ private DbTransaction Transaction { get; set; }
+
+ internal SqlInsertCommand(
+ TDataModel dataModel,
+ DbConnection connection,
+ DbTransaction transaction)
+ {
+ DataModel = dataModel;
+ Connection = connection;
+ Transaction = transaction;
+ }
+
+ ///
+ /// Executes the SQL command asynchronously
+ ///
+ /// The time in seconds to wait the execution
+ ///
+ ///
+ public async Task ExecuteAsync(int commandTimeout = 0)
+ {
+ return await Task.Run(() => Execute(commandTimeout));
+ }
+
+ ///
+ /// Executes the INSERT command and returns the number of affected rows for consistency.
+ ///
+ ///
+ ///
+ ///
+ public int Execute(int commandTimeout = 0)
+ {
+ try
+ {
+ var insertColumns = DataModel
+ .DataModelMap
+ .MappedProperties
+ .Where(x => DataModel.IsNotPristine(x.Value.PropertyName) && !x.Value.IsKey || (DataModel.DataModelMap.SqlInsertTemplate.IncludeKeyColumn && x.Value.IsKey))
+ .Select(x => x.Value);
+
+ var parameters = insertColumns
+ .Select(x => DataModel.DataModelMap.Provider.GetDbParameter(DataModel, x))
+ .ToList();
+
+ var sqlInsert = DataModel.DataModelMap.SqlInsertTemplate.SqlTemplate
+ .Replace("$$TABLEFULLNAME$$", DataModel.DataModelMap.TableFullName)
+ .Replace("$$COLUMNS$$", string.Join(",", insertColumns.Select(x => x.ColumnName)))
+ .Replace("$$PARAMETERS$$", string.Join(",", parameters.Select(x => x.ParameterName)))
+ // Not required
+ .Replace(
+ "$$KEY_COLUMN$$",
+ DataModel
+ .DataModelMap
+ .MappedProperties
+ .FirstOrDefault(x => x.Value.IsKey).Value?.ColumnName);
+
+ if (Connection.State != ConnectionState.Open)
+ {
+ Connection.Open();
+ }
+
+ DbParameter keyParameter = null;
+ if (DataModel.DataModelMap.SqlInsertTemplate.TryRetrieveKey)
+ {
+ keyParameter = DataModel.DataModelMap.Provider.GetDbParameter("pKey", DBNull.Value);
+
+ // TODO Improve the key data type handling: determine based on the mapped property type
+ keyParameter.DbType = DataModel.DataModelMap.Provider.GetDbType(DataModel.DataModelMap.KeyProperty);
+ keyParameter.Direction = ParameterDirection.Output;
+ parameters.Add(keyParameter);
+ }
+
+ var command = Connection.CreateCommand();
+ command.CommandText = DataModel.DataModelMap.SqlInsertTemplate.ReplaceParameters(sqlInsert);
+ command.Parameters.AddRange(parameters.ToArray());
+ command.Transaction = Transaction;
+ command.CommandTimeout = commandTimeout;
+
+ Debug.WriteLine(command.CommandText);
+
+ if (DataModel.DataModelMap.SqlInsertTemplate.TryRetrieveKey)
+ {
+ var rowsAffected = command.ExecuteNonQuery();
+ DataModel
+ .DataModelMap
+ .MappedProperties
+ .FirstOrDefault(x => x.Value.IsKey).Value.PropertyInfo.SetValue(DataModel, keyParameter.Value);
+ return rowsAffected;
+ }
+
+ return command.ExecuteNonQuery();
+ }
+ catch (TargetInvocationException ex)
+ {
+ // TODO Adopt this error handling on all other methods
+ Debug.WriteLine(ex.Message);
+ throw new ApplicationException(ex.InnerException?.Message ?? ex.Message, ex?.InnerException ?? ex);
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine(ex.Message);
+ throw new ApplicationException(ex.Message, ex);
+ }
+ }
}
diff --git a/src/DbBroker/SqlSelectAggregateCommand.cs b/src/DbBroker/SqlSelectAggregateCommand.cs
new file mode 100644
index 0000000..1add57e
--- /dev/null
+++ b/src/DbBroker/SqlSelectAggregateCommand.cs
@@ -0,0 +1,91 @@
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Common;
+using System.Diagnostics;
+using System.Linq;
+using DbBroker.Extensions;
+using DbBroker.Model;
+
+namespace DbBroker;
+
+///
+/// SQL AGGREGATE command
+///
+///
+///
+public abstract class SqlSelectAggregateCommand : SqlCommand where TDataModel : DataModel
+{
+ ///
+ /// Constructor for SQL AGGREGATE command
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SqlSelectAggregateCommand(
+ TDataModel dataModel,
+ DataModelMapProperty column,
+ DbConnection connection,
+ DbTransaction transaction,
+ string sqlTemplate) : base(dataModel, [column], [], connection, transaction, sqlTemplate)
+ {
+ }
+
+ ///
+ /// Renders the SQL command
+ ///
+ ///
+ internal protected override string RenderSqlCommand()
+ {
+ return SqlTemplate
+ .Replace("$$TABLEFULLNAME$$", DataModel.DataModelMap.TableFullName)
+ .Replace("$$COLUMNS$$", Columns.First().ColumnName)
+ .Replace("$$FILTERS$$", Filters.RenderWhereClause()); // TODO: Expose a configuration flag to avoid not filtered UPDATE or DELETE by default
+ }
+
+ ///
+ /// Executes the AGGREGATE command and returns the result
+ ///
+ ///
+ ///
+ public override TResult Execute(int commandTimeout = 0)
+ {
+ try
+ {
+ if (Connection.State != ConnectionState.Open)
+ {
+ Connection.Open();
+ }
+
+ List filterParameters = [];
+ foreach (var filter in Filters)
+ {
+ filterParameters.AddRange(filter.Parameters);
+ }
+
+ var command = Connection.CreateCommand();
+ command.CommandText = RenderSqlCommand();
+ command.Parameters.AddRange(Parameters.ToArray());
+ command.Parameters.AddRange(filterParameters.ToArray());
+ command.Transaction = Transaction;
+ command.CommandTimeout = commandTimeout;
+
+ Debug.WriteLine(command.CommandText);
+
+ var result = command.ExecuteScalar();
+
+ if (result is null)
+ {
+ return default;
+ }
+
+ return (TResult)command.ExecuteScalar();
+ }
+ catch
+ {
+ Debug.WriteLine(RenderSqlCommand());
+ throw;
+ }
+ }
+}
diff --git a/src/DbBroker/SqlSelectAvgCommand.cs b/src/DbBroker/SqlSelectAvgCommand.cs
new file mode 100644
index 0000000..36d7f36
--- /dev/null
+++ b/src/DbBroker/SqlSelectAvgCommand.cs
@@ -0,0 +1,27 @@
+using System.Data.Common;
+using DbBroker.Model;
+
+namespace DbBroker;
+
+///
+/// SQL AVG command
+///
+///
+///
+public class SqlSelectAvgCommand : SqlSelectAggregateCommand where TDataModel : DataModel
+{
+ ///
+ /// Constructor for SQL AVG command
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SqlSelectAvgCommand(
+ TDataModel dataModel,
+ DataModelMapProperty column,
+ DbConnection connection,
+ DbTransaction transaction) : base(dataModel, column, connection, transaction, Constants.SqlSelectAvgTemplate)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/DbBroker/SqlSelectByKeyCommand.cs b/src/DbBroker/SqlSelectByKeyCommand.cs
new file mode 100644
index 0000000..a8577e6
--- /dev/null
+++ b/src/DbBroker/SqlSelectByKeyCommand.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using DbBroker.Model;
+
+namespace DbBroker;
+
+///
+/// SQL SELECT BY KEY command
+///
+///
+public class SqlSelectByKeyCommand : SqlCommand where TDataModel : DataModel
+{
+ ///
+ /// Constructor for SQL SELECT BY KEY command
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SqlSelectByKeyCommand(
+ TDataModel dataModel,
+ DbConnection connection,
+ IEnumerable columns,
+ DbParameter primaryKey,
+ int depth = 0,
+ DbTransaction transaction = null) : base(dataModel, columns, [primaryKey], connection, transaction, Constants.SqlSelectCountTemplate)
+ {
+ }
+
+ ///
+ /// Executes the SELECT BY KEY command and returns the result
+ ///
+ ///
+ ///
+ ///
+ public override TDataModel Execute(int commandTimeout = 0)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/src/DbBroker/SqlSelectCommand.cs b/src/DbBroker/SqlSelectCommand.cs
index 9609160..2774454 100644
--- a/src/DbBroker/SqlSelectCommand.cs
+++ b/src/DbBroker/SqlSelectCommand.cs
@@ -10,56 +10,121 @@
using System.Diagnostics;
using System.Linq.Expressions;
using System.Collections;
+using System.Threading.Tasks;
namespace DbBroker;
-public class SqlSelectCommand : SqlCommand> where TDataModel : DataModel
+///
+/// Abstraction for SQL SELECT command
+///
+///
+public sealed class SqlSelectCommand : SqlCommand> where TDataModel : DataModel
{
private readonly int _maxDepth;
- private readonly int _skip;
+ private uint _skip;
- private readonly int _take;
+ private uint _take;
- private string _offsetFetchSql => _take > 0 ? $"OFFSET {_skip} ROWS FETCH NEXT {_take} ROWS ONLY" : string.Empty;
+ private string _offsetFetchSql => _skip + _take > 0 ?
+ $"{(_skip > 0 ? $"OFFSET {_skip} ROWS " : string.Empty)} {(_take > 0 ? $"FETCH NEXT {_take} ROWS ONLY" : string.Empty)}" : string.Empty;
private List _joins = [];
+ private List _viewSplits = [];
+
private readonly IEnumerable>> _load = [];
private readonly IEnumerable>> _ignore = [];
private readonly List> _orderBy = [];
- internal
- SqlSelectCommand(
+ private Dictionary _dataModelResultDictionary;
+
+ internal SqlSelectCommand(
TDataModel dataModel,
DbConnection connection,
IEnumerable>> load = null,
IEnumerable>> ignore = null,
DbTransaction transaction = null,
- int depth = 0,
- int skip = 0,
- int take = 0) :
+ int depth = 0) :
base(dataModel, columns: [], parameters: [], connection, transaction, Constants.SqlSelectTemplate)
{
_load = load ?? [];
_ignore = ignore ?? [];
_maxDepth = depth;
- _skip = skip;
- _take = take;
}
+ ///
+ /// Orders by the specified column
+ ///
+ ///
+ ///
+ ///
public SqlSelectCommand OrderBy(Expression> property, bool ascending = true)
{
_orderBy.Add(ascending ? SqlOrderBy.Ascending(property) : SqlOrderBy.Descending(property));
return this;
}
+ ///
+ /// Offsets the select result by the number of records specified
+ ///
+ ///
+ ///
+ public SqlSelectCommand Skip(uint skip)
+ {
+ _skip = skip;
+ return this;
+ }
+
+ ///
+ /// Offsets the select result by the number of records specified
+ ///
+ ///
+ ///
+ public SqlSelectCommand Skip(int skip)
+ {
+ if (skip < 0)
+ {
+ throw new ArgumentException("You cannot use a negative number", nameof(skip));
+ }
+
+ _skip = (uint)skip;
+ return this;
+ }
+
+ ///
+ /// Takes the number of records specified from select result
+ ///
+ ///
+ ///
+ public SqlSelectCommand Take(uint take)
+ {
+ _take = take;
+ return this;
+ }
+
+ ///
+ /// Takes the number of records specified from select result
+ ///
+ ///
+ ///
+ public SqlSelectCommand Take(int take)
+ {
+ if (take < 0)
+ {
+ throw new ArgumentException("You cannot use a negative number", nameof(take));
+ }
+
+ _take = (uint)take;
+ return this;
+ }
+
int joinIndex = 0;
private void LoadJoins(int depth, IDataModel dataModel, bool parentIsCollection, string parentAliasName, string path)
{
- if (depth > _maxDepth)
+ if (depth > _maxDepth || DataModel is IViewDataModel)
{
return;
}
@@ -151,7 +216,11 @@ private void LoadJoins(int depth, IDataModel dataModel, bool parentIsCollection,
}
}
- protected override string RenderSqlCommand()
+ ///
+ ///
+ ///
+ ///
+ internal protected override string RenderSqlCommand()
{
if (_maxDepth > 0)
{
@@ -159,6 +228,23 @@ protected override string RenderSqlCommand()
_joins = [.. _joins.OrderBy(x => x.Index)];
}
+ if (DataModel is IViewDataModel)
+ {
+ _viewSplits.AddRange(DataModel
+ .DataModelMap
+ .MappedReferences
+ .Values
+ .Select(x => new ViewSplitOnItem { SplitOnColumn = x.ColumnName, Type = x.PropertyInfo.PropertyType, PropertyInfo = x.PropertyInfo, Index = x.Index }));
+
+ _viewSplits.AddRange(DataModel
+ .DataModelMap
+ .MappedCollections
+ .Values
+ .Select(x => new ViewSplitOnItem { SplitOnColumn = x.ColumnName, Type = x.DataModelCollectionType, PropertyInfo = x.PropertyInfo, IsCollection = true, Index = x.Index }));
+
+ _viewSplits = [.. _viewSplits.OrderBy(x => x.Index)];
+ }
+
// prepare filters for a select
Filters.ForEach(x => x.Alias = "d0");
@@ -166,7 +252,7 @@ protected override string RenderSqlCommand()
.Replace("$$COLUMNS$$", RenderColumns())
.Replace("$$TABLEFULLNAME$$", DataModel.DataModelMap.TableFullName)
.Replace("$$JOINS$$", _joins.RenderJoins())
- .Replace("$$FILTERS$$", Filters.Any() ? Filters.RenderWhereClause() : string.Empty)
+ .Replace("$$FILTERS$$", Filters.RenderWhereClause())
.Replace("$$ORDERBYCOLUMNS$$", RenderOrderByColumns())
.Replace("$$OFFSETFETCH$$", _offsetFetchSql);
}
@@ -186,22 +272,27 @@ private string RenderOrderByColumns()
var propertyPathSplitted = propertyPath.Split('.');
if (propertyPathSplitted.Count() == 1)
{
- orderByColumns.Add($"d0.{DataModel.DataModelMap.MappedProperties[propertyPath].ColumnName} {(IsOrderByAscending(propertyPath) ? "ASC" : "DESC")}");
+ orderByColumns.Add($"d0.\"{DataModel.DataModelMap.MappedProperties[propertyPath].ColumnName}\" {(IsOrderByAscending(propertyPath) ? "ASC" : "DESC")}");
continue;
}
var join = _joins.FirstOrDefault(x => x.Path.Equals(string.Join(".", propertyPathSplitted.SkipLast(1))));
- if (join == null)
+ if (join is not null)
{
+ var mappedProperty = join
+ .DataModelMap
+ .MappedProperties[propertyPathSplitted.TakeLast(1).First()];
+
+ orderByColumns.Add($"{join.RefTableNameAlias}.\"{mappedProperty.ColumnName}\" {(IsOrderByAscending($"{join.Path}.{mappedProperty.PropertyName}") ? "ASC" : "DESC")}");
continue;
}
- var mappedProperty = join
- .DataModelMap
- .MappedProperties[propertyPathSplitted.TakeLast(1).First()];
-
- orderByColumns.Add($"{join.RefTableNameAlias}.{mappedProperty.ColumnName} {(IsOrderByAscending($"{join.Path}.{mappedProperty.PropertyName}") ? "ASC" : "DESC")}");
+ if (DataModel is IViewDataModel)
+ {
+ // TODO check if all providers do not accept duplicated columns on views like Oracle
+ orderByColumns.Add($"d0.\"{propertyPathSplitted.Last()}\" {(column.Asc ? "ASC" : "DESC")}");
+ }
}
return $"ORDER BY {string.Join(",", orderByColumns)}";
@@ -232,7 +323,7 @@ private string RenderColumns()
x => x.Value.IsKey
|| includedRootProperties.Count() == 0
|| includedRootProperties.Any(p => p.Equals(x.Value.PropertyName)))
- .Select(x => $"d0.{x.Value.ColumnName} AS {x.Value.PropertyName}"));
+ .Select(x => $"d0.\"{x.Value.ColumnName}\" AS {x.Value.PropertyName}"));
// Only the ones affecting joins
var includedReferencesProperties = _load
@@ -252,13 +343,27 @@ private string RenderColumns()
.Where(x => joinIncludedProperties.Any() ?
joinIncludedProperties.Contains($"{join.Path}.{x.Value.PropertyName}") || x.Value.IsKey
: true)
- .Select(x => $"{join.RefTableNameAlias}.{x.Value.ColumnName} AS {x.Value.PropertyName}"));
+ .Select(x => $"{join.RefTableNameAlias}.\"{x.Value.ColumnName}\" AS {x.Value.PropertyName}"));
+ }
+
+ // Views: The split columns are required
+ if (DataModel is IViewDataModel)
+ {
+ foreach (var splitItem in _viewSplits)
+ {
+ sqlSelectColumns.AddRange(splitItem.Type.GetSelectColumnsFromType());
+ }
}
return string.Join(",", sqlSelectColumns);
}
- public override IEnumerable Execute()
+ ///
+ /// Executes the SQL Command
+ ///
+ ///
+ ///
+ public override IEnumerable Execute(int commandTimeout = 0)
{
// TODO Check if there isn't invalid include expressions (duplicated, methods, collections not in root, etc.)
@@ -277,74 +382,147 @@ public override IEnumerable Execute()
Debug.WriteLine(sql);
+ // TODO implement a solution to allow external types to be loaded by custom types + map + splitsOn
Type[] types = [
typeof(TDataModel),
.. _joins.Select(x => x.RefPropertyCollectionType ?? x.RefPropertyInfo.PropertyType).ToArray(),
+ .. _viewSplits.Select(x => x.Type)
];
- var dataModelResultDictionary = new Dictionary(comparer: new DataModelKeyComparer());
+ _dataModelResultDictionary = new(comparer: new DataModelKeyComparer());
return Connection.Query(
sql: sql,
types: types,
- map: (objs) =>
+ map: MapResult,
+ param: parameterDictionary,
+ transaction: Transaction,
+ buffered: true,
+ splitOn: string.Join(",", [.. _joins.Select(x => x.DataModelMap.KeyProperty.Name), .. _viewSplits.Select(x => x.SplitOnColumn)]),
+ commandTimeout: commandTimeout,
+ commandType: CommandType.Text)
+ .Distinct();
+ }
+
+ ///
+ /// Executes the SQL command asynchronously
+ ///
+ /// The time in seconds to wait the execution
+ ///
+ public override async Task> ExecuteAsync(int commandTimeout = 0)
+ {
+ return await Task.Run(() => Execute(commandTimeout));
+ }
+
+ private TDataModel MapResult(params object[] objs)
+ {
+ return DataModel is IViewDataModel ? MapSelectViewResult(objs) : MapSelectTableResult(objs);
+ }
+
+ private TDataModel MapSelectViewResult(params object[] objs)
+ {
+ var keyValue = ((TDataModel)objs[0]).DataModelMap.KeyProperty.GetValue(objs[0], null);
+ if (!_dataModelResultDictionary.TryGetValue(keyValue, out TDataModel rootDataModel))
+ {
+ _dataModelResultDictionary.Add(keyValue, rootDataModel = (TDataModel)objs[0]);
+ }
+
+ for (int i = 0; i < _viewSplits.Count; ++i)
+ {
+ var splitItem = _viewSplits[i];
+ splitItem.TransientRef = objs[i + 1];
+
+ if (splitItem.IsCollection)
{
- var keyValue = ((TDataModel)objs[0]).DataModelMap.KeyProperty.GetValue(objs[0], null);
- if (!dataModelResultDictionary.TryGetValue(keyValue, out TDataModel rootDataModel))
+ var collectionValue = splitItem.PropertyInfo.GetValue(rootDataModel, null);
+
+ // Initialize the collection property
+ if (collectionValue is null)
{
- dataModelResultDictionary.Add(keyValue, rootDataModel = (TDataModel)objs[0]);
+ var collectionType = typeof(List<>).MakeGenericType(splitItem.Type);
+ collectionValue = Activator.CreateInstance(collectionType);
+
+ splitItem.PropertyInfo.SetValue(obj: rootDataModel, value: collectionValue);
}
- // Load mapped joins
- for (int i = 0; i < _joins.Count; ++i)
+ if (splitItem.TransientRef is null)
{
- var join = _joins[i];
- join.TransientRef = objs[i + 1];
-
- if (join.IsCollection)
- {
- var collectionValue = join.RefPropertyInfo.GetValue(rootDataModel, null);
-
- if (collectionValue == null)
- {
- var collectionType = typeof(List<>).MakeGenericType(join.RefPropertyCollectionType);
- collectionValue = Activator.CreateInstance(collectionType);
-
- join.RefPropertyInfo.SetValue(obj: rootDataModel, value: collectionValue);
- }
-
- if (join.TransientRef is null)
- {
- continue;
- }
-
- var joinTransientRefDataModel = (IDataModel)join.TransientRef;
- var transientRefKeyValue = joinTransientRefDataModel.DataModelMap.KeyProperty.GetValue(joinTransientRefDataModel, null);
-
- if (!join.MapBuffer.ContainsKey(transientRefKeyValue))
- {
- join.MapBuffer.Add(transientRefKeyValue, join.TransientRef);
- ((IList)collectionValue).Add(join.TransientRef);
- }
- continue;
- }
-
- if (objs[i + 1] == null)
- {
- continue;
- }
-
- var _targetJoin = _joins.Find(x => x.RefTableNameAlias.Equals(join.ParentTableAliasName));
- join.RefPropertyInfo.SetValue(obj: _targetJoin?.TransientRef ?? rootDataModel, value: objs[i + 1]);
+ continue;
}
- return rootDataModel;
- },
- param: parameterDictionary,
- transaction: Transaction,
- buffered: true,
- splitOn: string.Join(",", _joins.Select(x => x.DataModelMap.KeyProperty.Name)),
- commandTimeout: null,
- commandType: CommandType.Text)
- .Distinct();
+ ((IList)collectionValue).Add(splitItem.TransientRef);
+
+ // var transientRefKeyValue = splitItem.TransientRef.GetKeyValue();
+ // if (!splitItem.MapBuffer.ContainsKey(transientRefKeyValue))
+ // {
+ // splitItem.MapBuffer.Add(transientRefKeyValue, splitItem.TransientRef);
+ // }
+ continue;
+ }
+
+ if (objs[i + 1] == null)
+ {
+ continue;
+ }
+
+ splitItem.PropertyInfo.SetValue(rootDataModel, objs[i + 1]);
+ }
+
+ return rootDataModel;
+ }
+
+ private TDataModel MapSelectTableResult(params object[] objs)
+ {
+ var keyValue = ((TDataModel)objs[0]).DataModelMap.KeyProperty.GetValue(objs[0], null);
+ if (!_dataModelResultDictionary.TryGetValue(keyValue, out TDataModel rootDataModel))
+ {
+ _dataModelResultDictionary.Add(keyValue, rootDataModel = (TDataModel)objs[0]);
+ }
+
+ // Load mapped joins
+ for (int i = 0; i < _joins.Count; ++i)
+ {
+ var join = _joins[i];
+ join.TransientRef = objs[i + 1];
+
+ if (join.IsCollection)
+ {
+ var collectionValue = join.RefPropertyInfo.GetValue(rootDataModel, null);
+
+ // Initialize the collection property
+ if (collectionValue is null)
+ {
+ var collectionType = typeof(List<>).MakeGenericType(join.RefPropertyCollectionType);
+ collectionValue = Activator.CreateInstance(collectionType);
+
+ join.RefPropertyInfo.SetValue(obj: rootDataModel, value: collectionValue);
+ }
+
+ if (join.TransientRef is null)
+ {
+ continue;
+ }
+
+ var joinTransientRefDataModel = (IDataModel)join.TransientRef;
+ var transientRefKeyValue = joinTransientRefDataModel.DataModelMap.KeyProperty.GetValue(joinTransientRefDataModel, null);
+
+ if (!join.MapBuffer.ContainsKey(transientRefKeyValue))
+ {
+ join.MapBuffer.Add(transientRefKeyValue, join.TransientRef);
+ ((IList)collectionValue).Add(join.TransientRef);
+ }
+
+ continue;
+ }
+
+ if (objs[i + 1] == null)
+ {
+ continue;
+ }
+
+ var _targetJoin = _joins.Find(x => x.RefTableNameAlias.Equals(join.ParentTableAliasName));
+ join.RefPropertyInfo.SetValue(obj: _targetJoin?.TransientRef ?? rootDataModel, value: objs[i + 1]);
+ }
+
+ return rootDataModel;
}
}
diff --git a/src/DbBroker/SqlSelectCountCommand.cs b/src/DbBroker/SqlSelectCountCommand.cs
new file mode 100644
index 0000000..3a74ce6
--- /dev/null
+++ b/src/DbBroker/SqlSelectCountCommand.cs
@@ -0,0 +1,58 @@
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Common;
+using System.Diagnostics;
+using System.Linq;
+using DbBroker.Model;
+
+namespace DbBroker;
+
+///
+/// SQL SELECT COUNT command
+///
+///
+public class SqlSelectCountCommand : SqlCommand where TDataModel : DataModel
+{
+ internal SqlSelectCountCommand(
+ TDataModel dataModel,
+ DbConnection connection,
+ DbTransaction transaction) : base(dataModel, [], [], connection, transaction, Constants.SqlSelectCountTemplate)
+ {
+ }
+
+ ///
+ /// Executes the SELECT COUNT command and returns the result
+ ///
+ ///
+ ///
+ public override long Execute(int commandTimeout = 0)
+ {
+ try
+ {
+ if (Connection.State != ConnectionState.Open)
+ {
+ Connection.Open();
+ }
+
+ List filterParameters = [];
+ foreach (var filter in Filters)
+ {
+ filterParameters.AddRange(filter.Parameters);
+ }
+
+ var command = Connection.CreateCommand();
+ command.CommandText = RenderSqlCommand();
+ command.Parameters.AddRange(Parameters.ToArray());
+ command.Parameters.AddRange(filterParameters.ToArray());
+ command.Transaction = Transaction;
+ command.CommandTimeout = commandTimeout;
+
+ return long.Parse(command.ExecuteScalar().ToString());
+ }
+ catch
+ {
+ Debug.WriteLine(RenderSqlCommand());
+ throw;
+ }
+ }
+}
diff --git a/src/DbBroker/SqlSelectMaxCommand.cs b/src/DbBroker/SqlSelectMaxCommand.cs
new file mode 100644
index 0000000..a2b6c12
--- /dev/null
+++ b/src/DbBroker/SqlSelectMaxCommand.cs
@@ -0,0 +1,27 @@
+using System.Data.Common;
+using DbBroker.Model;
+
+namespace DbBroker;
+
+///
+/// Select SQL MAX command
+///
+///
+///
+public class SqlSelectMaxCommand : SqlSelectAggregateCommand where TDataModel : DataModel
+{
+ ///
+ /// Constructor for SQL MAX command
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SqlSelectMaxCommand(
+ TDataModel dataModel,
+ DataModelMapProperty column,
+ DbConnection connection,
+ DbTransaction transaction) : base(dataModel, column, connection, transaction, Constants.SqlSelectMaxTemplate)
+ {
+ }
+}
diff --git a/src/DbBroker/SqlSelectMinCommand.cs b/src/DbBroker/SqlSelectMinCommand.cs
new file mode 100644
index 0000000..35f5868
--- /dev/null
+++ b/src/DbBroker/SqlSelectMinCommand.cs
@@ -0,0 +1,27 @@
+using System.Data.Common;
+using DbBroker.Model;
+
+namespace DbBroker;
+
+///
+/// Select SQL MIN command
+///
+///
+///
+public class SqlSelectMinCommand : SqlSelectAggregateCommand where TDataModel : DataModel
+{
+ ///
+ /// Constructor for SQL MIN command
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SqlSelectMinCommand(
+ TDataModel dataModel,
+ DataModelMapProperty column,
+ DbConnection connection,
+ DbTransaction transaction) : base(dataModel, column, connection, transaction, Constants.SqlSelectMinTemplate)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/DbBroker/SqlSelectSumCommand.cs b/src/DbBroker/SqlSelectSumCommand.cs
new file mode 100644
index 0000000..2a6abbe
--- /dev/null
+++ b/src/DbBroker/SqlSelectSumCommand.cs
@@ -0,0 +1,27 @@
+using System.Data.Common;
+using DbBroker.Model;
+
+namespace DbBroker;
+
+///
+/// Select SQL SUM command
+///
+///
+///
+public class SqlSelectSumCommand : SqlSelectAggregateCommand where TDataModel : DataModel
+{
+ ///
+ /// Constructor for SQL SUM command
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SqlSelectSumCommand(
+ TDataModel dataModel,
+ DataModelMapProperty column,
+ DbConnection connection,
+ DbTransaction transaction) : base(dataModel, column, connection, transaction, Constants.SqlSelectSumTemplate)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/DbBroker/SqlUpdateCommand.cs b/src/DbBroker/SqlUpdateCommand.cs
index acd058d..aed45aa 100644
--- a/src/DbBroker/SqlUpdateCommand.cs
+++ b/src/DbBroker/SqlUpdateCommand.cs
@@ -4,6 +4,10 @@
namespace DbBroker;
+///
+/// Represents a SQL UPDATE command for a specific data model type.
+///
+///
public class SqlUpdateCommand : SqlCommand where TDataModel : DataModel
{
internal SqlUpdateCommand(
@@ -11,6 +15,9 @@ internal SqlUpdateCommand(
IEnumerable columns,
IEnumerable parameters,
DbConnection connection,
- DbTransaction transaction) :
- base(dataModel, columns, parameters, connection, transaction, Constants.SqlUpdateTemplate) { }
+ DbTransaction transaction) :
+ base(dataModel, columns, parameters, connection, transaction, Constants.SqlUpdateTemplate)
+ {
+ RequireFilter = true;
+ }
}
diff --git a/src/DbBroker/SqlUpsertCommand.cs b/src/DbBroker/SqlUpsertCommand.cs
index 4e9b201..b5500fd 100644
--- a/src/DbBroker/SqlUpsertCommand.cs
+++ b/src/DbBroker/SqlUpsertCommand.cs
@@ -6,7 +6,12 @@
namespace DbBroker;
-public class SqlUpsertCommand : SqlCommand where TDataModel : DataModel
+// TODO it may not make sense to inherit from SqlCommand
+///
+/// Abstraction for SQL UPSERT command
+///
+///
+public sealed class SqlUpsertCommand : SqlCommand where TDataModel : DataModel
{
private const string SqlUpsertOracleTemplate =
@"MERGE INTO $$TABLEFULLNAME$$ t
@@ -36,8 +41,10 @@ internal SqlUpsertCommand(
IEnumerable parameters,
DbConnection connection,
DbTransaction transaction) :
- base(dataModel, columns, parameters, connection, transaction, Constants.SqlUpdateTemplate)
+ base(dataModel, columns, parameters, connection, transaction, string.Empty)
{
+ RequireFilter = false;
+
switch (dataModel.DataModelMap.Provider)
{
case SupportedDatabaseProviders.SqlServer:
@@ -49,13 +56,17 @@ internal SqlUpsertCommand(
}
}
- protected override string RenderSqlCommand()
+ ///
+ ///
+ ///
+ ///
+ internal protected override string RenderSqlCommand()
{
var keyPropertyMap = DataModel.DataModelMap.MappedProperties.FirstOrDefault(x => x.Value.IsKey);
return SqlTemplate
.Replace("$$TABLEFULLNAME$$", DataModel.DataModelMap.TableFullName)
- .Replace("$$USING$$", SqlUpsertOracleUsingTemplate.Replace("$$COLUMNS$$", string.Join("," , Parameters.Select(x => $"{x.ParameterName} AS {x.ParameterName[1..]}"))))
+ .Replace("$$USING$$", SqlUpsertOracleUsingTemplate.Replace("$$COLUMNS$$", string.Join(",", Parameters.Select(x => $"{x.ParameterName} AS {x.ParameterName[1..]}"))))
.Replace("$$KEYCOLUMN$$", keyPropertyMap.Value.ColumnName)
.Replace("$$KEYCOLUMNPARAM$$", keyPropertyMap.Value.ColumnName)
.Replace("$$UPDATE$$", string.Join(",", $"UPDATE SET {string.Join(",", Columns.Where(c => !c.IsKey).Select(x => $"t.{x.ColumnName} = s.{x.ColumnName}"))}"))
diff --git a/src/README.md b/src/README.md
new file mode 100644
index 0000000..eed63fe
--- /dev/null
+++ b/src/README.md
@@ -0,0 +1,24 @@
+# DBBroker Tests or Showcase
+
+The DBBroker Tests and Showcase CLI application share the same databases.
+
+## Prerequisites
+
+- Docker;
+- .NET 8 SDK or later;
+
+## Get Started
+
+Before running the Tests or Showcase CLI application, make sure you spin up the databases running:
+
+```bash
+docker compose --profile all --file ./Databases/docker-compose.yaml up --detach
+```
+
+**IMPORTANT**: The Oracle database container image is not on Docker Hub, so you might need to `docker login` into [Oracle Registry](https://container-registry.oracle.com/), or use `no-oracle` profile instead of `all`.
+
+To stop and clean up the containers:
+
+```bash
+docker compose --profile all --file ./Databases/docker-compose.yaml down --volumes
+```