In many enterprise applications, the Data Access Layer is a thin technical boundary. It opens a connection, executes SQL, maps rows into objects, and returns typed models to the application.

In ITS Framework, the DAL is more than a database helper.

It is the data runtime engine of the platform.

The DAL knows how datasets are defined, how stored procedures are executed, how parameters are resolved, how data is cached, how DataSet objects are prepared, how DataRow entities flow to the UI, and how changes return to SQL Server.

The DAL is the layer that turns the database contract into a live runtime model.

The Core Idea

ITS Framework is database-driven.

The database defines:

Modules
Datasets
Stored procedures
Parameters
Validation rules
Master-detail relationships
Cell styles
UI composition
Reports
Configuration metadata

The DAL reads that contract and makes it executable.

It does not only do this:

Execute SQL and return rows.

It does this:

Resolve what dataset is needed.
Resolve which stored procedure executes.
Resolve which parameters are required.
Resolve where those parameters come from.
Execute the stored procedure.
Return a DataSet.
Prepare table names, keys, defaults, relations, and dynamic columns.
Cache the result when allowed.
Expose it to the UI engine.
Track changed rows.
Send changes back through structured SQL contracts.

That makes the DAL central to the framework.

DAL as a Named Dataset Registry

A key design idea is that application code does not always need to know SQL details directly.

Instead of treating every database call as custom code, datasets are known by name.

Conceptually:

ClaimDetails
UserSecurity
ModuleConfig
PharmacyPlans
EntityMaster

A named dataset represents a configured data operation.

The code asks for the dataset by logical name. The DAL knows what that name means.

The dataset configuration can define:

Stored procedure name
Command type
Command timeout
Parameter metadata
Cache TTL
Load behavior
Result table metadata

This means the caller does not need to hardcode every SQL command.

The caller asks for data by logical name.

The DAL resolves the physical execution.

This live Visual Studio debugging session follows the runtime from connection and configuration initialization through named dataset resolution, indexed row access, row-cache reads, and on-demand DataTable materialization.

Visual Studio debugging the ITS Framework DAL while resolving named datasets, rows, and cached data
From configured name to live data object. The debugger makes the runtime contract visible: strings resolve to DAL instances, configured datasets become DataSet, DataTable, and DataRow objects, and repeated indexed selections are served from memory when caching is enabled.

What to watch: connection and configuration initialization at 00:00 · row-array caching at 00:30 · implicit dataset, table, and row resolution at 00:49 · indexed row lookup without a database round trip at 01:20 · filtered and sorted row-cache reads at 01:45 · on-demand table materialization at 02:20.

Code: Named Access Through DAL

The framework makes named dataset access compact through conversion operators and helper methods.

From ITSDAL/ITSDAL.cs:

static public implicit operator DAL(string n)
{
    return new DAL(n);
}

static public implicit operator DataTable(DAL dal)
{
    return dal.T;
}

static public implicit operator DataSet(DAL dal)
{
    return dal.Ds;
}

This allows framework code to treat a dataset name as a data object.

For example, the code can resolve a named dataset into a DataTable:

DataTable t = (DAL)"Setting";

Or retrieve rows by dataset/table name:

static public DataRow R(string t, string f = "", string s = "")
{
    return Rs(t, f, s).FirstOrDefault();
}

static public DataRow RoId<T>(string t, params T[] ids)
{
    DataTable ix = new DAL(t);
    return ix?.RoId(ids);
}

This is not just syntactic convenience.

It reflects the architecture:

The dataset name is the logical contract. The DAL resolves the runtime object.

The Database Is the Contract

In traditional applications, the data contract is often duplicated across many layers:

SQL schema
C# entity
DTO
API contract
JSON model
UI model
Save model
Mapper configuration

ITS Framework avoids much of that duplication.

The database contract flows through the DAL as DataSet, DataTable, and DataRow.

The DAL does not try to hide the database behind an object model.

It preserves the database shape and carries it forward:

SQL Server
 -> Stored Procedure
 -> DAL
 -> DataSet
 -> DataTable
 -> DataRow
 -> UI Engine

That works because ITS Framework treats the database as the universal contract.

The DAL is the bridge between the stored database contract and the runtime application contract.

DataSet as Runtime Package

The DAL manages DataSet objects as first-class runtime assets.

