Chiola.AseClient
9.0.3
dotnet add package Chiola.AseClient --version 9.0.3
NuGet\Install-Package Chiola.AseClient -Version 9.0.3
<PackageReference Include="Chiola.AseClient" Version="9.0.3" />
<PackageVersion Include="Chiola.AseClient" Version="9.0.3" />
<PackageReference Include="Chiola.AseClient" />
paket add Chiola.AseClient --version 9.0.3
#r "nuget: Chiola.AseClient, 9.0.3"
#:package Chiola.AseClient@9.0.3
#addin nuget:?package=Chiola.AseClient&version=9.0.3
#tool nuget:?package=Chiola.AseClient&version=9.0.3
Chiola.AseClient
This is a fork of DataAction/AdoNetCore.AseClient, a .NET data provider for SAP/Sybase ASE originally created and maintained by DataAction and its contributors. All credit for the original TDS protocol implementation goes to them — see LICENSE (Apache-2.0, preserved from upstream).
Why this fork exists: built as the driver dependency for
Chiola.EntityFrameworkCore.Ase, a from-scratch EF Core provider for SAP ASE. Several real bugs and gaps in the upstream driver were found and worked around while building that provider (see itsDECISIONS.md) — this fork exists to fix them directly at the source instead of accumulating workarounds downstream.What changed from upstream (see
DECISIONS.mdfor the full history and reasoning behind each of these — including a detour where the fork briefly targeted upstream's entire legacy matrix plusnet5.0–net9.0, and aStrongNamepackage, before both were reverted to keep this fork minimal for its one real consumer):
- Targets
net9.0only (upstream targetsnetcoreapp1.0throughnetstandard2.0/net46). This fork exists to serveChiola.EntityFrameworkCore.Ase, which is itselfnet9.0-only — no other consumer to support, so no reason to carry upstream's wider compatibility matrix.- Dropped the
AdoNetCore.AseClient.BenchmarkandAdoNetCore.AseClient.StrongNameprojects — neither relevant to this fork's single consumer.- Test project pinned to NUnit 3.x (upstream's actual test suite, ~1200 unit tests + a real integration suite against live ASE) rather than upgrading to NUnit 4 outright, to avoid churn from its
CollectionAssert/StringAssertnamespace move — revisit later if worth it.- Real bug fixes (see
DECISIONS.mdfor each):AseConnection.ClearPool()/ClearPools()were literal no-ops upstream (//todo: implement) — implemented for real via a pool-generation pattern. Added proactive idle-connection eviction to the pool (previously only checked lazily, when a connection was next reserved — a pool that went idle just stayed open on the server forever).- Versioning diverges from upstream's
0.xscheme on purpose: this package is versioned9.0.0to signal its minimum/only target framework (net9.0) at a glance, not to track upstream's release history or imply any particular feature-completeness level.Package published as
Chiola.AseClienton NuGet (notAdoNetCore.AseClient, to avoid clashing with the upstream package).
A .NET data provider for SAP ASE — fork of DataAction/AdoNetCore.AseClient.
SAP (formerly Sybase) has supported accessing the ASE database management system from ADO.NET for many years. Unfortunately SAP has not yet made a driver available to support .NET Core, so this project enables product teams that are dependent upon ASE to keep moving their application stack forwards.
The current .NET 4 version of SAP's Sybase.Data.AseClient driver is a .NET Framework managed wrapper around SAP's unmanged ADO DB provider and is dependent upon COM. COM is a Windows-only technology and will never be available to .NET Core, making it difficult to port the existing SAP driver.
Under the hood, ASE (and Microsoft Sql Server for that matter) relies on an application-layer protocol called Tabular Data Stream to transfer data between the database server and the client application. ASE uses TDS 5.0.
This project provides a .NET Core native implementation of the TDS 5.0 protocol via an ADO.NET DB Provider, making SAP ASE accessible from .NET Core applications hosted on Windows, Linux, Docker and also serverless platforms like AWS Lambda.
Table of Contents
Downloads
The latest stable release of the AdoNetCore.AseClient is available on NuGet.
Objectives
Functional parity with the
Sybase.Data.AseClientprovided by SAP. Ideally, our driver will be a drop in replacement for theSybase.Data.AseClient(with some namespace changes). The following types are supported:- AseClientFactory - .NET Core 2.1+
- AseCommand
- AseCommandBuilder
- AseConnection
- AseConnectionPool
- AseConnectionPoolManager
- AseDataAdapter
- AseDataReader
- AseDbType
- AseDecimal
- AseError
- AseErrorCollection
- AseException
- AseInfoMessageEventArgs
- AseInfoMessageEventHandler
- AseParameter
- AseParameterCollection
- AseRowUpdatedEventArgs - .NET Core 2.0+
- AseRowUpdatedEventHandler - .NET Core 2.0+
- AseRowUpdatingEventArgs - .NET Core 2.0+
- AseRowUpdatingEventHandler - .NET Core 2.0+
- TraceEnterEventHandler
- TraceExitEventHandler
Not all features are currently supported, and some features will not be supported. Refer to upstream's Unsupported features wiki page (still applicable — this fork hasn't diverged on feature support yet).
Performance equivalent to or better than that of
Sybase.Data.AseClientprovided by SAP. This is possible as we are eliminating the COM and OLE DB layers from this driver and .NET Core is fast.Target
net9.0(see the fork notice at the top of this README for why this differs from upstream's wider target matrix).Should work with Dapper at least as well as the
Sybase.Data.AseClient
Performance benchmarks
The benchmark project (AdoNetCore.AseClient.Benchmark) and its historical results against Sybase.Data.AseClient were dropped in this fork (see the fork notice above) — not relevant to this fork's goal of serving Chiola.EntityFrameworkCore.Ase. See upstream's README for the original methodology and results.
Connection strings
connectionstrings.com lists the following connection string properties for the ASE ADO.NET Data Provider. In keeping with our objective of being a drop-in replacement for the Sybase.Data.AseClient, we aim to use identical connection string syntax to the Sybase.Data.AseClient, however our support for the various properties will be limited. Our support is as follows:
| Property | Support | Notes |
|---|---|---|
AnsiNull |
✓ | By default (0) AnsiNull is disabled which means that SQL statements can use = NULL and IS NULL syntax. Set to 1 to instruct the connection to only permit IS NULL syntax. |
ApplicationName or Application Name |
✓ | |
BufferCacheSize |
✓ | Buffer caching is automatically managed via an internal ArrayPool<T>. Setting this value in the connection string does nothing, but the behaviour is supported. |
Charset |
✓ | If not specified, the server should dictate the character set |
ActualCharset |
✓ | New in this fork. Forces the encoding used to decode/encode string data, unconditionally overriding whatever the server declares via ENVCHANGE (unlike Charset, which is only a fallback if the server doesn't specify one). For servers that declare a stale/wrong charset (e.g. cp850, a common default ASE-on-Windows install setting) while the bytes actually on disk were written in a different one (e.g. windows-1252, from a client that never negotiated charset correctly) — applies to both reads and writes, since the server performs no real charset conversion in that scenario either way. Legacy charsets like windows-1252/cp850 need Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance) registered by the consumer — the driver doesn't do this on its own. See DECISIONS.md for the real production case this was built for. |
ClientHostName |
✓ | |
ClientHostProc |
✓ | |
CodePageType |
✕ | This doesn't appear to be relevant any more. You can specify the Charset without reference to a code page type, or allow the server to set the Charset which is the default behaviour. |
Connection Lifetime or ConnectionLifetime |
✓ | |
ConnectionIdleTimeout or Connection IdleTimeout or Connection Idle Timeout |
✓ | |
CumulativeRecordCount |
TODO | |
Database or Db or Initial Catalog |
✓ | |
Data Source or DataSource or Address or Addr or Network Address or Server Name |
✓ | |
DSURL or Directory Service URL |
✓ | Multiple URLs are not supported; network drivers other than NLWNSCK (TCP/IP socket) are not supported; LDAP is not supported |
EnableServerPacketSize |
✓ | |
Encryption |
✓ | The designated encryption. Possible values: ssl, none. |
EncryptPassword |
✓ | Values 0 (disabled) and 1 (enabled) are supported. The highest encryption standard of the ASE 15.x and 16x servers is implemented. |
LoginTimeOut or Connect Timeout or Connection Timeout |
✓ | For pooled connections this translates to the time it takes to reserve a connection from the pool |
Max Pool Size |
✓ | |
Min Pool Size |
✓ | <ul><li>The pool will attempt to prime itself on creation up to this size (in a thread)</li><li>When a connection is killed, the pool will attempt to replace it if the pool size is less than Min</li></ul> |
NamedParameters |
✓ | |
PacketSize or Packet Size |
✓ | The server can decide to change this value |
Ping Server |
✓ | |
Pooling |
✓ | |
Port or Server Port |
✓ | |
Pwd or Password |
✓ | |
TextSize |
✓ | |
TrustedFile |
✓ | This property must be used along with Encryption=ssl. The value must be set to the path to the trusted file. |
Uid or UserID or User ID or User |
✓ | |
UseAseDecimal |
✓ |
Supported types
Types supported when sending requests to the database
| DbType | Send | .NET Type(s) | Notes |
|---|---|---|---|
AnsiString |
✓ | string |
|
AnsiStringFixedLength |
✓ | string |
|
Binary |
✓ | byte[] |
|
Boolean |
✓ | bool |
|
Byte |
✓ | byte |
|
Currency |
✓ | decimal |
Sent as decimal type; may change to send as TDS_MONEY, which is shorter |
Date |
✓ | DateTime |
Time component is ignored |
DateTime |
✓ | DateTime |
|
DateTime2 |
X | ASE does not support a DateTime2 type. Use DateTime instead |
|
DateTimeOffset |
X | ASE does not support a DateTimeOffset type. Use DateTime instead |
|
Decimal |
✓ | decimal |
|
Double |
✓ | double |
|
Guid |
✓ | System.Guid |
Technically ASE does not support GUID or UUID types. Our driver supports it, but converts to Binary under the hood. You can obtain the same result by calling .ToByteArray() and using DbType.Binary. |
Int16 |
✓ | short |
|
Int32 |
✓ | int |
|
Int64 |
✓ | long |
|
Object |
X | ASE does not support an Object type |
|
SByte |
✓ | sbyte |
Sent as int16 |
Single |
✓ | float |
|
String |
✓ | string |
UTF-16 encoded, sent to server as binary with usertype 35 |
StringFixedLength |
✓ | string |
UTF-16 encoded, sent to server as binary with usertype 34 |
Time |
✓ | TimeSpan |
|
UInt16 |
✓ | ushort |
|
UInt32 |
✓ | uint |
|
UInt64 |
✓ | ulong |
|
VarNumeric |
✓ | decimal |
|
Xml |
X | ASE does not support an Xml type |
Types supported when reading responses from the database
| ASE Type | Receive | .NET Type(s) | Notes |
|---|---|---|---|
bigdatetime |
X | DateTime |
To be implemented. TDS_BIGDATETIME = 0xBB |
bigint |
✓ | long |
|
bigtime |
X | DateTime |
To be implemented. TDS_BIGTIME = 0xBC |
binary |
✓ | byte[] |
|
bit |
✓ | bool |
|
char |
✓ | string |
|
date |
✓ | DateTime |
|
datetime |
✓ | DateTime |
|
decimal |
✓ | decimal |
|
double precision |
✓ | double |
|
float |
✓ | float |
|
image |
✓ | byte[] |
|
int |
✓ | int |
|
money |
✓ | decimal |
|
nchar |
✓ | string |
|
numeric |
✓ | decimal |
|
nvarchar |
✓ | string |
|
smalldatetime |
✓ | DateTime |
|
smallint |
✓ | short |
|
smallmoney |
✓ | decimal |
|
time |
✓ | DateTime |
We have added a GetTimeSpan method to AseDataReader |
tinyint |
✓ | byte |
|
unichar |
✓ | string |
Server sends as binary with usertype 34 |
univarchar |
✓ | string |
Server sends as binary with usertype 35 |
unsigned bigint |
✓ | ulong |
|
unsigned int |
✓ | uint |
|
unsigned smallint |
✓ | usmallint |
|
varchar |
✓ | string |
|
text |
✓ | string |
|
unitext |
✓ | string |
|
varbinary |
✓ | byte[] |
Code samples
Open a database connection
var connectionString = "Data Source=myASEserver;Port=5000;Database=myDataBase;Uid=myUsername;Pwd=myPassword;";
using(var connection = new AseConnection(connectionString))
{
connection.Open();
// use the connection...
}
Execute a SQL statement and read response data
var connectionString = "Data Source=myASEserver;Port=5000;Database=myDataBase;Uid=myUsername;Pwd=myPassword;";
using (var connection = new AseConnection(connectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "SELECT FirstName, LastName FROM Customer";
using (var reader = command.ExecuteReader())
{
// Get the results.
while (reader.Read())
{
var firstName = reader.GetString(0);
var lastName = reader.GetString(1);
// Do something with the data...
}
}
}
}
Execute a SQL statement that returns no results
var connectionString = "Data Source=myASEserver;Port=5000;Database=myDataBase;Uid=myUsername;Pwd=myPassword;";
using (var connection = new AseConnection(connectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "INSERT INTO Customer (FirstName, LastName) VALUES ('Fred', 'Flintstone')";
var recordsModified = command.ExecuteNonQuery();
}
}
Execute a SQL statement that returns a scalar value
var connectionString = "Data Source=myASEserver;Port=5000;Database=myDataBase;Uid=myUsername;Pwd=myPassword;";
using (var connection = new AseConnection(connectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "SELECT COUNT(*) FROM Customer";
var result = command.ExecuteScalar();
}
}
Use input parameters with a SQL query
Note: ASE only allows Output, InputOutput, and ReturnValue parameters with stored procedures
var connectionString = "Data Source=myASEserver;Port=5000;Database=myDataBase;Uid=myUsername;Pwd=myPassword;";
using (var connection = new AseConnection(connectionString)
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "SELECT TOP 1 FirstName FROM Customer WHERE LastName = @lastName";
command.Parameters.AddWithValue("@lastName", "Rubble");
var result = command.ExecuteScalar();
}
}
Execute a stored procedure and read response data
var connectionString = "Data Source=myASEserver;Port=5000;Database=myDataBase;Uid=myUsername;Pwd=myPassword;";
using (var connection = new AseConnection(connectionString)
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "GetCustomer";
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@lastName", "Rubble");
using (var reader = command.ExecuteReader())
{
// Get the results.
while (reader.Read())
{
var firstName = reader.GetString(0);
var lastName = reader.GetString(1);
// Do something with the data...
}
}
}
}
Execute a stored procedure that returns no results
var connectionString = "Data Source=myASEserver;Port=5000;Database=myDataBase;Uid=myUsername;Pwd=myPassword;";
using (var connection = new AseConnection(connectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "CreateCustomer";
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@firstName", "Fred");
command.Parameters.AddWithValue("@lastName", "Flintstone");
command.ExecuteNonQuery();
}
}
Execute a stored procedure that returns a scalar value
var connectionString = "Data Source=myASEserver;Port=5000;Database=myDataBase;Uid=myUsername;Pwd=myPassword;";
using (var connection = new AseConnection(connectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "CountCustomer";
command.CommandType = CommandType.StoredProcedure;
var result = command.ExecuteScalar();
}
}
Use input, output, and return parameters with a stored procedure
var connectionString = "Data Source=myASEserver;Port=5000;Database=myDataBase;Uid=myUsername;Pwd=myPassword;";
using (var connection = new AseConnection(connectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "GetCustomerFirstName";
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@lastName", "Rubble");
var outputParameter = command.Parameters.Add("@firstName", AseDbType.VarChar);
outputParameter.Direction = ParameterDirection.Output;
var returnParameter = command.Parameters.Add("@returnValue", AseDbType.Integer);
returnParameter.Direction = ParameterDirection.ReturnValue;
command.ExecuteNonQuery();
//Do something with outputParameter.Value and returnParameter.Value...
}
}
Execute a stored procedure and read response data with Dapper
var connectionString = "Data Source=myASEserver;Port=5000;Database=myDataBase;Uid=myUsername;Pwd=myPassword;";
using (var connection = new AseConnection(connectionString))
{
connection.Open();
var barneyRubble = connection.Query<Customer>("GetCustomer", new {lastName = "Rubble"}, commandType: CommandType.StoredProcedure).First();
// Do something with the result...
}
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 was computed. 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. |
-
net9.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Chiola.AseClient:
| Package | Downloads |
|---|---|
|
Chiola.EntityFrameworkCore.Ase
Entity Framework Core provider for SAP ASE (Sybase Adaptive Server Enterprise). |
GitHub repositories
This package is not used by any popular GitHub repositories.