Linger.DataAccess
1.4.2
dotnet add package Linger.DataAccess --version 1.4.2
NuGet\Install-Package Linger.DataAccess -Version 1.4.2
<PackageReference Include="Linger.DataAccess" Version="1.4.2" />
<PackageVersion Include="Linger.DataAccess" Version="1.4.2" />
<PackageReference Include="Linger.DataAccess" />
paket add Linger.DataAccess --version 1.4.2
#r "nuget: Linger.DataAccess, 1.4.2"
#:package Linger.DataAccess@1.4.2
#addin nuget:?package=Linger.DataAccess&version=1.4.2
#tool nuget:?package=Linger.DataAccess&version=1.4.2
Linger.DataAccess
A core data access library that provides database abstraction and common database operations.
Features
- Database Abstraction: Provider-agnostic database access
- CRUD Operations: Complete Create, Read, Update, Delete operations
- Async Support: Full async/await support for modern applications
- Multiple Data Types: Support for DataTable, DataSet, Entity objects, and Hashtable
- Transaction Support: Built-in transaction management
- SQL Builder: Helper for dynamic SQL generation
- Bulk Operations: Interface for high-performance bulk data insertion
- Batch Query: Large parameter list splitting (default batchSize = 1000) with parameterized & raw variants
Supported .NET Versions
- .NET 10.0
- .NET 9.0
- .NET 8.0
- .NET Framework 4.7.2+
Installation
This library is typically not installed directly, but is automatically referenced when installing specific database implementation packages:
# For SQL Server
dotnet add package Linger.DataAccess.SqlServer
# For Oracle Database
dotnet add package Linger.DataAccess.Oracle
# For SQLite
dotnet add package Linger.DataAccess.Sqlite
Core Interfaces
IDatabase
High-frequency interface groups (most commonly used):
- Execute commands:
ExecuteBySql(...),ExecuteByProc(...) - Query table/dataset:
Query(...),QueryTable(...),QueryAsync(...),QueryTableAsync(...) - List/entity mapping:
FindListBySql<T>(...),FindEntityBySql<T>(...) - Scalar helpers:
FindCountBySql(...),FindMaxBySql(...) - Batch query for huge
INlists:QueryInBatches(...),QueryInBatchesAsync(...)
Complete API reference (source of truth):
IDatabase: IDatabase.csIBaseDatabase: IBaseDatabase.csDatabaseimplementation: Database.cs
IProvider
Database provider abstraction for different database engines.
- Provider contract: IProvider.cs
- Low-level execution base: BaseDatabase.cs
Basic Usage
using Linger.DataAccess;
// Execute queries
var users = database.FindListBySql<User>("SELECT * FROM Users WHERE Active = 1");
var userTable = await database.FindTableBySqlAsync("SELECT * FROM Users");
// Batch query (IDs split automatically, default batchSize 1000)
var ids = Enumerable.Range(1, 5000).Select(i => i.ToString()).ToList();
var dt = database.QueryInBatches("SELECT * FROM Users WHERE Id IN ({0})", ids);
// Custom batch size
var dt500 = database.QueryInBatches("SELECT * FROM Users WHERE Id IN ({0})", ids, 500);
// Raw version (trusted numeric IDs only)
var dtRaw = database.QueryInBatchesRaw("SELECT * FROM Users WHERE Id IN ({0})", ids, 800, quote: false);
// Async parameterized version
var dtAsync = await database.QueryInBatchesAsync("SELECT * FROM Users WHERE Id IN ({0})", ids, 750);
// Execute commands
int affected = database.ExecuteBySql("UPDATE Users SET LastLogin = GETDATE()");
// Count operations
int userCount = await database.FindCountBySqlAsync("SELECT COUNT(*) FROM Users");
Batch Query
When dealing with very large IN lists (thousands of IDs) a single SQL statement may exceed length limits or degrade performance. The batch query helpers automatically split the list and concatenate the results.
// Parameterized (safe)
var result = database.QueryInBatches(
"SELECT * FROM Orders WHERE OrderId IN ({0})",
orderIds); // default batchSize = 1000
// Raw (only for trusted constant values)
var resultRaw = database.QueryInBatchesRaw(
"SELECT * FROM Orders WHERE OrderId IN ({0})",
orderIds, 500, quote: false);
Guidelines:
- Use {0} in sql where the batch placeholder will be injected.
- Prefer parameterized methods for security (prevents SQL injection).
- Raw methods are only for fully trusted data (e.g., internally generated numeric IDs).
- Adjust batchSize to balance network round-trips and SQL size limits.
Return Type:
- All batch methods merge rows into a single DataTable preserving schema of the first non-empty batch.
Transaction Contract
When calling low-level transaction overloads in BaseDatabase / IBaseDatabase:
transaction.Connectionmust be attached and non-null.- Detached/disposed transactions are rejected with
ArgumentNullException. - The connection used by transaction overloads comes from
transaction.Connection.
// Correct: transaction is attached to the same open connection
using var conn = provider.CreateConnection(connString);
conn.Open();
using var tx = conn.BeginTransaction();
_ = baseDatabase.ExecuteNonQuery(tx, CommandType.Text, "UPDATE Users SET Active = 1 WHERE Id = @Id", param);
// Invalid: detached transaction -> throws ArgumentNullException
// _ = baseDatabase.ExecuteNonQuery(detachedTx, CommandType.Text, "UPDATE ...", param);
Architecture
This library provides the foundation for database-specific implementations:
- Linger.DataAccess.SqlServer - SQL Server implementation
- Linger.DataAccess.Oracle - Oracle Database implementation
- Linger.DataAccess.Sqlite - SQLite implementation
Key Components
Database Class
Base implementation of IDatabase interface providing common database operations.
BaseDatabase Class
Core database functionality including connection management and parameter handling.
SqlBuilder Class
Helper utility for building dynamic SQL queries safely.
Async/Await Best Practices
True Async Implementation ✅
All async methods in this library use true asynchronous I/O operations, not Task.Run wrappers:
// ✅ TRUE ASYNC - Releases thread during I/O
public async Task<bool> ExistsAsync(string sql, CancellationToken ct = default)
{
var count = await FindCountBySqlAsync(sql).ConfigureAwait(false);
return count > 0;
}
// ❌ PSEUDO-ASYNC (Not used in this library)
// Task.Run just wraps synchronous blocking code
public Task<bool> BadExistsAsync(string sql)
{
return Task.Run(() => FindCountBySql(sql)); // Wastes thread pool threads
}
Performance Benefits
| Metric | Synchronous | Pseudo-Async (Task.Run) | True Async ✅ |
|---|---|---|---|
| Thread Usage | 1 thread blocked | 1 thread pool thread | 0 threads during I/O |
| Concurrency | ~Thousands | ~Thousands | ~Tens of thousands |
| Memory | ~1MB per thread | ~1MB per thread | ~Few KB per task |
| Scalability | Limited | Limited | Excellent |
| Cancellation | Not supported | Only before start | During I/O operation |
Usage Recommendations
// ✅ DO: Use async methods for I/O operations
var users = await database.FindTableBySqlAsync("SELECT * FROM Users");
var count = await database.FindCountBySqlAsync("SELECT COUNT(*) FROM Orders");
// ✅ DO: Use ConfigureAwait(false) in library code (already done internally)
var result = await database.QueryAsync(sql).ConfigureAwait(false);
// ✅ DO: Support cancellation tokens
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var data = await database.QueryTableAsync(sql, null, cts.Token);
// ❌ DON'T: Mix sync and async (use one or the other)
var badResult = database.FindTableBySqlAsync(sql).Result; // Can deadlock!
High Concurrency Scenarios
For applications handling thousands of concurrent requests:
// ✅ Scales to tens of thousands of concurrent operations
var tasks = Enumerable.Range(1, 10000)
.Select(id => database.FindTableBySqlAsync($"SELECT * FROM Orders WHERE Id = {id}"))
.ToList();
var results = await Task.WhenAll(tasks); // Minimal thread usage
Best Practices
- Use parameterized queries to prevent SQL injection
- Use batch query helpers for large IN lists instead of manual concatenation
- Implement proper disposal patterns with
usingstatements - Use async methods for I/O intensive operations - All async methods use true async I/O
- Always pass CancellationToken to async methods for proper cancellation support
- Use ConfigureAwait(false) in library code (already done internally)
- Choose appropriate database-specific implementations for optimal performance
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Framework | net472 is compatible. net48 was computed. net481 was computed. |
-
.NETFramework 4.7.2
- Linger.Utils (>= 1.4.2)
-
net10.0
- Linger.Utils (>= 1.4.2)
-
net8.0
- Linger.Utils (>= 1.4.2)
-
net9.0
- Linger.Utils (>= 1.4.2)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Linger.DataAccess:
| Package | Downloads |
|---|---|
|
Linger.DataAccess.SqlServer
Package Description |
|
|
Linger.DataAccess.Sqlite
Package Description |
|
|
Linger.DataAccess.Oracle
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.4.2 | 154 | 5/20/2026 |
| 1.4.1-preview | 150 | 5/12/2026 |
| 1.4.0 | 150 | 5/6/2026 |
| 1.3.3-preview | 144 | 5/5/2026 |
| 1.3.2-preview | 133 | 4/29/2026 |
| 1.3.1-preview | 143 | 4/28/2026 |
| 1.3.0-preview | 139 | 4/27/2026 |
| 1.2.0-preview | 150 | 3/29/2026 |
| 1.1.0 | 162 | 2/4/2026 |
| 1.0.3-preview | 156 | 1/9/2026 |
| 1.0.2-preview | 164 | 1/8/2026 |
| 1.0.0 | 355 | 11/12/2025 |
| 1.0.0-preview2 | 262 | 11/6/2025 |
| 1.0.0-preview1 | 255 | 11/5/2025 |
| 0.9.9 | 248 | 10/16/2025 |
| 0.9.8 | 260 | 10/14/2025 |
| 0.9.7-preview | 241 | 10/13/2025 |
| 0.9.6-preview | 235 | 10/12/2025 |
| 0.9.5 | 240 | 9/28/2025 |
| 0.9.4-preview | 256 | 9/25/2025 |