A DataSet can contain multiple related DataTable objects.

That matters because enterprise screens are rarely single-table screens.

They often need:

Main rows
Lookup tables
Status tables
Validation tables
Child rows
Command metadata
Style information
Configuration rows

A single dataset load can return everything needed by a screen or framework component.

The DAL centralizes that behavior.

Instead of making many small calls from different UI components, the framework loads a configured dataset and distributes its tables to the correct controls.

This is why DataSet is not just a transport object in ITS Framework.

It is a runtime package.

Code: Stored Procedure Execution

The core execution path builds a SqlCommand from dataset metadata, resolves parameters, executes it, and fills an ITSDS.

From ITSDAL/ITSDAL.cs:

private ITSDS ExecDBCmm()
{
    using (SqlConnection cnn = new SqlConnection(DBUtls.GetCnn()))
    {
        using (SqlCommand cmm = new SqlCommand(DSNDMeta.Cmm, cnn))
        {
            try
            {
                if (DSNDMeta.TimeOut > 0)
                    cmm.CommandTimeout = DSNDMeta.TimeOut;

                StackTrace trace = new StackTrace();
                cmm.CommandType = DSNDMeta.CmmType;

                SqlParList sqlPars = new SqlParList(DSNDMeta.SqlPars);

                SQLPVMetas?.AddSQLPar(cmm, sqlPars, trace);
                DSNDMeta.AddFixedSQLPars(cmm, sqlPars, trace);
                AddSQLPars?.Invoke(cmm, sqlPars, trace);
                EnvAddSQLPars?.Invoke(cmm, sqlPars);

                ITSDS ds = new ITSDS(Ix);
                (new SqlDataAdapter(cmm)).Fill(ds);

                if (IsInit)
                    SetDSTbls(ds, DN);

                return ds;
            }
            catch (Exception ex)
            {
                throw new Exception($"SP NAME: {DSNDMeta.Cmm}", ex);
            }
        }
    }
}

This shows the main responsibilities of the DAL execution path:

Read command metadata.
Apply command timeout.
Build pending parameter checklist.
Add hardcoded/request parameters.
Add fixed metadata parameters.
Add UI/context parameters.
Add environment parameters.
Execute the command.
Fill the DataSet.
Prepare dataset tables.

The stored procedure call is not isolated from framework behavior.

It participates in metadata, parameter resolution, initialization, and dataset preparation.

Parameter Resolution Chain

One of the most important DAL concepts is parameter resolution.

A stored procedure may require parameters from many places:

Current user
Timezone
Module id
Selected rows
Parent form state
Filter controls
Dataset metadata
Security context

A conventional system may force the caller to know all parameters and pass them manually.

That creates tight coupling.

In ITS Framework, parameter resolution works like a checklist.

The DAL knows which parameters are pending.

Each layer contributes what it owns.

Conceptually:

Stored procedure requires:
@usr
@tz
@mod_id
@claim_ids
@active

The chain resolves them:

Security context adds @usr and @tz.
Dataset metadata adds @mod_id.
Grid selection adds @claim_ids.
Container/filter adds @active.

When all parameters are resolved, the stored procedure can execute.

The key idea is:

No single component needs to know the full parameter picture.

Each participant only asks:

Do I have anything to contribute?

If yes, it adds the parameter.

If not, it passes the request along.

Code: SqlParList as Checklist

SqlParList tracks which SQL parameters are still pending.

From ITSDAL/SqlParList.cs:

public class SqlParList
{
    public List<string> SqlPars { get; }

    public SqlParList(string[] splPars)
    {
        SqlPars = splPars.ToList();
    }

    public bool PendingPars
    { get { return SqlPars.Count > 0; } }

    public bool NoPendingPars
    { get { return SqlPars.Count == 0; } }
}

The important concept is simple:

SqlPars contains the parameters not yet resolved.
Each resolver tries to add what it owns.
When a parameter is added, it is checked off.
Execution succeeds when nothing remains pending.

The DAL does not need a giant method that knows every possible parameter source.

The checklist lets the framework compose parameter ownership from environment, metadata, controls, and containers.

Code: Structured Parameters from DataTables

The same checklist supports XML and structured table-valued parameters.

From ITSDAL/SqlParList.cs:

