Skip to content

Data Access Patterns

There is no ORM. All data access goes through ADO.NET, in two coexisting styles, both of which build SQL as strings. Knowing both styles — and their shared weakness — is essential for reading and safely changing the code.

The gateway: HAKDAC (DataAccessControl)

The global HAKDAC (FrameWork.SmartSoft.General.DataAccessControl) is the primary data gateway. Its surface:

Method Does
GetConnectionString(server, db, uid, pwd) Builds the SQL-auth connection string.
DataTable(ConnStr, SQL) Runs a query → DataTable. CommandTimeout = 0. On error returns Nothing, sets ErrMessage.
DataFound(ConnStr, Table, Condition) SELECT count(*) … WHERE … > 0 existence check.
GetDataTable(ConnStr, Table, Condition, Result) Single scalar via SqlDataReader.
NonQuery(...) INSERT/UPDATE/DELETE; overloads for string, StringBuilder, SqlCommand (stored proc), or a list of statements. Logs failures to REF_ERRORLOG.
Scalar / DataSet / DataReader Other read shapes.
TableExist / ColumnExist / ProcedureExist Schema metadata checks.
flowchart LR
    Code["Form / Class code"] -->|"SQL string"| HAKDAC["HAKDAC.DataTable / NonQuery"]
    HAKDAC -->|"SqlConnection · SqlCommand · SqlDataAdapter"| DB[("SQL Server")]
    HAKDAC -->|"on failure"| LOG[("REF_ERRORLOG")]
    style HAKDAC fill:#e8eaf6,stroke:#3f51b5
    style DB fill:#e0f2f1,stroke:#00897b

Style 1 — Inline StringBuilder SQL

The most common pattern. SQL is concatenated with &, session/user values interpolated directly, then run through HAKDAC or a raw SqlConnection/SqlCommand/SqlDataReader.

' Illustrative — the shape you will see everywhere
Dim sb As New StringBuilder
sb.Append("SELECT * FROM tbMaster_Barang ")
sb.Append("WHERE PRD_KODESUP = '" & pSupplier & "' ")
sb.Append("  AND PRD_AKTIF = 'Y' ")
sb.Append("ORDER BY PRD_NAMA")
Dim dt As DataTable = HAKDAC.DataTable(ConnStr, sb.ToString)

Style 2 — Typed data-access classes (BrowseData pattern)

The FrameWork provides ~380 typed classes (tbMaster_*, tbTr_*, tbHist_*, tv_*, REF_*) under DataAccessControl. Each wraps one table/view. You set properties, then call a verb:

' Illustrative
Dim oBarang As New SmartSoft.Inventory.FrameWork.DataAccess.tbMaster_Barang
oBarang.ConnStr  = ConnStr
oBarang.Criteria = "PRD_KODESUP = '" & pSupplier & "'"
oBarang.OrderBy  = "PRD_NAMA"
Dim dt As DataTable = oBarang.BrowseData()
Verb Purpose
BrowseData() Query rows matching Criteria / OrderBy.
GetDataByPrimaryKey() Load one record by its key.
SaveData() / UpdateData() / DeleteData() Persist changes.
AuditTrail() Write an audit entry (where wired up).

These classes are organised into product bundles:

  • FrameWork.SmartSoft.DataAccess — ~129 shared classes.
  • SmartSoft.Inventory.FrameWork.DataAccess — ~183 inventory classes (tbMaster_Barang, tv_BackOffice_D, …).
  • SmartSoft.POS.FrameWork.DataAccess — ~48 POS classes.
  • SmartSoft.Service.FrameWork.DataAccess — ~29 service classes.

The typed classes still build string SQL

The BrowseData classes look safer, but internally they also concatenate Criteria into the SQL string. They are a convenience wrapper, not a parameterization layer.

The shared weakness: no parameterization

SQL injection exposure is system-wide

Neither style parameterizes queries. Session and user-entered values are interpolated straight into SQL text. The only parameterized paths are the handful of stored-procedure calls (NonQuery(SqlCommand) with CommandType.StoredProcedure) and CekTable's @NamaFile. Everywhere else, a value containing a quote can break — or rewrite — the query.

This is the established pattern, not an isolated bug. Treat every string-built query as a potential injection point, and see Known Issues for the bigger picture.

Conventions & gotchas

Convention Detail
CommandTimeout = 0 No query timeout — used deliberately on long recompute/report queries.
Errors swallowed DataTable(...) returns Nothing on failure; callers often Try/CatchNothing. Check for Nothing, don't assume a DataTable.
REF_ERRORLOG NonQuery failures are logged here (minimal escaping only).
Reuse HAKDAC Prefer the global gateway over opening ad-hoc SqlConnections where a helper exists.
Oracle mirror MyLib.DACOracle mirrors the SQL helper for Oracle, but SQL Server is the deployed engine.

How to add a data call (following the grain)

  1. Reading a list? Prefer the typed BrowseData class for the table if one exists; otherwise inline StringBuilder + HAKDAC.DataTable.
  2. Writing? Use the transaction-class pattern (build an ArrayList of statements, run as one HAKDAC.NonQuery batch) so the writes stay together — see Transactions & Posting.
  3. Escape or validate any interpolated value you cannot avoid concatenating. Better still, use a stored proc via NonQuery(SqlCommand) for new sensitive writes.
  4. Check for Nothing after every DataTable(...) call.