GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
GPALDatabase.cs
1// =============================================================================
2// GPAL - Generally Positive Automation Library
3// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4//
5// This file is part of GPAL.
6// Licensed under the Business Source License 1.1
7//
8// Primary development, architecture, and vision by Michael B. Vederman,
9// CEO of Software Decisions, Inc., Texas.
10//
11// Internal development maintained privately.
12// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13//
14// See LICENSE for full terms, including Additional Use Grant.
15// =============================================================================
16
17using System;
18using System.Collections.Generic;
19using System.Data.SqlClient;
20using System.Linq;
21using System.Text;
22using System.Threading.Tasks;
24using System.Data;
25using static GenerallyPositive.Enums;
26
27namespace GenerallyPositive
28{
33 public class GPALDatabase : IGPALDatabase, IAllowDatabaseGridSettings, IDisposable
34 {
35 private DatabaseSettings.DatabaseColumn lastDatabaseColumn { get; set; }
36 internal DatabaseSettings DatabaseSettings { get; set; }
37
38 public delegate int CallOnError(GPALDatabase database);
39
46 public class DatabaseError
47 {
49 public int Number { get; internal set; }
51 public string Message { get; internal set; }
53 public string Sql { get; internal set; }
55 public IGPALGrid<string> Parameters { get; internal set; }
57 public int Row { get; internal set; } = -1;
59 public List<string> RowValues { get; internal set; }
61 public DateTime Occurred { get; internal set; }
63 public Exception Exception { get; internal set; }
64 }
65
71 {
72 get
73 {
74 return DatabaseSettings.LastError;
75 }
76 internal set
77 {
78 DatabaseSettings.LastError = value;
79 }
80 }
81
106 {
107 return this;
108 }
109
110 internal GPALDatabase()
111 {
112 DatabaseSettings = new DatabaseSettings(this);
113 }
114
115 #region <Getters/Setters>
116 // each verb says which one it is and nothing more. copying its sql into SqlCommand left that sql behind
117 // for the next verb and replaced a stored procedure with text on the way; the statement is chosen from
118 // the verb when the command is built. See DatabaseSettings.Verb
119 public IAllowParametersAndUsing Create
120 {
121 get
122 {
123 DatabaseSettings.Verb = DatabaseSettings.CrudVerb.Create;
124
125 return this;
126 }
127 }
128 public IAllowParametersAndInto Read
129 {
130 get
131 {
132 DatabaseSettings.Verb = DatabaseSettings.CrudVerb.Read;
133
134 return this;
135 }
136 }
137 public IAllowParametersAndUsing Update
138 {
139 get
140 {
141 DatabaseSettings.Verb = DatabaseSettings.CrudVerb.Update;
142
143 return this;
144 }
145 }
146 public IAllowParametersAndUsing Delete
147 {
148 get
149 {
150 DatabaseSettings.Verb = DatabaseSettings.CrudVerb.Delete;
151
152 return this;
153 }
154 }
159 {
160 get
161 {
162 DatabaseSettings.ParametersList?.Clear();
163 DatabaseSettings.ParametersGrid?.Clear();
164
165 return this;
166 }
167 }
168
174 public IGPALGrid<string> Tokens
175 {
176 get
177 {
178 return DatabaseSettings.Tokens;
179 }
180 internal set
181 {
182 DatabaseSettings.Tokens = value;
183 }
184 }
185
186 public IAllowParametersAndActions BeginTransaction
187 {
188 get
189 {
190 if (null == DatabaseSettings.SqlTransaction)
191 {
192 try
193 {
194 DatabaseHelper.OpenConnection(DatabaseSettings);
195 DatabaseSettings.SqlTransaction = DatabaseSettings.Connection.BeginTransaction();
196 }
197 catch (Exception ex)
198 {
199 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, "ERROR beginning transaction", this, Enums.GPALObjectType.Database, ex);
200 }
201 }
202 else
203 {
204 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Already in a transaction. You cannot start a new transaction while in a transaction. Doing nothing.", this, Enums.GPALObjectType.Database);
205 }
206 return this;
207 }
208 }
209
210 public IAllowDatabaseActions Rollback
211 {
212 get
213 {
214 DatabaseSettings.SqlTransaction?.Rollback();
215 DatabaseSettings.SqlTransaction = null;
216 DatabaseHelper.CloseEverything(DatabaseSettings);
217 return this;
218 }
219 }
220
221 public IAllowDatabaseActions Commit
222 {
223 get
224 {
225 DatabaseSettings.SqlTransaction?.Commit();
226 DatabaseSettings.SqlTransaction = null;
227 DatabaseHelper.CloseEverything(DatabaseSettings);
228 return this;
229 }
230 }
231
232 #endregion <Getters/Setters>
233 #region <Fluent Methods>
249 public IAllowDatabaseSettings WithConnectionString(string connectionString)
250 {
251 DatabaseSettings.ConnectionString = connectionString;
252 return this;
253 }
254
260 public IAllowDatabaseSettings WithUsername(string username)
261 {
262 DatabaseSettings.UserName = username;
263 return this;
264 }
265
271 public IAllowDatabaseSettings WithPassword(string password)
272 {
273 DatabaseSettings.Password = password;
274 return this;
275 }
276
281 public IAllowDatabaseSettings WithSQLCommand(string sqlCommand)
282 {
283 DatabaseSettings.SqlCommand = sqlCommand;
284 return this;
285 }
286
291 public IAllowDatabaseSettings WithStoredProcedure(string storedProcedure)
292 {
293 DatabaseSettings.StoredProcedure = storedProcedure;
294 return this;
295 }
296
302 public IAllowDatabaseSettings WithParameterName(string parameterName)
303 {
304 DatabaseSettings.ParameterNamesList.Add(parameterName);
305 return this;
306 }
307
313 {
314 // TODO: if datbase action already called, clear the parms
315 DatabaseSettings.ParametersList.Add(parameter);
316 return this;
317 }
318
323 public IAllowParametersAndActions WithParameterGrid(IGPALGrid<string> parametersGrid)
324 {
325 DatabaseSettings.ParametersGrid = parametersGrid;
326 return this;
327 }
328 // TODO: figure out fluent syntax for using the underlying sql
329 public IAllowDatabaseSettings WithCreateAs(string sqlCommand)
330 {
331 DatabaseSettings.CreateSQL = sqlCommand;
332 return this;
333 }
334 // TODO: figure out fluent syntax for using the underlying sql
335 public IAllowDatabaseSettings WithReadAs(string sqlCommand)
336 {
337 DatabaseSettings.ReadSQL = sqlCommand;
338 return this;
339 }
340 // TODO: figure out fluent syntax for using the underlying sql
341 public IAllowDatabaseSettings WithUpdateAs(string sqlCommand)
342 {
343 DatabaseSettings.UpdateSQL = sqlCommand;
344 return this;
345 }
346 // TODO: figure out fluent syntax for using the underlying sql
347 public IAllowDatabaseSettings WithDeleteAs(string sqlCommand)
348 {
349 DatabaseSettings.DeleteSQL = sqlCommand;
350 return this;
351 }
352
370 public IAllowDatabaseSettings WithDatabaseType(Enums.DatabaseType databaseType)
371 {
372 DatabaseSettings.DatabaseType = databaseType;
373 return this;
374 }
375
391 public IAllowDatabaseSettings WithServerName(string serverName)
392 {
393 DatabaseSettings.ServerName = serverName;
394 return this;
395 }
396
412 public IAllowDatabaseSettings WithDatabaseName(string databaseName)
413 {
414 DatabaseSettings.DatabaseName = databaseName;
415 return this;
416 }
417
434 public IAllowDatabaseSettings WithTableName(string tableName)
435 {
436 DatabaseSettings.TableName = tableName;
437 return this;
438 }
439
466 public IAllowDatabaseSettings WithCommandTimeout(int timeoutInSeconds)
467 {
468 DatabaseSettings.CommandTimeoutSeconds = timeoutInSeconds;
469 return this;
470 }
471
478 {
479 DatabaseHelper.CloseEverything(DatabaseSettings);
480 return this;
481 }
482
486 public void Dispose()
487 {
488 DatabaseHelper.CloseEverything(DatabaseSettings);
489 }
490
491 public IAllowDatabaseSettings WithRowCount(int rowCount)
492 {
493 DatabaseSettings.RowCount = rowCount;
494 return this;
495 }
496
497 #region <Output Column Settings [not yet implemented]>
503 public IAllowDatabaseSettings WithDatetimeParameter(string parameterName)
504 {
505 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.DateTime);
506 lastDatabaseColumn = databaseColumn;
507 DatabaseSettings.ColumnList.Add(databaseColumn);
508 return this;
509 }
515 public IAllowDatabaseSettings WithTimeParameter(string parameterName)
516 {
517 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.Time);
518 lastDatabaseColumn = databaseColumn;
519 DatabaseSettings.ColumnList.Add(databaseColumn);
520 return this;
521 }
527 public IAllowDatabaseSettings WithTimestampParameter(string parameterName)
528 {
529 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.Timestamp);
530 lastDatabaseColumn = databaseColumn;
531 DatabaseSettings.ColumnList.Add(databaseColumn);
532 return this;
533 }
539 public IAllowPrecsionScaleAndSettings WithDecimalParameter(string parameterName)
540 {
541 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.Decimal);
542 lastDatabaseColumn = databaseColumn;
543 DatabaseSettings.ColumnList.Add(databaseColumn);
544 return this;
545 }
551 public IAllowDatabaseSettings WithFloatParameter(string parameterName)
552 {
553 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.Float);
554 lastDatabaseColumn = databaseColumn;
555 DatabaseSettings.ColumnList.Add(databaseColumn);
556 return this;
557 }
563 public IAllowDatabaseSettings WithImageParameter(string parameterName)
564 {
565 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.Image);
566 lastDatabaseColumn = databaseColumn;
567 DatabaseSettings.ColumnList.Add(databaseColumn);
568 return this;
569 }
575 public IAllowDatabaseSettings WithIntegerParameter(string parameterName)
576 {
577 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.Int);
578 lastDatabaseColumn = databaseColumn;
579 DatabaseSettings.ColumnList.Add(databaseColumn);
580 return this;
581 }
587 public IAllowDatabaseSettings WithMoneyParameter(string parameterName)
588 {
589 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.Money);
590 lastDatabaseColumn = databaseColumn;
591 DatabaseSettings.ColumnList.Add(databaseColumn);
592 return this;
593 }
599 public IAllowParameterLength WithCharParameter(string parameterName)
600 {
601 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.Char);
602 lastDatabaseColumn = databaseColumn;
603 DatabaseSettings.ColumnList.Add(databaseColumn);
604 return this;
605 }
611 public IAllowParameterLength WithNCharParameter(string parameterName)
612 {
613 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.NChar);
614 lastDatabaseColumn = databaseColumn;
615 DatabaseSettings.ColumnList.Add(databaseColumn);
616 return this;
617 }
623 public IAllowParameterLength WithVarCharParameter(string parameterName)
624 {
625 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.VarChar);
626 lastDatabaseColumn = databaseColumn;
627 DatabaseSettings.ColumnList.Add(databaseColumn);
628 return this;
629 }
635 public IAllowParameterLength WithNVarCharParameter(string parameterName)
636 {
637 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.NVarChar);
638 lastDatabaseColumn = databaseColumn;
639 DatabaseSettings.ColumnList.Add(databaseColumn);
640 return this;
641 }
647 public IAllowParameterLength WithTextParameter(string parameterName)
648 {
649 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.Text);
650 lastDatabaseColumn = databaseColumn;
651 DatabaseSettings.ColumnList.Add(databaseColumn);
652 return this;
653 }
659 public IAllowParameterLength WithNTextParameter(string parameterName)
660 {
661 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, System.Data.SqlDbType.NText);
662 lastDatabaseColumn = databaseColumn;
663 DatabaseSettings.ColumnList.Add(databaseColumn);
664 return this;
665 }
666 public IAllowDatabaseSettings WithParameterLength(int length)
667 {
668 if (true == NamedAColumn("length"))
669 lastDatabaseColumn.ColumnLength = length;
670
671 return this;
672 }
673
674 public IAllowPrecsionScaleAndSettings WithParameterPrecision(int columnPrecision)
675 {
676 if (true == NamedAColumn("precision"))
677 lastDatabaseColumn.ColumnPrecision = columnPrecision;
678
679 return this;
680 }
681
682 public IAllowPrecsionScaleAndSettings WithParameterScale(int columnScale)
683 {
684 if (true == NamedAColumn("scale"))
685 lastDatabaseColumn.ColumnScale = columnScale;
686
687 return this;
688 }
689
696 private bool NamedAColumn(string setting)
697 {
698 bool retVal = null != lastDatabaseColumn;
699
700 if (false == retVal)
701 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
702 $"No parameter to set a {setting} on. Define one first, with .WithVarCharParameter, .WithDecimalParameter or another of the parameter calls",
703 this, GPALObjectType.Database);
704
705 return retVal;
706 }
707
708 public IAllowDatabaseSettings WithTableParameter(string parameterName)
709 {
710 DatabaseSettings.DatabaseColumn databaseColumn = new DatabaseSettings.DatabaseColumn(parameterName, SqlDbType.Structured);
711 lastDatabaseColumn = databaseColumn;
712 DatabaseSettings.ColumnList.Add(databaseColumn);
713 return this;
714 }
715 public IAllowDatabaseSettings WithUDTTName(string typeName)
716 {
717 if (lastDatabaseColumn == null)
718 {
719 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
720 "No column defined before setting UDTT name. Call .WithTableParameter() first.",
721 this, GPALObjectType.Database);
722 return this;
723 }
724 if (lastDatabaseColumn.ColumnType != SqlDbType.Structured)
725 {
726 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
727 $"UDTT name specified for non-Structured column [{lastDatabaseColumn.ColumnName}]. Ignoring.",
728 this, GPALObjectType.Database);
729 }
730 lastDatabaseColumn.TvTableTypeName = typeName;
731 return this;
732 }
733 public IAllowDatabaseExecute Using(IGPALGrid<string> parametersGrid)
734 {
735 DatabaseSettings.ParametersGrid = parametersGrid;
736 return this;
737 }
738
739 public IAllowDatabaseExecute Using(DataTable dataTable)
740 {
741 IGPALGrid<string> parametersGrid = DatabaseHelper.DataTableToGrid(dataTable, out List<DatabaseSettings.DatabaseColumn> columns);
742 DatabaseSettings.ParametersGrid = parametersGrid;
743 DatabaseSettings.ColumnList = columns;
744 return this;
745 }
746
747 public IAllowDatabaseGridSettings SaveTo(out IGPALGrid<string> dataGrid)
748 {
749 DatabaseHelper.GetGrid(DatabaseSettings, out dataGrid);
750 return this;
751 }
752
753 public IAllowDatabaseSettings GetRowCount(out List<int> rowCountsPerTable)
754 {
755 rowCountsPerTable = DatabaseSettings.LastGridRowCountsPerTable ?? new List<int>();
756 return this;
757 }
758
759 public IAllowDatabaseSettings SaveTo(out DataTable dataTable)
760 {
761 DatabaseHelper.GetDataTable(DatabaseSettings, out dataTable);
762 return this;
763 }
764
765 public IAllowDatabaseSettings SaveTo(out DataSet dataSet)
766 {
767 DatabaseHelper.GetDataSet(DatabaseSettings, out dataSet);
768 return this;
769 }
770
785 public IAllowDatabaseSettings SaveTo(out string value)
786 {
787 DatabaseHelper.GetScalar(DatabaseSettings, out value);
788 return this;
789 }
790
791 public IAllowParametersAndActions Execute(out int rowCount)
792 {
793 rowCount = DatabaseHelper.ExecuteSQL(DatabaseSettings, DatabaseHelper.BuildWriteStatement(DatabaseSettings), ParameterTokens());
794 return this;
795 }
796
797 public IAllowParametersAndActions Execute(out List<int> rowCountsPerStatement)
798 {
799 DatabaseHelper.ExecuteSQL(DatabaseSettings, DatabaseHelper.BuildWriteStatement(DatabaseSettings), ParameterTokens(), out rowCountsPerStatement);
800 return this;
801 }
802
810 private IGPALGrid<string> ParameterTokens()
811 {
812 IGPALGrid<string> retVal = DatabaseSettings.ParametersGrid;
813
814 if (null == retVal || 0 == retVal.Rows)
815 {
816 Tokens.Clear();
817 Tokens.AddRow(DatabaseSettings.ParametersList);
818
819 retVal = Tokens;
820 }
821
822 return retVal;
823 }
824
825 public IAllowDatabaseSettings WithErrorCallback(GPALDatabase.CallOnError callOnError)
826 {
827 DatabaseSettings.CallOnErrorHandler = callOnError;
828 return this;
829 }
830 #endregion <Output Column Settings [saving not yet implemented]>
831 #endregion <Fluent Methods>
832 #region <Private>
833 #endregion <Private>
834 }
835}
836
What went wrong on the last call, and enough about it to act on. A failure hands the callback the da...
string Message
What the failure said.
string Sql
The sql that was running.
Exception Exception
The exception itself, for a caller that wants the stack.
int Number
The server's error number, 0 when the failure did not come from the server.
int Row
Which row of Parameters was being written, -1 when the failure was not on a row.
IGPALGrid< string > Parameters
The rows of parameters the sql was running against.
List< string > RowValues
That row's values, so the callback does not have to index back into the grid.
Class to define database usage. Currently only used for input from a table, sql or stored procedure....
IAllowDatabaseSettings WithUsername(string username)
Set the username used to connect to the database. If no connection string is provided,...
IAllowDatabaseSettings WithStoredProcedure(string storedProcedure)
The stored procedure to execute for the input data grid (tokens).
IAllowDatabaseSettings Close()
Closes the connection and rolls back a transaction still open on it. The database can be used again a...
IGPALGrid< string > Tokens
The rows/columns returned from the database. Tokens are loaded upon calling any [Append/FillIn/Inser...
DatabaseError LastError
The last failure, or null when the last call did not fail. Cleared at the start of every call,...
IAllowDatabaseSettings WithCommandTimeout(int timeoutInSeconds)
Set the numbers of rows to select from the table.
IAllowDatabaseSettings WithTableName(string tableName)
Set the table name to query for input. If no SQL command or stored procedure is defined,...
IAllowDatabaseSettings WithConnectionString(string connectionString)
Set the connectrion string used to connect to the database. If no connection string is provided,...
IAllowDatabaseSettings SaveTo(out string value)
Reads a single value: the first column of the first row, without building a grid around it....
IAllowDatabaseSettings WithDatabaseType(Enums.DatabaseType databaseType)
Set the type of database GPAL is interacting with. Currently only SQL Server is supported....
IAllowParametersAndActions Execute(out List< int > rowCountsPerStatement)
Per-statement breakdown (via SqlCommand.StatementCompleted) instead of the single aggregate count.
IAllowParametersAndActions WithParameterGrid(IGPALGrid< string > parametersGrid)
Add a parameter to the parameters list. .
IAllowDatabaseSettings WithPassword(string password)
Set the password used to connect to the database. If no connection string is provided,...
IGPALDatabase ToGPALObject()
Hands back the whole database, which is also how a chain gets from describing one to using one....
IAllowDatabaseSettings WithServerName(string serverName)
Set the database servername. Used with databasename and [optional] usename/password to construct a c...
IAllowDatabaseSettings WithSQLCommand(string sqlCommand)
The SQL command to execute for the input data grid (tokens).
IAllowDatabaseSettings WithDatabaseName(string databaseName)
Set the database instance name. Used with database servername and [optional] usename/password to con...
void Dispose()
Closes the connection, so a database can be held in a using block. Same work as Close.
IAllowParametersAndActions WithParameter(string parameter)
Add a parameter to the parameters list. .
IAllowParameters ClearParameters
If you have already defined parameter objectss on a GPAL.Database, you can clear them and add new par...
IAllowDatabaseSettings GetRowCount(out List< int > rowCountsPerTable)
Only SaveTo(out IGPALGrid<string>) returns this - meaningless for SaveTo(DataTable)/SaveTo(DataSet),...
IAllowDatabaseSettings WithParameterName(string parameterName)
Add a parameter name to the parameter names list. CAVEAT: we need to know the type of parameter,...
SaveTo(...) already ran the read (an out parameter forces that), so chaining .Execute() afterward wou...
What a database is, not what to do with it: the connection, the command, and the definitions of the p...