switch (t)
{
    case "x":
        return AddPar(cmm, SqlDbType.Xml, parMeta[0], () => tbl.XML(fltr), -1, par);
    case "xpc":
        return AddPar(cmm, SqlDbType.Xml, parMeta[0], () => tbl.GetChanges().XML(fltr), -1, par);
    case "stpc":
        return AddPar(cmm, SqlDbType.Structured, parMeta[0], () => tbl.GetChanges().UDTT(parMeta[2], fltr), -1, par);
    case "st":
        return AddPar(cmm, SqlDbType.Structured, parMeta[0], () => tbl.UDTT(parMeta[2], fltr), -1, par);
    default:
        return AddPar(cmm, SqlDbType.Structured, parMeta[0], () => tbl.UDTT(parMeta[2], fltr.ConcatFltr(Const.SelFltr)), -1, par);
}

This is a key part of the database tube.

A DataTable can become:

XML parameter
Structured UDTT parameter
Changed-rows-only XML
Changed-rows-only UDTT
Selected-rows-only UDTT

The DAL can therefore send row sets back to SQL Server without first converting them into DTOs.

Metadata-Driven SQL Parameters

The DAL also supports metadata-defined SQL parameters.

Instead of hardcoding every parameter in C#, metadata can describe the parameter name, type, length, and value.

From ITSDAL/DALMETA.cs:

public class SQLPMetas : Dictionary<string, SQLPMeta>
{
    public SQLPMetas(string m) : this()
    {
        Add(m);
    }

    public void Add(string str)
    {
        if (str.IsNullOrDefault())
            return;

        string[] m = new StrMeta(str, '&');
        foreach (string s in m)
        {
            Add(new SQLPMeta(s));
        }
    }

    public bool AddSQLPar(SqlCommand cmm, SqlParList sqlPars, StackTrace trace)
    {
        if (trace.WasInvoked(this, MethodBase.GetCurrentMethod()))
            return true;

        foreach (SQLPMeta p in Values)
        {
            if (!sqlPars.AddPar<Func<SqlParameter>>(cmm, () => p, p.N))
                return false;
        }

        return true;
    }
}

The format is compact, but the idea is powerful:

Metadata defines the parameter.
SQLPMetas parses it.
SqlParList checks whether it is needed.
SqlCommand receives the typed SqlParameter.

This supports the broader framework rule:

Configuration before hardcode.

Code: Parameter Type Conversion

SQLPMeta converts metadata into a typed SqlParameter.

From ITSDAL/DALMETA.cs:

private SqlParameter Par
{
    get
    {
        SqlParameter par;
        string pName = "@" + N;

        switch (PType)
        {
            case ParType.INT:
                par = new SqlParameter(pName, SqlDbType.Int);
                par.Value = _Val.IsNull() ? Const.NullId : _Val.As<int>();
                break;

            case ParType.BIT:
                par = new SqlParameter(pName, SqlDbType.Bit);
                par.Value = _Val.As<bool>();
                break;

            case ParType.STR:
                par = new SqlParameter(pName, SqlDbType.VarChar, PL);
                par.Value = _Val.IsNull() ? string.Empty : _Val.ToString();
                break;

            case ParType.NSTR:
                par = new SqlParameter(pName, SqlDbType.NVarChar, PL);
                par.Value = _Val.IsNull() ? string.Empty : _Val.ToString();
                break;

            case ParType.XML:
                par = new SqlParameter(pName, SqlDbType.Xml);
                par.Value = _Val.As<string>();
                break;

            case ParType.Long:
                par = new SqlParameter(pName, SqlDbType.BigInt);
                par.Value = _Val.As<long>();
                break;
        }

        return par;
    }
}

This is the typed boundary between metadata and SQL Server.

The metadata remains compact.

The final command still receives real SqlParameter objects with explicit SQL types.

Cache as a DAL Responsibility

The DAL also manages caching.

Not every dataset should be loaded the same way.

Some data is configuration data and changes rarely.

Some data is business data and changes often.

Some data should never be cached.

Some data can be cached for a short period.

A dataset can have cache behavior such as:

Cache forever
Cache for N milliseconds
Do not cache
Reuse from shared cache
Reload on demand

The DAL centralizes this behavior.

The ITS Framework desktop runtime targets .NET 10 for Windows. In its execution model, the cache boundary is the application process, not an individual module. Each module instance runs on its own STA thread with its own Windows message loop, while those module threads remain inside the same process, managed runtime, and address space.

The DAL caches are therefore static, in-process state shared by every module thread in that application process. No IPC, shared-memory mechanism, or central cache service is required. This sharing stops at the process boundary: a separate application process has its own DAL cache.

The caller asks for the dataset.

The DAL decides whether to:

Return cached DataSet
Refresh expired DataSet
Execute stored procedure
Bypass cache
Invalidate row-selection cache
Notify listeners

Code: DataSet Cache with TTL

From ITSDAL/ITSDAL.cs:

private class DSTTLWrapper
{
    public DSTTLWrapper(DataSet ds, int ttl, string n)
    {
        Ds = ds;
        N = n;
        _TTL = ttl == 0 ? DateTime.MaxValue : DateTime.UtcNow.AddMilliseconds(ttl);
    }

    public string N { get; }

    public DataSet Ds { get; }

    private DateTime _TTL;

    public bool Expired
    {
        get { return DateTime.UtcNow > _TTL; }
    }

    public void ReleaseCacheDs()
    {
        (Ds as ITSDS).ReleaseFromRWCache();
        _DsCache.Remove(N);
    }
}

The cache wrapper makes TTL behavior part of the dataset lifecycle.

A dataset with TTL = 0 can effectively live for the application lifetime.

A dataset with a positive TTL expires after that interval.

A dataset with a negative TTL can be treated as non-cacheable by the load path.

Code: Load and Reuse Cached DataSets

From ITSDAL/ITSDAL.cs:

private void Load(bool forceRefresh)
{
    if (DN == "C")
    {
        _Ds = _C;
        return;
    }

    DSTTLWrapper dsw;
    _DsCache.TryGetValue(DN, out dsw);
    if (dsw != null)
    {
        if (dsw.Expired || forceRefresh)
            dsw.ReleaseCacheDs();
        else
        {
            _Ds = dsw.Ds;
            return;
        }
    }

    _Ds = this.ExecDBCmm();

    if (DSNDMeta.TTL < 0 || _Ds == null || _Ds.Tables.Count == 0)
        return;

    dsw = new DSTTLWrapper(_Ds, DSNDMeta.TTL, DN);

    _DsCache[DN] = dsw;

    _Notifier.Notify(DN);
    EvDSUpdated?.Invoke(null, new DSUpdatedEvArg(DN));
}

The caller does not manage caching directly.

The dataset metadata and the DAL load path decide whether the data is reused, refreshed, or bypassed.

This avoids duplicated cache rules across modules.

Initialization as a Single Data Contract Load

At startup, the DAL loads the framework configuration dataset.

This initialization dataset is the bootstrap contract of the platform.

Code extract. The following fragment from ITSDAL/ITSDAL.cs is abridged to show the bootstrap sequence. It is not a complete production implementation when surrounding guards, logging, cleanup, or environment-specific branches are omitted.

static public bool Init(Action<SqlCommand, SqlParList> envAddSQLPars,
                     string initSP, Action<DataTable> setTblStndrCls)
{
    try
    {
        _DsCache = new ConcurrentDictionary<string, DSTTLWrapper>();
        _RwsCache = new ConcurrentDictionary<(string, string), DataRow[]>();
        _RwsCacheTbls = new ConcurrentDictionary<object, List<(string, string)>>();
        EnvAddSQLPars = envAddSQLPars;
        _SetEnvStrTbCls = setTblStndrCls;

        using (SqlConnection cnn = new SqlConnection(DBUtls.GetCnn()))
        {
            using (SqlCommand cmm = new SqlCommand(initSP, cnn))
            {
                cmm.CommandType = CommandType.StoredProcedure;

                _C = new ITSDS();
                (new SqlDataAdapter(cmm)).Fill(_C);

                DataColumn[] pks = { DSN.Columns["n"] };
                DSN.PrimaryKey = pks;

                SetDSTbls(_C, "C");

                return true;
            }
        }
    }
    catch (Exception)
    {
        if (GlobalSettings.DebugDAL)
            throw;

        return false;
    }
}

These dictionaries are initialized once for the application process and then reused by all module STA threads in that process; they are not recreated for each module instance. The synchronization around them coordinates concurrent access from those module threads.

This initialization does several important things:

Creates shared caches.
Registers environment parameter injection.
Loads the init stored procedure.
Stores the configuration DataSet.
Sets the primary key on the dataset registry table.
Prepares table metadata.

The framework starts by loading its database-defined operating contract.

Preparing DataSets for Runtime Use

After a dataset is loaded, the DAL prepares it.

It can assign table names, primary keys, default values, dynamic rows, relations, and dynamic columns.

From ITSDAL/ITSDAL.cs:

public static void SetDSTbls(DataSet ds, string dn)
{
    ds.DataSetName = dn;

    foreach (DataTable t in ds.Tables)
    {
        if (t.Columns.Contains("sys"))
            t.Columns["sys"].DefaultValue = false;

        DataRow dsExt = R("DsExt", "n='" + dn + "' and ix=" + ds.Tables.IndexOf(t).ToString());

        SetTbl(t, dsExt);

        switch (dn)
        {
            case "L":
                break;
            case "C":
                break;
            default:
                _SetEnvStrTbCls?.Invoke(t);
                break;
        }

        DataRow[] ixs = DSN.Select("d='" + dn + "' and ix=" + ds.Tables.IndexOf(t).ToString());
        DataRow ix = ixs.Length == 1 ? ixs[0] : null;

        SetTbl(t, ix);

        t.AcceptChanges();
    }

    ds.SetRelations(dn);
    ds.SetDynColumns(dn);
}

This is an important distinction.

The DAL does not just fill tables.

It turns raw SQL results into framework-aware runtime tables.

Row Cache for Repeated Selects

The DAL also supports caching filtered DataRow[] selections.

Code extract. This abridged fragment from ITSDAL/ITSDAL.cs illustrates the relationship between cached row selections and the tables that invalidate them.

public static DataRow[] Rs(string t, string f = "", string s = "")
{
    if (!EnableCacheRs)
    {
        DataTable ixTbl = new DAL(t);
        return ixTbl?.Rs(f, s);
    }

    var key = (t, $"{f}-{s}");

    if (_RwsCache.TryGetValue(key, out var cachedRows))
        return cachedRows;

    var ix = new DAL(t);
    DataTable tbl = ix;
    var rows = tbl?.Rs(f, s);

    if (ix.DN != "C" && !_DsCache.ContainsKey(ix.DN))
        return rows;

    _RwsCacheTbls.AddOrUpdate(tbl,
        _ =>
        {
            (tbl.DataSet as ITSDS)?.SyncWCache(tbl);
            return new List<(string, string)> { key };
        },
        (_, existingList) =>
        {
            existingList.Add(key);
            return existingList;
        });

    _RwsCache.TryAdd(key, rows);

    return rows;
}

Concurrency note. This concurrency is real in the ITS execution model because independent module message loops can call the same static DAL cache at the same time. ConcurrentDictionary protects operations on the dictionary; it does not make a mutable List<T> stored as a value thread-safe. The call to existingList.Add(key) can race when multiple callers update the same table entry. A production implementation must synchronize every access to that list with the same lock, or replace it with a concurrent or immutable value collection. The extract above illustrates the cache-indexing concept, not a complete concurrency implementation.

The row cache is connected to table changes.

When a table changes, cached selections can be released.

This keeps repeated lookups fast while still responding to modifications.

DAL and DataRow as Entity

The DAL is what makes DataRow as Entity possible.

The DAL does not convert rows into separate objects.

It returns the DataSet.

The DataTable carries the schema.

The DataRow carries the entity.

That means the UI can bind directly to the data:

DAL
 -> DataSet
 -> DataTable
 -> DataRow
 -> Grid / Editor

When the user edits data, the same DataRow tracks the change.

The DAL can later participate in saving those changes.

The row does not need to be converted into a DTO first.

That keeps the system direct.

The Tube from Database to Client

The DAL participates in the larger ITS Framework tube:

SQL Server
 -> Stored Procedure
 -> DAL
 -> DataSet
 -> DataTable
 -> DataRow
 -> WinForms Binding
 -> Grid / Editor
 -> Changed DataRow
 -> UDTT
 -> Stored Procedure
 -> SQL Server

The DAL controls the first half:

SQL Server
 -> Stored Procedure
 -> DataSet

and supports the return path:

Changed rows
 -> XML / UDTT / structured parameters
 -> Stored Procedure
 -> SQL Server

This avoids unnecessary translation layers.

No ORM entity is required.

No DTO is required.

No JSON contract is required between the app and the database.

The data remains database-native.

DALWF: The WinForms Bridge

The architecture separates pure data access from WinForms binding concerns.

Conceptually:

ITSDAL   -> stored procedures, DataSet management, caching
ITSDALWF -> bridge between DAL and WinForms UI controls

This distinction matters.

The base DAL understands SQL execution and datasets.

The WinForms bridge understands how those datasets bind into controls.

That allows the framework to keep data access logic centralized while still supporting a rich UI engine.

The UI does not need to know how every stored procedure is executed.

The UI asks for data and receives bindable tables and rows.

DAL as Runtime Contract Resolver

The DAL resolves many contracts at runtime:

Dataset name -> stored procedure
Dataset metadata -> SQL parameters
Security context -> environment parameters
UI control state -> selected row parameters
Cache metadata -> reuse or reload
Stored procedure result -> DataSet
DataSet tables -> UI controls
Changed rows -> save payload

This is why the DAL is more than an infrastructure layer.

It is a runtime contract resolver.

It sits between:

Database configuration
SQL execution
Framework metadata
UI binding
Change tracking
Persistence

and makes them work as one system.

Why This Saves Code

Without this kind of DAL, every module would need its own version of:

Build SQL command
Add user parameter
Add timezone parameter
Add selected rows
Add filters
Execute stored procedure
Fill DataSet
Prepare table metadata
Cache result
Bind grid
Track changes
Save changed rows
Handle errors
Refresh dependent data

That would create repeated code across every module.

The ITS DAL makes these operations framework behavior.

A module can focus on intent:

Which dataset do I need?
Which context am I in?
What changed?
What command is being executed?

The framework handles the mechanics.

DAL and Conventions

The DAL benefits from naming conventions.

If columns are named consistently, the DAL and UI engine can avoid mappings.

Examples:

id
name
descr
usr
ts
action_id
csys_*

These conventions help the framework infer:

Identifiers
Display values
Descriptions
User references
Timestamps
Relationships
System columns

This reduces configuration and avoids hardcoded special cases.

The DAL works best when the database contract is predictable.

DAL and Configuration

The DAL is configuration-aware, but it should not require configuration for everything.

The framework rule still applies:

Conventions over configuration.
Configuration before hardcode.

The DAL should infer what it can.

It should read configuration when behavior varies.

It should rely on code only for stable mechanisms.

For example:

Column name follows convention
    -> infer behavior

Dataset has cache metadata
    -> apply configured cache behavior

Stored procedure requires parameters
    -> resolve through parameter chain

Execution mechanics
    -> framework code

This balance keeps the DAL powerful without making it noisy.

The Real Innovation

The innovation is not simply having a DAL.

Most systems have one.

The innovation is that the DAL is not a mapper layer.

It is not an ORM wrapper.

It is not a repository abstraction over entity classes.

It is a data runtime engine for a database-driven framework.

It manages:

Named datasets
Stored procedure execution
Parameter resolution
Environment context
Metadata-defined parameters
DataSet lifecycle
Cache behavior
DataRow entities
Change tracking
Structured saves
UI binding support

That makes it the central tube controller between SQL Server and the runtime UI.

Final Principle

The concept can be summarized like this:

The DAL does not translate the database into another model. It carries the database contract into the runtime.

Or:

**The database defines the contract.

The DAL executes the contract.

The DataSet carries the contract.

The DataRow becomes the entity.

The UI binds to the entity.**

That is the role of the DAL in ITS Framework.

Summary

In ITS Framework, the DAL is not just a helper for SQL calls.

It is the layer that makes the database-driven platform possible.

It resolves dataset names, stored procedures, parameters, cache behavior, runtime context, DataSet lifecycle, DataRow entities, and save contracts.

It supports the full tube:

SQL Server
 -> Stored Procedure
 -> DAL
 -> DataSet
 -> DataTable
 -> DataRow
 -> UI
 -> Changed DataRow
 -> UDTT
 -> Stored Procedure
 -> SQL Server

This avoids unnecessary DTOs, ORM entities, mapper layers, and duplicated models.

The DAL is the controlled passage between the database contract and the living application.

That is why, in ITS Framework, the DAL is not only data access.

It is the data runtime engine.